summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
authorBen Becker <ben.becker@10gen.com>2012-06-14 22:43:20 -0700
committerBen Becker <ben.becker@10gen.com>2012-06-14 22:43:20 -0700
commitfb66c84bc7bc1ece63a65766bfea2f797f3b7121 (patch)
tree10fa3f0e5dde4c9e11a0dd1c84ac729010a33284 /src/mongo
parentb62ea5f5eb0107fe3f50275dda6ee5ffd05be1bb (diff)
downloadmongo-fb66c84bc7bc1ece63a65766bfea2f797f3b7121.tar.gz
SERVER-828: implement positional operator ($) and for query projections
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/db/clientcursor.cpp8
-rw-r--r--src/mongo/db/clientcursor.h6
-rwxr-xr-xsrc/mongo/db/matcher.cpp2
-rw-r--r--src/mongo/db/ops/query.cpp43
-rw-r--r--src/mongo/db/ops/query.h11
-rw-r--r--src/mongo/db/projection.cpp183
-rw-r--r--src/mongo/db/projection.h42
-rw-r--r--src/mongo/db/scanandorder.cpp18
-rw-r--r--src/mongo/db/scanandorder.h5
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: