diff options
author | Ben Becker <ben.becker@10gen.com> | 2012-06-14 22:43:20 -0700 |
---|---|---|
committer | Ben Becker <ben.becker@10gen.com> | 2012-06-14 22:43:20 -0700 |
commit | fb66c84bc7bc1ece63a65766bfea2f797f3b7121 (patch) | |
tree | 10fa3f0e5dde4c9e11a0dd1c84ac729010a33284 /src/mongo | |
parent | b62ea5f5eb0107fe3f50275dda6ee5ffd05be1bb (diff) | |
download | mongo-fb66c84bc7bc1ece63a65766bfea2f797f3b7121.tar.gz |
SERVER-828: implement positional operator ($) and for query projections
Diffstat (limited to 'src/mongo')
-rw-r--r-- | src/mongo/db/clientcursor.cpp | 8 | ||||
-rw-r--r-- | src/mongo/db/clientcursor.h | 6 | ||||
-rwxr-xr-x | src/mongo/db/matcher.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/ops/query.cpp | 43 | ||||
-rw-r--r-- | src/mongo/db/ops/query.h | 11 | ||||
-rw-r--r-- | src/mongo/db/projection.cpp | 183 | ||||
-rw-r--r-- | src/mongo/db/projection.h | 42 | ||||
-rw-r--r-- | src/mongo/db/scanandorder.cpp | 18 | ||||
-rw-r--r-- | src/mongo/db/scanandorder.h | 5 |
9 files changed, 264 insertions, 54 deletions
diff --git a/src/mongo/db/clientcursor.cpp b/src/mongo/db/clientcursor.cpp index 2b94362cb8c..3f1fc3143d3 100644 --- a/src/mongo/db/clientcursor.cpp +++ b/src/mongo/db/clientcursor.cpp @@ -415,21 +415,21 @@ namespace mongo { } if ( fillWithNull ) - b.appendNull( key.fieldName() ); + b.appendNull( key.fieldName() ); } return b.obj(); } - void ClientCursor::fillQueryResultFromObj( BufBuilder &b ) const { + void ClientCursor::fillQueryResultFromObj( BufBuilder &b, const MatchDetails* details ) const { const Projection::KeyOnly *keyFieldsOnly = c()->keyFieldsOnly(); if ( keyFieldsOnly ) { - mongo::fillQueryResultFromObj( b, 0, keyFieldsOnly->hydrate( c()->currKey() ) ); + mongo::fillQueryResultFromObj( b, 0, keyFieldsOnly->hydrate( c()->currKey() ), details ); } else { DiskLoc loc = c()->currLoc(); - mongo::fillQueryResultFromObj( b, fields.get(), c()->current(), + mongo::fillQueryResultFromObj( b, fields.get(), c()->current(), details, ( ( pq && pq->showDiskLoc() ) ? &loc : 0 ) ); } } diff --git a/src/mongo/db/clientcursor.h b/src/mongo/db/clientcursor.h index 328a6522f89..595a9d775a4 100644 --- a/src/mongo/db/clientcursor.h +++ b/src/mongo/db/clientcursor.h @@ -286,9 +286,9 @@ namespace mongo { * NOTE: copied from BSONObj::extractFields */ BSONObj extractFields(const BSONObj &pattern , bool fillWithNull = false) ; - - void fillQueryResultFromObj( BufBuilder &b ) const; - + + void fillQueryResultFromObj( BufBuilder &b, const MatchDetails* details = NULL ) const; + bool currentIsDup() { return _c->getsetdup( _c->currLoc() ); } bool currentMatches() { diff --git a/src/mongo/db/matcher.cpp b/src/mongo/db/matcher.cpp index 81467ccc023..3957d1d71ef 100755 --- a/src/mongo/db/matcher.cpp +++ b/src/mongo/db/matcher.cpp @@ -808,7 +808,7 @@ namespace mongo { valuesMatch(z, toMatch, compareOp, em) ) { // "field.<n>" array notation was used if ( details ) - details->setElemMatchKey( z.fieldName() ); + details->setElemMatchKey( z.fieldName() ); return 1; } } diff --git a/src/mongo/db/ops/query.cpp b/src/mongo/db/ops/query.cpp index 34d0359c957..5c16c88e1af 100644 --- a/src/mongo/db/ops/query.cpp +++ b/src/mongo/db/ops/query.cpp @@ -141,8 +141,14 @@ namespace mongo { break; } + MatchDetails details; + if ( cc->fields && cc->fields->getArrayOpType() == Projection::ARRAY_OP_POSITIONAL ) { + // field projection specified, and contains an array operator + details.requestElemMatchKey(); + } + // in some cases (clone collection) there won't be a matcher - if ( !c->currentMatches() ) { + if ( !c->currentMatches( &details ) ) { } else if ( manager && ! manager->belongsToMe( cc ) ){ LOG(2) << "cursor skipping document in un-owned chunk: " << c->current() << endl; @@ -155,7 +161,7 @@ namespace mongo { last = c->currLoc(); n++; - cc->fillQueryResultFromObj( b ); + cc->fillQueryResultFromObj( b, &details ); if ( ( ntoreturn && n >= ntoreturn ) || b.len() > MaxBytesToReturnToClientAtOnce ) { c->advance(); @@ -322,7 +328,7 @@ namespace mongo { _bufferedMatches() { } - bool OrderedBuildStrategy::handleMatch( bool &orderedMatch ) { + bool OrderedBuildStrategy::handleMatch( bool &orderedMatch, MatchDetails& details ) { DiskLoc loc = _cursor->currLoc(); if ( _cursor->getsetdup( loc ) ) { return orderedMatch = false; @@ -333,7 +339,8 @@ namespace mongo { } // Explain does not obey soft limits, so matches should not be buffered. if ( !_parsedQuery.isExplain() ) { - fillQueryResultFromObj( _buf, _parsedQuery.getFields(), current( true ), + fillQueryResultFromObj( _buf, _parsedQuery.getFields(), + current( true ), &details, ( _parsedQuery.showDiskLoc() ? &loc : 0 ) ); ++_bufferedMatches; } @@ -360,7 +367,7 @@ namespace mongo { _scanAndOrder.reset( newScanAndOrder( queryPlan ) ); } - bool ReorderBuildStrategy::handleMatch( bool &orderedMatch ) { + bool ReorderBuildStrategy::handleMatch( bool &orderedMatch, MatchDetails& details ) { orderedMatch = false; if ( _cursor->getsetdup( _cursor->currLoc() ) ) { return false; @@ -377,7 +384,7 @@ namespace mongo { int ReorderBuildStrategy::rewriteMatches() { cc().curop()->debug().scanAndOrder = true; int ret = 0; - _scanAndOrder->fill( _buf, _parsedQuery.getFields(), ret ); + _scanAndOrder->fill( _buf, &_parsedQuery, ret ); _bufferedMatches = ret; return ret; } @@ -422,9 +429,9 @@ namespace mongo { QueryPlanSummary() ) ); } - bool HybridBuildStrategy::handleMatch( bool &orderedMatch ) { + bool HybridBuildStrategy::handleMatch( bool &orderedMatch, MatchDetails& details ) { if ( !_queryOptimizerCursor->currentPlanScanAndOrderRequired() ) { - return _orderedBuild.handleMatch( orderedMatch ); + return _orderedBuild.handleMatch( orderedMatch, details ); } orderedMatch = false; return handleReorderMatch(); @@ -497,14 +504,21 @@ namespace mongo { } bool QueryResponseBuilder::addMatch() { - if ( !currentMatches() ) { + MatchDetails details; + + if ( _parsedQuery.getFields() && _parsedQuery.getFields()->getArrayOpType() == Projection::ARRAY_OP_POSITIONAL ) { + // field projection specified, and contains an array operator + details.requestElemMatchKey(); + } + + if ( !currentMatches( details ) ) { return false; } if ( !chunkMatches() ) { return false; } bool orderedMatch = false; - bool match = _builder->handleMatch( orderedMatch ); + bool match = _builder->handleMatch( orderedMatch, details ); _explain->noteIterate( match, orderedMatch, true, false ); return match; } @@ -601,8 +615,7 @@ namespace mongo { ( HybridBuildStrategy::make( _parsedQuery, _queryOptimizerCursor, _buf ) ); } - bool QueryResponseBuilder::currentMatches() { - MatchDetails details; + bool QueryResponseBuilder::currentMatches( MatchDetails& details ) { if ( _cursor->currentMatches( &details ) ) { return true; } @@ -948,7 +961,11 @@ namespace mongo { return ""; } } - + + // sanity check the query and projection + if ( pq.getFields() != NULL ) + pq.getFields()->validateQuery( query ); + // these now may stored in a ClientCursor or somewhere else, // so make sure we use a real copy jsobj = jsobj.getOwned(); diff --git a/src/mongo/db/ops/query.h b/src/mongo/db/ops/query.h index 79da8da8310..431c7dfba2b 100644 --- a/src/mongo/db/ops/query.h +++ b/src/mongo/db/ops/query.h @@ -138,7 +138,8 @@ namespace mongo { * @return true if a match is found. * @param orderedMatch set if it is an ordered match. */ - virtual bool handleMatch( bool &orderedMatch ) = 0; + virtual bool handleMatch( bool& orderedMatch, MatchDetails& details ) = 0; + /** * Write all matches into the buffer, overwriting existing data. * @return number of matches written, or -1 if no op. @@ -170,7 +171,7 @@ namespace mongo { public: OrderedBuildStrategy( const ParsedQuery &parsedQuery, const shared_ptr<Cursor> &cursor, BufBuilder &buf ); - virtual bool handleMatch( bool &orderedMatch ); + virtual bool handleMatch( bool& orderedMatch, MatchDetails& details ); virtual int bufferedMatches() const { return _bufferedMatches; } private: int _skip; @@ -186,7 +187,7 @@ namespace mongo { const shared_ptr<Cursor>& cursor, BufBuilder& buf, const QueryPlanSummary& queryPlan ); - virtual bool handleMatch( bool &orderedMatch ); + virtual bool handleMatch( bool &orderedMatch, MatchDetails& details ); /** Handle a match without performing deduping. */ void _handleMatchNoDedup(); virtual int rewriteMatches(); @@ -227,7 +228,7 @@ namespace mongo { const shared_ptr<QueryOptimizerCursor> &cursor, BufBuilder &buf ); void init(); - virtual bool handleMatch( bool &orderedMatch ); + virtual bool handleMatch( bool &orderedMatch, MatchDetails &details ); virtual int rewriteMatches(); virtual int bufferedMatches() const; virtual void finishedFirstBatch(); @@ -282,7 +283,7 @@ namespace mongo { ( const QueryPlanSummary &queryPlan, const BSONObj &oldPlan ) const; shared_ptr<ResponseBuildStrategy> newResponseBuildStrategy ( const QueryPlanSummary &queryPlan ); - bool currentMatches(); + bool currentMatches( MatchDetails& details ); bool chunkMatches(); const ParsedQuery &_parsedQuery; shared_ptr<Cursor> _cursor; diff --git a/src/mongo/db/projection.cpp b/src/mongo/db/projection.cpp index a2ec4af325d..ed30f2c6bd3 100644 --- a/src/mongo/db/projection.cpp +++ b/src/mongo/db/projection.cpp @@ -17,7 +17,8 @@ #include "pch.h" #include "projection.h" -#include "../util/mongoutils/str.h" +#include "mongo/db/matcher.h" +#include "mongo/util/mongoutils/str.h" namespace mongo { @@ -36,7 +37,7 @@ namespace mongo { if (e.type() == Object) { BSONObj obj = e.embeddedObject(); BSONElement e2 = obj.firstElement(); - if ( strcmp(e2.fieldName(), "$slice") == 0 ) { + if ( mongoutils::str::equals( e2.fieldName(), "$slice" ) ) { if (e2.isNumber()) { int i = e2.numberInt(); if (i < 0) @@ -60,18 +61,35 @@ namespace mongo { uassert(13098, "$slice only supports numbers and [skip, limit] arrays", false); } } + else if ( mongoutils::str::equals( e2.fieldName(), "$elemMatch" ) ) { + // validate $elemMatch arguments and dependancies + uassert( 16342, "elemMatch: invalid argument. object required.", + e2.type() == Object ); + uassert( 16343, "Cannot specify positional operator and $elemMatch" + " (currently unsupported).", + _arrayOpType != ARRAY_OP_POSITIONAL ); + uassert( 16344, "Cannot use $elemMatch projection on a nested field" + " (currently unsupported).", + ! mongoutils::str::contains( e.fieldName(), '.' ) ); + _arrayOpType = ARRAY_OP_ELEM_MATCH; + + // initialize new Matcher object(s) + + _matchers.insert( make_pair( mongoutils::str::before( e.fieldName(), '.' ), + new Matcher( e.wrap(), true ) ) ); + add( e.fieldName(), true ); + } else { - uassert(13097, string("Unsupported projection option: ") + obj.firstElementFieldName(), false); + uasserted(13097, string("Unsupported projection option: ") + + obj.firstElementFieldName() ); } } else if (!strcmp(e.fieldName(), "_id") && !e.trueValue()) { _includeID = false; - } else { - - add (e.fieldName(), e.trueValue()); + add( e.fieldName(), e.trueValue() ); // validate input if (true_false == -1) { @@ -79,10 +97,22 @@ namespace mongo { _include = !e.trueValue(); } else { - uassert( 10053 , "You cannot currently mix including and excluding fields. Contact us if this is an issue." , + uassert( 10053 , "You cannot currently mix including and excluding fields. " + "Contact us if this is an issue." , (bool)true_false == e.trueValue() ); } } + if ( mongoutils::str::contains( e.fieldName(), ".$" ) ) { + // positional op found; add parent fields + uassert( 16345, "Cannot exclude array elements with the positional operator" + " (currently unsupported).", e.trueValue() ); + uassert( 16346, "Cannot specify more than one positional array element per query" + " (currently unsupported).", _arrayOpType != ARRAY_OP_POSITIONAL ); + uassert( 16347, "Cannot specify positional operator and $elemMatch" + " (currently unsupported).", _arrayOpType != ARRAY_OP_ELEM_MATCH ); + _arrayOpType = ARRAY_OP_POSITIONAL; + add( mongoutils::str::before( e.fieldName(), ".$"), e.trueValue() ); + } } } @@ -125,7 +155,9 @@ namespace mongo { } } - void Projection::transform( const BSONObj& in , BSONObjBuilder& b ) const { + void Projection::transform( const BSONObj& in , BSONObjBuilder& b, const MatchDetails* details ) const { + const ArrayOpType& arrayOpType = getArrayOpType(); + BSONObjIterator i(in); while ( i.more() ) { BSONElement e = i.next(); @@ -134,14 +166,44 @@ namespace mongo { b.append( e ); } else { - append( b , e ); + Matchers::const_iterator matcher = _matchers.find( e.fieldName() ); + if ( matcher == _matchers.end() ) { + // no array projection matchers for this field + append( b, e, details, arrayOpType ); + } else { + // field has array projection with $elemMatch specified. + massert( 16348, "matchers are only supported for $elemMatch", + arrayOpType == ARRAY_OP_ELEM_MATCH ); + MatchDetails arrayDetails; + arrayDetails.requestElemMatchKey(); + if ( matcher->second->matches( in, &arrayDetails ) ) { + log(4) << "Matched array on field: " << matcher->first << endl + << " from array: " << in.getField( matcher->first ) << endl + << " in object: " << in << endl + << " at position: " << arrayDetails.elemMatchKey() << endl; + FieldMap::const_iterator field = _fields.find( e.fieldName() ); + massert( 16349, "$elemMatch specified, but projection field not found.", + field != _fields.end() ); + BSONArrayBuilder a; + BSONObjBuilder o; + massert( 16350, "$elemMatch called on document element with eoo", + ! in.getField( e.fieldName() ).eoo() ); + massert( 16351, "$elemMatch called on array element with eoo", + ! in.getField( e.fieldName() ).Obj().getField( + arrayDetails.elemMatchKey() ).eoo() ); + a.append( in.getField( e.fieldName() ).Obj() + .getField( arrayDetails.elemMatchKey() ) ); + o.appendArray( matcher->first, a.arr() ); + append( b, o.done().firstElement(), details, arrayOpType ); + } + } } } } - BSONObj Projection::transform( const BSONObj& in ) const { + BSONObj Projection::transform( const BSONObj& in, const MatchDetails* details ) const { BSONObjBuilder b; - transform( in , b ); + transform( in , b, details ); return b.obj(); } @@ -192,17 +254,19 @@ namespace mongo { } } - void Projection::append( BSONObjBuilder& b , const BSONElement& e ) const { - FieldMap::const_iterator field = _fields.find( e.fieldName() ); + void Projection::append( BSONObjBuilder& b , const BSONElement& e, const MatchDetails* details, + const ArrayOpType arrayOpType ) const { + FieldMap::const_iterator field = _fields.find( e.fieldName() ); if (field == _fields.end()) { if (_include) b.append(e); } else { Projection& subfm = *field->second; - - if ((subfm._fields.empty() && !subfm._special) || !(e.type()==Object || e.type()==Array) ) { + if ( ( subfm._fields.empty() && !subfm._special ) || + !(e.type()==Object || e.type()==Array) ) { + // field map empty, or element is not an array/object if (subfm._include) b.append(e); } @@ -210,19 +274,98 @@ namespace mongo { BSONObjBuilder subb; BSONObjIterator it(e.embeddedObject()); while (it.more()) { - subfm.append(subb, it.next()); + subfm.append(subb, it.next(), details, arrayOpType); } b.append(e.fieldName(), subb.obj()); - } else { //Array - BSONObjBuilder subb; - subfm.appendArray(subb, e.embeddedObject()); - b.appendArray(e.fieldName(), subb.obj()); + BSONObjBuilder matchedBuilder; + if ( details && arrayOpType == ARRAY_OP_POSITIONAL ) { + // $ positional operator specified + + log(4) << "projection: checking if element " << e << " matched spec: " + << getSpec() << " match details: " << *details << endl; + uassert( 16352, mongoutils::str::stream() << "positional operator (" + << e.fieldName() + << ".$) requires corresponding field in query specifier", + details && details->hasElemMatchKey() ); + + uassert( 16353, "positional operator element mismatch", + ! e.embeddedObject()[details->elemMatchKey()].eoo() ); + + // append as the first and only element in the projected array + matchedBuilder.appendAs( e.embeddedObject()[details->elemMatchKey()], "0" ); + } + else { + // append exact array; no subarray matcher specified + subfm.appendArray( matchedBuilder, e.embeddedObject() ); + } + b.appendArray( e.fieldName(), matchedBuilder.obj() ); } } } + Projection::ArrayOpType Projection::getArrayOpType( ) const { + return _arrayOpType; + } + + Projection::ArrayOpType Projection::getArrayOpType( const BSONObj spec ) { + BSONObjIterator iq( spec ); + while ( iq.more() ) { + // iterate through each element + const BSONElement& elem = iq.next(); + const char* const& fieldName = elem.fieldName(); + if ( mongoutils::str::contains( fieldName, ".$" ) ) { + // projection contains positional or $elemMatch operator + return ARRAY_OP_POSITIONAL; + } + if ( mongoutils::str::contains( fieldName, "$elemMatch" ) ) { + // projection contains positional or $elemMatch operator + return ARRAY_OP_ELEM_MATCH; + } + + // check nested elements + if ( elem.type() == Object ) + return getArrayOpType( elem.embeddedObject() ); + } + return ARRAY_OP_NORMAL; + } + + void Projection::validateQuery( const BSONObj query ) const { + // this function only validates positional operator ($) projections + if ( getArrayOpType() != ARRAY_OP_POSITIONAL ) + return; + + BSONObjIterator querySpecIter( query ); + while ( querySpecIter.more() ) { + // for each query element + + BSONElement queryElement = querySpecIter.next(); + if ( mongoutils::str::equals( queryElement.fieldName(), "$and" ) ) + // don't check $and to avoid deep comparison of the arguments. + // TODO: can be replaced with Matcher::FieldSink when complete (SERVER-4644) + return; + + BSONObjIterator projectionSpecIter( getSpec() ); + while ( projectionSpecIter.more() ) { + // for each projection element + + BSONElement projectionElement = projectionSpecIter.next(); + if ( mongoutils::str::contains( projectionElement.fieldName(), ".$" ) && + mongoutils::str::before( queryElement.fieldName(), '.' ) == + mongoutils::str::before( projectionElement.fieldName(), "." ) ) { + + // found query spec that matches positional array projection spec + log(4) << "Query specifies field named for positional operator: " + << queryElement.fieldName() << endl; + return; + } + } + } + + uasserted( 16354, "Positional operator does not match the query specifier." ); + } + Projection::KeyOnly* Projection::checkKey( const BSONObj& keyPattern ) const { if ( _include ) { // if we default to including then we can't diff --git a/src/mongo/db/projection.h b/src/mongo/db/projection.h index b5e0a0c4289..32932dde72a 100644 --- a/src/mongo/db/projection.h +++ b/src/mongo/db/projection.h @@ -22,6 +22,10 @@ namespace mongo { + // fwd decls + class Matcher; + class MatchDetails; + /** * given a document and a projection specification * can transform the document @@ -54,12 +58,19 @@ namespace mongo { int _stringSize; }; + enum ArrayOpType { + ARRAY_OP_NORMAL = 0, + ARRAY_OP_ELEM_MATCH, + ARRAY_OP_POSITIONAL + }; + Projection() : _include(true) , _special(false) , _includeID(true) , _skip(0) , _limit(-1) , + _arrayOpType(ARRAY_OP_NORMAL), _hasNonSimple(false) { } @@ -77,13 +88,13 @@ namespace mongo { /** * transforms in according to spec */ - BSONObj transform( const BSONObj& in ) const; + BSONObj transform( const BSONObj& in, const MatchDetails* details = NULL ) const; /** * transforms in according to spec */ - void transform( const BSONObj& in , BSONObjBuilder& b ) const; + void transform( const BSONObj& in , BSONObjBuilder& b, const MatchDetails* details = NULL ) const; /** @@ -96,14 +107,32 @@ namespace mongo { bool includeID() const { return _includeID; } + /** + * get the type of array operator in the projection + * @param spec Optional specifier to check (uses _source if unspecified) + * @return ARRAY_OP_NORMAL if no array projection modifier, + * ARRAY_OP_ELEM_MATCH if $elemMatch specifier, + * ARRAY_OP_POSITIONAL if '.$' projection specified + */ + static ArrayOpType getArrayOpType( const BSONObj spec ); + ArrayOpType getArrayOpType() const; + + /** + * Validate the given query satisfies this projection's positional operator. + * NOTE: this function is only used to validate projections with a positional operator. + * @param query User-supplied query specifier + * @return Field name if found, empty string otherwise. + */ + void validateQuery( const BSONObj query ) const; + private: /** * appends e to b if user wants it * will descend into e if needed */ - void append( BSONObjBuilder& b , const BSONElement& e ) const; - + void append( BSONObjBuilder& b , const BSONElement& e, const MatchDetails* details = NULL, + const ArrayOpType arrayOpType = ARRAY_OP_NORMAL ) const; void add( const string& field, bool include ); void add( const string& field, int skip, int limit ); @@ -122,6 +151,11 @@ namespace mongo { int _skip; int _limit; + // used for $elemMatch and positional operator ($) + typedef map<string, shared_ptr<Matcher> > Matchers; + Matchers _matchers; + ArrayOpType _arrayOpType; + bool _hasNonSimple; }; diff --git a/src/mongo/db/scanandorder.cpp b/src/mongo/db/scanandorder.cpp index b93b632f035..4de80d12156 100644 --- a/src/mongo/db/scanandorder.cpp +++ b/src/mongo/db/scanandorder.cpp @@ -20,6 +20,8 @@ #include "pch.h" #include "scanandorder.h" +#include "mongo/db/matcher.h" +#include "mongo/util/mongoutils/str.h" namespace mongo { @@ -55,15 +57,27 @@ namespace mongo { } - void ScanAndOrder::fill(BufBuilder& b, const Projection *filter, int& nout ) const { + void ScanAndOrder::fill( BufBuilder& b, const ParsedQuery *parsedQuery, int& nout ) const { int n = 0; int nFilled = 0; + Projection *projection = parsedQuery ? parsedQuery->getFields() : NULL; + scoped_ptr<Matcher> arrayMatcher; + scoped_ptr<MatchDetails> details; + if ( projection && projection->getArrayOpType() == Projection::ARRAY_OP_POSITIONAL ) { + // the projection specified an array positional match operator; create a new matcher + // for the projected array + arrayMatcher.reset( new Matcher( parsedQuery->getFilter() ) ); + details.reset( new MatchDetails ); + details->requestElemMatchKey(); + } for ( BestMap::const_iterator i = _best.begin(); i != _best.end(); i++ ) { n++; if ( n <= _startFrom ) continue; const BSONObj& o = i->second; - fillQueryResultFromObj(b, filter, o); + massert( 16355, "positional operator specified, but no array match", + ! arrayMatcher || arrayMatcher->matches( o, details.get() ) ); + fillQueryResultFromObj( b, projection, o, details.get() ); nFilled++; if ( nFilled >= _limit ) break; diff --git a/src/mongo/db/scanandorder.h b/src/mongo/db/scanandorder.h index acb19d21ad0..f104cfe51fc 100644 --- a/src/mongo/db/scanandorder.h +++ b/src/mongo/db/scanandorder.h @@ -53,10 +53,11 @@ namespace mongo { */ inline void fillQueryResultFromObj(BufBuilder& bb, const Projection *filter, const BSONObj& js, + const MatchDetails* details = NULL, const DiskLoc* loc=NULL) { if ( filter ) { BSONObjBuilder b( bb ); - filter->transform( js , b ); + filter->transform( js , b, details ); if (loc) b.append("$diskLoc", loc->toBSONObj()); b.done(); @@ -93,7 +94,7 @@ namespace mongo { void add(const BSONObj &o, const DiskLoc* loc); /* scanning complete. stick the query result in b for n objects. */ - void fill(BufBuilder& b, const Projection *filter, int& nout ) const; + void fill(BufBuilder& b, const ParsedQuery *query, int& nout) const; /** Functions for testing. */ protected: |