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)); }