Ejemplo n.º 1
0
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());
}
Ejemplo n.º 2
0
    // 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);
    }
Ejemplo n.º 3
0
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));
}
Ejemplo n.º 4
0
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));
}
Ejemplo n.º 5
0
    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() );
    }
Ejemplo n.º 6
0
    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 ) );
    }
Ejemplo n.º 7
0
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));
}
Ejemplo n.º 8
0
    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));
    }
Ejemplo n.º 9
0
TEST(AndOp, NoClauses) {
    AndMatchExpression andMatchExpression;
    ASSERT(andMatchExpression.matchesBSON(BSONObj(), NULL));
}