TEST(AndOp, ElemMatchKey) { BSONObj baseOperand1 = BSON("a" << 1); BSONObj baseOperand2 = BSON("b" << 2); unique_ptr<ComparisonMatchExpression> sub1(new EqualityMatchExpression()); ASSERT(sub1->init("a", baseOperand1["a"]).isOK()); unique_ptr<ComparisonMatchExpression> sub2(new EqualityMatchExpression()); ASSERT(sub2->init("b", baseOperand2["b"]).isOK()); AndMatchExpression andOp; andOp.add(sub1.release()); andOp.add(sub2.release()); MatchDetails details; details.requestElemMatchKey(); ASSERT(!andOp.matchesBSON(BSON("a" << BSON_ARRAY(1)), &details)); ASSERT(!details.hasElemMatchKey()); ASSERT(!andOp.matchesBSON(BSON("b" << BSON_ARRAY(2)), &details)); ASSERT(!details.hasElemMatchKey()); ASSERT(andOp.matchesBSON(BSON("a" << BSON_ARRAY(1) << "b" << BSON_ARRAY(1 << 2)), &details)); ASSERT(details.hasElemMatchKey()); // The elem match key for the second $and clause is recorded. ASSERT_EQUALS("1", details.elemMatchKey()); }
// static // XXX TODO: This does not belong here at all. MatchExpression* CanonicalQuery::logicalRewrite(MatchExpression* tree) { // Only thing we do is pull an OR up at the root. if (MatchExpression::AND != tree->matchType()) { return tree; } // We want to bail out ASAP if we have nothing to do here. size_t numOrs = 0; for (size_t i = 0; i < tree->numChildren(); ++i) { if (MatchExpression::OR == tree->getChild(i)->matchType()) { ++numOrs; } } // Only do this for one OR right now. if (1 != numOrs) { return tree; } // Detach the OR from the root. invariant(NULL != tree->getChildVector()); std::vector<MatchExpression*>& rootChildren = *tree->getChildVector(); MatchExpression* orChild = NULL; for (size_t i = 0; i < rootChildren.size(); ++i) { if (MatchExpression::OR == rootChildren[i]->matchType()) { orChild = rootChildren[i]; rootChildren.erase(rootChildren.begin() + i); break; } } // AND the existing root with each or child. invariant(NULL != orChild); invariant(NULL != orChild->getChildVector()); std::vector<MatchExpression*>& orChildren = *orChild->getChildVector(); for (size_t i = 0; i < orChildren.size(); ++i) { AndMatchExpression* ama = new AndMatchExpression(); ama->add(orChildren[i]); ama->add(tree->shallowClone()); orChildren[i] = ama; } delete tree; // Clean up any consequences from this tomfoolery. return normalizeTree(orChild); }
TEST(AndOp, MatchesThreeClauses) { BSONObj baseOperand1 = BSON("$gt" << 1); BSONObj baseOperand2 = BSON("$lt" << 10); BSONObj baseOperand3 = BSON("$lt" << 100); unique_ptr<ComparisonMatchExpression> sub1(new GTMatchExpression()); ASSERT(sub1->init("a", baseOperand1["$gt"]).isOK()); unique_ptr<ComparisonMatchExpression> sub2(new LTMatchExpression()); ASSERT(sub2->init("a", baseOperand2["$lt"]).isOK()); unique_ptr<ComparisonMatchExpression> sub3(new LTMatchExpression()); ASSERT(sub3->init("b", baseOperand3["$lt"]).isOK()); AndMatchExpression andOp; andOp.add(sub1.release()); andOp.add(sub2.release()); andOp.add(sub3.release()); ASSERT(andOp.matchesBSON(BSON("a" << 5 << "b" << 6), NULL)); ASSERT(!andOp.matchesBSON(BSON("a" << 5), NULL)); ASSERT(!andOp.matchesBSON(BSON("b" << 6), NULL)); ASSERT(!andOp.matchesBSON(BSON("a" << 1 << "b" << 6), NULL)); ASSERT(!andOp.matchesBSON(BSON("a" << 10 << "b" << 6), NULL)); }
TEST(AndOp, MatchesElementThreeClauses) { BSONObj baseOperand1 = BSON("$lt" << "z1"); BSONObj baseOperand2 = BSON("$gt" << "a1"); BSONObj match = BSON("a" << "r1"); BSONObj notMatch1 = BSON("a" << "z1"); BSONObj notMatch2 = BSON("a" << "a1"); BSONObj notMatch3 = BSON("a" << "r"); unique_ptr<ComparisonMatchExpression> sub1(new LTMatchExpression()); ASSERT(sub1->init("a", baseOperand1["$lt"]).isOK()); unique_ptr<ComparisonMatchExpression> sub2(new GTMatchExpression()); ASSERT(sub2->init("a", baseOperand2["$gt"]).isOK()); unique_ptr<RegexMatchExpression> sub3(new RegexMatchExpression()); ASSERT(sub3->init("a", "1", "").isOK()); AndMatchExpression andOp; andOp.add(sub1.release()); andOp.add(sub2.release()); andOp.add(sub3.release()); ASSERT(andOp.matchesBSON(match)); ASSERT(!andOp.matchesBSON(notMatch1)); ASSERT(!andOp.matchesBSON(notMatch2)); ASSERT(!andOp.matchesBSON(notMatch3)); }
StatusWithMatchExpression MatchExpressionParser::_parseElemMatch( const char* name, const BSONElement& e ) { if ( e.type() != Object ) return StatusWithMatchExpression( ErrorCodes::BadValue, "$elemMatch needs an Object" ); BSONObj obj = e.Obj(); if ( _isExpressionDocument( e ) ) { // value case AndMatchExpression theAnd; Status s = _parseSub( "", obj, &theAnd ); if ( !s.isOK() ) return StatusWithMatchExpression( s ); std::auto_ptr<ElemMatchValueMatchExpression> temp( new ElemMatchValueMatchExpression() ); s = temp->init( name ); if ( !s.isOK() ) return StatusWithMatchExpression( s ); for ( size_t i = 0; i < theAnd.numChildren(); i++ ) { temp->add( theAnd.getChild( i ) ); } theAnd.clearAndRelease(); return StatusWithMatchExpression( temp.release() ); } // object case StatusWithMatchExpression sub = _parse( obj, false ); if ( !sub.isOK() ) return sub; std::auto_ptr<ElemMatchObjectMatchExpression> temp( new ElemMatchObjectMatchExpression() ); Status status = temp->init( name, sub.getValue() ); if ( !status.isOK() ) return StatusWithMatchExpression( status ); return StatusWithMatchExpression( temp.release() ); }
TEST( AndOp, MatchesSingleClause ) { BSONObj baseOperand = BSON( "$ne" << 5 ); auto_ptr<ComparisonMatchExpression> ne( new ComparisonMatchExpression() ); ASSERT( ne->init( "a", ComparisonMatchExpression::NE, baseOperand[ "$ne" ] ).isOK() ); AndMatchExpression andOp; andOp.add( ne.release() ); ASSERT( andOp.matches( BSON( "a" << 4 ), NULL ) ); ASSERT( andOp.matches( BSON( "a" << BSON_ARRAY( 4 << 6 ) ), NULL ) ); ASSERT( !andOp.matches( BSON( "a" << 5 ), NULL ) ); ASSERT( !andOp.matches( BSON( "a" << BSON_ARRAY( 4 << 5 ) ), NULL ) ); }
TEST(AndOp, MatchesSingleClause) { BSONObj baseOperand = BSON("$ne" << 5); unique_ptr<ComparisonMatchExpression> eq(new EqualityMatchExpression()); ASSERT(eq->init("a", baseOperand["$ne"]).isOK()); unique_ptr<NotMatchExpression> ne(new NotMatchExpression()); ASSERT(ne->init(eq.release()).isOK()); AndMatchExpression andOp; andOp.add(ne.release()); ASSERT(andOp.matchesBSON(BSON("a" << 4), NULL)); ASSERT(andOp.matchesBSON(BSON("a" << BSON_ARRAY(4 << 6)), NULL)); ASSERT(!andOp.matchesBSON(BSON("a" << 5), NULL)); ASSERT(!andOp.matchesBSON(BSON("a" << BSON_ARRAY(4 << 5)), NULL)); }
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)); }
TEST(AndOp, NoClauses) { AndMatchExpression andMatchExpression; ASSERT(andMatchExpression.matchesBSON(BSONObj(), NULL)); }