Exemple #1
0
    PlanStage::StageState NearStage::advanceNext(WorkingSetID* toReturn) {

        if (_resultBuffer.empty()) {

            getNearStats()->intervalStats.push_back(*_nextIntervalStats);
            _nextIntervalStats.reset();

            _searchState = SearchState_Buffering;
            return PlanStage::NEED_TIME;
        }

        *toReturn = _resultBuffer.top().resultID;
        _resultBuffer.pop();
        return PlanStage::ADVANCED;
    }
    GeoNear2DSphereStage::GeoNear2DSphereStage(const GeoNearParams& nearParams,
                                               OperationContext* txn,
                                               WorkingSet* workingSet,
                                               Collection* collection,
                                               IndexDescriptor* s2Index)
        : NearStage(txn,
                    workingSet,
                    collection,
                    new PlanStageStats(CommonStats(kS2IndexNearStage.c_str()),
                                       STAGE_GEO_NEAR_2DSPHERE)),
          _nearParams(nearParams),
          _s2Index(s2Index),
          _fullBounds(geoNearDistanceBounds(nearParams.nearQuery)),
          _currBounds(_fullBounds.center(), -1, _fullBounds.getInner()),
          _boundsIncrement(twoDSphereBoundsIncrement(s2Index)) {

        getNearStats()->keyPattern = s2Index->keyPattern();
    }
    GeoNear2DStage::GeoNear2DStage(const GeoNearParams& nearParams,
                                   OperationContext* txn,
                                   WorkingSet* workingSet,
                                   Collection* collection,
                                   IndexDescriptor* twoDIndex)
        : NearStage(txn,
                    workingSet,
                    collection,
                    new PlanStageStats(CommonStats(kTwoDIndexNearStage.c_str()),
                                       STAGE_GEO_NEAR_2D)),
          _nearParams(nearParams),
          _twoDIndex(twoDIndex),
          _fullBounds(twoDDistanceBounds(nearParams, twoDIndex)),
          _currBounds(_fullBounds.center(), -1, _fullBounds.getInner()),
          _boundsIncrement(twoDBoundsIncrement(nearParams)) {

        getNearStats()->keyPattern = twoDIndex->keyPattern();
    }
    StatusWith<NearStage::CoveredInterval*> //
    GeoNear2DSphereStage::nextInterval(OperationContext* txn,
                                       WorkingSet* workingSet,
                                       Collection* collection) {

        // The search is finished if we searched at least once and all the way to the edge
        if (_currBounds.getInner() >= 0 && _currBounds.getOuter() == _fullBounds.getOuter()) {
            return StatusWith<CoveredInterval*>(NULL);
        }

        //
        // Setup the next interval
        //

        const NearStats* stats = getNearStats();

        if (!stats->intervalStats.empty()) {

            const IntervalStats& lastIntervalStats = stats->intervalStats.back();

            // TODO: Generally we want small numbers of results fast, then larger numbers later
            if (lastIntervalStats.numResultsBuffered < 300)
                _boundsIncrement *= 2;
            else if (lastIntervalStats.numResultsBuffered > 600)
                _boundsIncrement /= 2;
        }

        R2Annulus nextBounds(_currBounds.center(),
                             _currBounds.getOuter(),
                             min(_currBounds.getOuter() + _boundsIncrement,
                                 _fullBounds.getOuter()));
        
        bool isLastInterval = (nextBounds.getOuter() == _fullBounds.getOuter());
        _currBounds = nextBounds;

        //
        // Setup the covering region and stages for this interval
        //

        IndexScanParams scanParams;
        scanParams.descriptor = _s2Index;
        scanParams.direction = 1;
        // We use a filter on the key.  The filter rejects keys that don't intersect with the
        // annulus.  An object that is in the annulus might have a key that's not in it and a key
        // that's in it.  As such we can't just look at one key per object.
        //
        // This does force us to do our own deduping of results, though.
        scanParams.doNotDedup = true;
        scanParams.bounds = _nearParams.baseBounds;

        // Because the planner doesn't yet set up 2D index bounds, do it ourselves here
        const string s2Field = _nearParams.nearQuery.field;
        const int s2FieldPosition = getFieldPosition(_s2Index, s2Field);
        scanParams.bounds.fields[s2FieldPosition].intervals.clear();
        OrderedIntervalList* coveredIntervals = &scanParams.bounds.fields[s2FieldPosition];

        TwoDSphereKeyInRegionExpression* keyMatcher = 
            new TwoDSphereKeyInRegionExpression(_currBounds, s2Field);

        ExpressionMapping::cover2dsphere(keyMatcher->getRegion(),
                                         _s2Index->infoObj(),
                                         coveredIntervals);

        // IndexScan owns the hash matcher
        IndexScan* scan = new IndexScanWithMatch(txn, scanParams, workingSet, keyMatcher);

        // FetchStage owns index scan
        FetchStage* fetcher(new FetchStage(workingSet, scan, _nearParams.filter, collection));

        return StatusWith<CoveredInterval*>(new CoveredInterval(fetcher,
                                                                true,
                                                                nextBounds.getInner(),
                                                                nextBounds.getOuter(),
                                                                isLastInterval));
    }
    StatusWith<NearStage::CoveredInterval*> //
    GeoNear2DStage::nextInterval(OperationContext* txn,
                                 WorkingSet* workingSet,
                                 Collection* collection) {

        // The search is finished if we searched at least once and all the way to the edge
        if (_currBounds.getInner() >= 0 && _currBounds.getOuter() == _fullBounds.getOuter()) {
            return StatusWith<CoveredInterval*>(NULL);
        }

        //
        // Setup the next interval
        //

        const NearStats* stats = getNearStats();

        if (!stats->intervalStats.empty()) {

            const IntervalStats& lastIntervalStats = stats->intervalStats.back();

            // TODO: Generally we want small numbers of results fast, then larger numbers later
            if (lastIntervalStats.numResultsBuffered < 300)
                _boundsIncrement *= 2;
            else if (lastIntervalStats.numResultsBuffered > 600)
                _boundsIncrement /= 2;
        }

        _boundsIncrement = max(_boundsIncrement,
                               min2DBoundsIncrement(_nearParams.nearQuery, _twoDIndex));

        R2Annulus nextBounds(_currBounds.center(),
                             _currBounds.getOuter(),
                             min(_currBounds.getOuter() + _boundsIncrement,
                                 _fullBounds.getOuter()));

        const bool isLastInterval = (nextBounds.getOuter() == _fullBounds.getOuter());
        _currBounds = nextBounds;

        //
        // Get a covering region for this interval
        //

        const CRS queryCRS = _nearParams.nearQuery.centroid.crs;

        auto_ptr<R2Region> coverRegion;

        if (FLAT == queryCRS) {

            // NOTE: Due to floating point math issues, FLAT searches of a 2D index need to treat
            // containment and distance separately.
            // Ex: (distance) 54.001 - 54 > 0.001, but (containment) 54 + 0.001 <= 54.001
            // The idea is that a $near search with bounds is really a $within search, sorted by
            // distance.  We attach a custom $within : annulus matcher to do the $within search,
            // and adjust max/min bounds slightly since, as above, containment does not mean the
            // distance calculation won't slightly overflow the boundary.
            //
            // The code below adjusts:
            // 1) Overall min/max bounds of the generated distance intervals to be more inclusive
            // 2) Bounds of the interval covering to be more inclusive
            // ... and later on we add the custom $within : annulus matcher.
            //
            // IMPORTANT: The *internal* interval distance bounds are *exact thresholds* - these
            // should not be adjusted.
            // TODO: Maybe integrate annuluses as a standard shape, and literally transform $near
            // internally into a $within query with $near just as sort.

            // Compute the maximum axis-aligned distance error
            const double epsilon = std::numeric_limits<double>::epsilon()
                                   * (max(abs(_fullBounds.center().x), abs(_fullBounds.center().y))
                                      + _fullBounds.getOuter());

            if (nextBounds.getInner() > 0 && nextBounds.getInner() == _fullBounds.getInner()) {
                nextBounds = R2Annulus(nextBounds.center(),
                                       max(0.0, nextBounds.getInner() - epsilon),
                                       nextBounds.getOuter());
            }

            if (nextBounds.getOuter() > 0 && nextBounds.getOuter() == _fullBounds.getOuter()) {
                // We're at the max bound of the search, adjust interval maximum
                nextBounds = R2Annulus(nextBounds.center(),
                                       nextBounds.getInner(),
                                       nextBounds.getOuter() + epsilon);
            }

            // *Always* adjust the covering bounds to be more inclusive
            coverRegion.reset(new R2Annulus(nextBounds.center(),
                                            max(0.0, nextBounds.getInner() - epsilon),
                                            nextBounds.getOuter() + epsilon));
        }
        else {
            invariant(SPHERE == queryCRS);
            // TODO: As above, make this consistent with $within : $centerSphere

            // Our intervals aren't in the same CRS as our index, so we need to adjust them
            coverRegion.reset(new R2Annulus(projectBoundsToTwoDDegrees(nextBounds)));
        }

        //
        // Setup the stages for this interval
        //

        IndexScanParams scanParams;
        scanParams.descriptor = _twoDIndex;
        scanParams.direction = 1;
        // We use a filter on the key.  The filter rejects keys that don't intersect with the
        // annulus.  An object that is in the annulus might have a key that's not in it and a key
        // that's in it.  As such we can't just look at one key per object.
        //
        // This does force us to do our own deduping of results, though.
        scanParams.doNotDedup = true;

        // Scan bounds on 2D indexes are only over the 2D field - other bounds aren't applicable.
        // This is handled in query planning.
        scanParams.bounds = _nearParams.baseBounds;

        // The "2d" field is always the first in the index
        const string twoDFieldName = _nearParams.nearQuery.field;
        const int twoDFieldPosition = 0;

        OrderedIntervalList coveredIntervals;
        coveredIntervals.name = scanParams.bounds.fields[twoDFieldPosition].name;

        ExpressionMapping::cover2d(*coverRegion,
                                   _twoDIndex->infoObj(),
                                   internalGeoNearQuery2DMaxCoveringCells,
                                   &coveredIntervals);

        // Intersect the $near bounds we just generated into the bounds we have for anything else
        // in the scan (i.e. $within)
        IndexBoundsBuilder::intersectize(coveredIntervals,
                                         &scanParams.bounds.fields[twoDFieldPosition]);
        
        // These parameters are stored by the index, and so must be ok
        GeoHashConverter::Parameters hashParams;
        GeoHashConverter::parseParameters(_twoDIndex->infoObj(), &hashParams);

        MatchExpression* keyMatcher =
            new TwoDKeyInRegionExpression(coverRegion.release(),
                                          hashParams,
                                          twoDFieldName);

        // 2D indexes support covered search over additional fields they contain
        // TODO: Don't need to clone, can just attach to custom matcher above
        if (_nearParams.filter) {
            AndMatchExpression* andMatcher = new AndMatchExpression();
            andMatcher->add(keyMatcher);
            andMatcher->add(_nearParams.filter->shallowClone());
            keyMatcher = andMatcher;
        }

        // IndexScanWithMatch owns the matcher
        IndexScan* scan = new IndexScanWithMatch(txn, scanParams, workingSet, keyMatcher);
        
        MatchExpression* docMatcher = NULL;
        
        // FLAT searches need to add an additional annulus $within matcher, see above
        if (FLAT == queryCRS) {
            docMatcher = new TwoDPtInAnnulusExpression(_fullBounds, twoDFieldName);
        }
        
        // FetchStage owns index scan
        FetchStage* fetcher(new FetchStageWithMatch(workingSet, 
                                                    scan, 
                                                    docMatcher, 
                                                    collection));

        return StatusWith<CoveredInterval*>(new CoveredInterval(fetcher,
                                                                true,
                                                                nextBounds.getInner(),
                                                                nextBounds.getOuter(),
                                                                isLastInterval));
    }