diff options
Diffstat (limited to 'src/mongo/db/matcher/expression_parser.cpp')
-rw-r--r-- | src/mongo/db/matcher/expression_parser.cpp | 1149 |
1 files changed, 559 insertions, 590 deletions
diff --git a/src/mongo/db/matcher/expression_parser.cpp b/src/mongo/db/matcher/expression_parser.cpp index b2977e34117..4146051bfb5 100644 --- a/src/mongo/db/matcher/expression_parser.cpp +++ b/src/mongo/db/matcher/expression_parser.cpp @@ -41,774 +41,743 @@ namespace { - using namespace mongo; +using namespace mongo; - /** - * Returns true if subtree contains MatchExpression 'type'. - */ - bool hasNode(const MatchExpression* root, MatchExpression::MatchType type) { - if (type == root->matchType()) { +/** + * Returns true if subtree contains MatchExpression 'type'. + */ +bool hasNode(const MatchExpression* root, MatchExpression::MatchType type) { + if (type == root->matchType()) { + return true; + } + for (size_t i = 0; i < root->numChildren(); ++i) { + if (hasNode(root->getChild(i), type)) { return true; } - for (size_t i = 0; i < root->numChildren(); ++i) { - if (hasNode(root->getChild(i), type)) { - return true; - } - } - return false; } + return false; +} -} // namespace +} // namespace namespace mongo { - using std::string; +using std::string; - StatusWithMatchExpression MatchExpressionParser::_parseComparison( const char* name, - ComparisonMatchExpression* cmp, - const BSONElement& e ) { - std::unique_ptr<ComparisonMatchExpression> temp(cmp); +StatusWithMatchExpression MatchExpressionParser::_parseComparison(const char* name, + ComparisonMatchExpression* cmp, + const BSONElement& e) { + std::unique_ptr<ComparisonMatchExpression> temp(cmp); - // Non-equality comparison match expressions cannot have - // a regular expression as the argument (e.g. {a: {$gt: /b/}} is illegal). - if (MatchExpression::EQ != cmp->matchType() && RegEx == e.type()) { - std::stringstream ss; - ss << "Can't have RegEx as arg to predicate over field '" << name << "'."; - return StatusWithMatchExpression(Status(ErrorCodes::BadValue, ss.str())); - } - - Status s = temp->init( name, e ); - if ( !s.isOK() ) - return StatusWithMatchExpression(s); - - return StatusWithMatchExpression( temp.release() ); + // Non-equality comparison match expressions cannot have + // a regular expression as the argument (e.g. {a: {$gt: /b/}} is illegal). + if (MatchExpression::EQ != cmp->matchType() && RegEx == e.type()) { + std::stringstream ss; + ss << "Can't have RegEx as arg to predicate over field '" << name << "'."; + return StatusWithMatchExpression(Status(ErrorCodes::BadValue, ss.str())); } - StatusWithMatchExpression MatchExpressionParser::_parseSubField( const BSONObj& context, - const AndMatchExpression* andSoFar, - const char* name, - const BSONElement& e, - int level ) { + Status s = temp->init(name, e); + if (!s.isOK()) + return StatusWithMatchExpression(s); + + return StatusWithMatchExpression(temp.release()); +} - // TODO: these should move to getGtLtOp, or its replacement +StatusWithMatchExpression MatchExpressionParser::_parseSubField(const BSONObj& context, + const AndMatchExpression* andSoFar, + const char* name, + const BSONElement& e, + int level) { + // TODO: these should move to getGtLtOp, or its replacement - if ( mongoutils::str::equals( "$eq", e.fieldName() ) ) - return _parseComparison( name, new EqualityMatchExpression(), e ); + if (mongoutils::str::equals("$eq", e.fieldName())) + return _parseComparison(name, new EqualityMatchExpression(), e); - if ( mongoutils::str::equals( "$not", e.fieldName() ) ) { - return _parseNot( name, e, level ); - } + if (mongoutils::str::equals("$not", e.fieldName())) { + return _parseNot(name, e, level); + } - int x = e.getGtLtOp(-1); - switch ( x ) { + int x = e.getGtLtOp(-1); + switch (x) { case -1: // $where cannot be a sub-expression because it works on top-level documents only. - if ( mongoutils::str::equals( "$where", e.fieldName() ) ) { - return StatusWithMatchExpression( ErrorCodes::BadValue, - "$where cannot be applied to a field" ); + if (mongoutils::str::equals("$where", e.fieldName())) { + return StatusWithMatchExpression(ErrorCodes::BadValue, + "$where cannot be applied to a field"); } - return StatusWithMatchExpression( ErrorCodes::BadValue, - mongoutils::str::stream() << "unknown operator: " - << e.fieldName() ); + return StatusWithMatchExpression(ErrorCodes::BadValue, + mongoutils::str::stream() + << "unknown operator: " << e.fieldName()); case BSONObj::LT: - return _parseComparison( name, new LTMatchExpression(), e ); + return _parseComparison(name, new LTMatchExpression(), e); case BSONObj::LTE: - return _parseComparison( name, new LTEMatchExpression(), e ); + return _parseComparison(name, new LTEMatchExpression(), e); case BSONObj::GT: - return _parseComparison( name, new GTMatchExpression(), e ); + return _parseComparison(name, new GTMatchExpression(), e); case BSONObj::GTE: - return _parseComparison( name, new GTEMatchExpression(), e ); + return _parseComparison(name, new GTEMatchExpression(), e); case BSONObj::NE: { if (RegEx == e.type()) { // Just because $ne can be rewritten as the negation of an // equality does not mean that $ne of a regex is allowed. See SERVER-1705. - return StatusWithMatchExpression(Status(ErrorCodes::BadValue, - "Can't have regex as arg to $ne.")); + return StatusWithMatchExpression( + Status(ErrorCodes::BadValue, "Can't have regex as arg to $ne.")); } - StatusWithMatchExpression s = _parseComparison( name, new EqualityMatchExpression(), e ); - if ( !s.isOK() ) + StatusWithMatchExpression s = _parseComparison(name, new EqualityMatchExpression(), e); + if (!s.isOK()) return s; - std::unique_ptr<NotMatchExpression> n( new NotMatchExpression() ); - Status s2 = n->init( s.getValue() ); - if ( !s2.isOK() ) - return StatusWithMatchExpression( s2 ); - return StatusWithMatchExpression( n.release() ); + std::unique_ptr<NotMatchExpression> n(new NotMatchExpression()); + Status s2 = n->init(s.getValue()); + if (!s2.isOK()) + return StatusWithMatchExpression(s2); + return StatusWithMatchExpression(n.release()); } case BSONObj::Equality: - return _parseComparison( name, new EqualityMatchExpression(), e ); + return _parseComparison(name, new EqualityMatchExpression(), e); case BSONObj::opIN: { - if ( e.type() != Array ) - return StatusWithMatchExpression( ErrorCodes::BadValue, "$in needs an array" ); - std::unique_ptr<InMatchExpression> temp( new InMatchExpression() ); - Status s = temp->init( name ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - s = _parseArrayFilterEntries( temp->getArrayFilterEntries(), e.Obj() ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - return StatusWithMatchExpression( temp.release() ); + if (e.type() != Array) + return StatusWithMatchExpression(ErrorCodes::BadValue, "$in needs an array"); + std::unique_ptr<InMatchExpression> temp(new InMatchExpression()); + Status s = temp->init(name); + if (!s.isOK()) + return StatusWithMatchExpression(s); + s = _parseArrayFilterEntries(temp->getArrayFilterEntries(), e.Obj()); + if (!s.isOK()) + return StatusWithMatchExpression(s); + return StatusWithMatchExpression(temp.release()); } case BSONObj::NIN: { - if ( e.type() != Array ) - return StatusWithMatchExpression( ErrorCodes::BadValue, "$nin needs an array" ); - std::unique_ptr<InMatchExpression> temp( new InMatchExpression() ); - Status s = temp->init( name ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - s = _parseArrayFilterEntries( temp->getArrayFilterEntries(), e.Obj() ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - - std::unique_ptr<NotMatchExpression> temp2( new NotMatchExpression() ); - s = temp2->init( temp.release() ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - - return StatusWithMatchExpression( temp2.release() ); + if (e.type() != Array) + return StatusWithMatchExpression(ErrorCodes::BadValue, "$nin needs an array"); + std::unique_ptr<InMatchExpression> temp(new InMatchExpression()); + Status s = temp->init(name); + if (!s.isOK()) + return StatusWithMatchExpression(s); + s = _parseArrayFilterEntries(temp->getArrayFilterEntries(), e.Obj()); + if (!s.isOK()) + return StatusWithMatchExpression(s); + + std::unique_ptr<NotMatchExpression> temp2(new NotMatchExpression()); + s = temp2->init(temp.release()); + if (!s.isOK()) + return StatusWithMatchExpression(s); + + return StatusWithMatchExpression(temp2.release()); } case BSONObj::opSIZE: { int size = 0; - if ( e.type() == String ) { + if (e.type() == String) { // matching old odd semantics size = 0; - } - else if ( e.type() == NumberInt || e.type() == NumberLong ) { + } else if (e.type() == NumberInt || e.type() == NumberLong) { if (e.numberLong() < 0) { // SERVER-11952. Setting 'size' to -1 means that no documents // should match this $size expression. size = -1; - } - else { + } else { size = e.numberInt(); } - } - else if ( e.type() == NumberDouble ) { - if ( e.numberInt() == e.numberDouble() ) { + } else if (e.type() == NumberDouble) { + if (e.numberInt() == e.numberDouble()) { size = e.numberInt(); - } - else { + } else { // old semantcs require exact numeric match // so [1,2] != 1 or 2 size = -1; } - } - else { - return StatusWithMatchExpression( ErrorCodes::BadValue, "$size needs a number" ); + } else { + return StatusWithMatchExpression(ErrorCodes::BadValue, "$size needs a number"); } - std::unique_ptr<SizeMatchExpression> temp( new SizeMatchExpression() ); - Status s = temp->init( name, size ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - return StatusWithMatchExpression( temp.release() ); + std::unique_ptr<SizeMatchExpression> temp(new SizeMatchExpression()); + Status s = temp->init(name, size); + if (!s.isOK()) + return StatusWithMatchExpression(s); + return StatusWithMatchExpression(temp.release()); } case BSONObj::opEXISTS: { - if ( e.eoo() ) - return StatusWithMatchExpression( ErrorCodes::BadValue, "$exists can't be eoo" ); - std::unique_ptr<ExistsMatchExpression> temp( new ExistsMatchExpression() ); - Status s = temp->init( name ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - if ( e.trueValue() ) - return StatusWithMatchExpression( temp.release() ); - std::unique_ptr<NotMatchExpression> temp2( new NotMatchExpression() ); - s = temp2->init( temp.release() ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - return StatusWithMatchExpression( temp2.release() ); + if (e.eoo()) + return StatusWithMatchExpression(ErrorCodes::BadValue, "$exists can't be eoo"); + std::unique_ptr<ExistsMatchExpression> temp(new ExistsMatchExpression()); + Status s = temp->init(name); + if (!s.isOK()) + return StatusWithMatchExpression(s); + if (e.trueValue()) + return StatusWithMatchExpression(temp.release()); + std::unique_ptr<NotMatchExpression> temp2(new NotMatchExpression()); + s = temp2->init(temp.release()); + if (!s.isOK()) + return StatusWithMatchExpression(s); + return StatusWithMatchExpression(temp2.release()); } case BSONObj::opTYPE: { - if ( !e.isNumber() ) - return StatusWithMatchExpression( ErrorCodes::BadValue, "$type has to be a number" ); + if (!e.isNumber()) + return StatusWithMatchExpression(ErrorCodes::BadValue, "$type has to be a number"); int type = e.numberInt(); - if ( e.type() != NumberInt && type != e.number() ) + if (e.type() != NumberInt && type != e.number()) type = -1; - std::unique_ptr<TypeMatchExpression> temp( new TypeMatchExpression() ); - Status s = temp->init( name, type ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - return StatusWithMatchExpression( temp.release() ); + std::unique_ptr<TypeMatchExpression> temp(new TypeMatchExpression()); + Status s = temp->init(name, type); + if (!s.isOK()) + return StatusWithMatchExpression(s); + return StatusWithMatchExpression(temp.release()); } case BSONObj::opMOD: - return _parseMOD( name, e ); + return _parseMOD(name, e); case BSONObj::opOPTIONS: { // TODO: try to optimize this // we have to do this since $options can be before or after a $regex // but we validate here - BSONObjIterator i( context ); - while ( i.more() ) { + BSONObjIterator i(context); + while (i.more()) { BSONElement temp = i.next(); - if ( temp.getGtLtOp( -1 ) == BSONObj::opREGEX ) - return StatusWithMatchExpression( NULL ); + if (temp.getGtLtOp(-1) == BSONObj::opREGEX) + return StatusWithMatchExpression(NULL); } - return StatusWithMatchExpression( ErrorCodes::BadValue, "$options needs a $regex" ); + return StatusWithMatchExpression(ErrorCodes::BadValue, "$options needs a $regex"); } case BSONObj::opREGEX: { - return _parseRegexDocument( name, context ); + return _parseRegexDocument(name, context); } case BSONObj::opELEM_MATCH: - return _parseElemMatch( name, e, level ); + return _parseElemMatch(name, e, level); case BSONObj::opALL: - return _parseAll( name, e, level ); + return _parseAll(name, e, level); case BSONObj::opWITHIN: case BSONObj::opGEO_INTERSECTS: - return expressionParserGeoCallback( name, x, context ); - } - - return StatusWithMatchExpression( ErrorCodes::BadValue, - mongoutils::str::stream() << "not handled: " << e.fieldName() ); + return expressionParserGeoCallback(name, x, context); } - StatusWithMatchExpression MatchExpressionParser::_parse( const BSONObj& obj, int level ) { - if (level > kMaximumTreeDepth) { - mongoutils::str::stream ss; - ss << "exceeded maximum query tree depth of " << kMaximumTreeDepth - << " at " << obj.toString(); - return StatusWithMatchExpression( ErrorCodes::BadValue, ss ); - } - - std::unique_ptr<AndMatchExpression> root( new AndMatchExpression() ); - - bool topLevel = (level == 0); - level++; - - BSONObjIterator i( obj ); - while ( i.more() ){ + return StatusWithMatchExpression(ErrorCodes::BadValue, + mongoutils::str::stream() << "not handled: " << e.fieldName()); +} - BSONElement e = i.next(); - if ( e.fieldName()[0] == '$' ) { - const char * rest = e.fieldName() + 1; +StatusWithMatchExpression MatchExpressionParser::_parse(const BSONObj& obj, int level) { + if (level > kMaximumTreeDepth) { + mongoutils::str::stream ss; + ss << "exceeded maximum query tree depth of " << kMaximumTreeDepth << " at " + << obj.toString(); + return StatusWithMatchExpression(ErrorCodes::BadValue, ss); + } - // TODO: optimize if block? - if ( mongoutils::str::equals( "or", rest ) ) { - if ( e.type() != Array ) - return StatusWithMatchExpression( ErrorCodes::BadValue, - "$or needs an array" ); - std::unique_ptr<OrMatchExpression> temp( new OrMatchExpression() ); - Status s = _parseTreeList( e.Obj(), temp.get(), level ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - root->add( temp.release() ); - } - else if ( mongoutils::str::equals( "and", rest ) ) { - if ( e.type() != Array ) - return StatusWithMatchExpression( ErrorCodes::BadValue, - "and needs an array" ); - std::unique_ptr<AndMatchExpression> temp( new AndMatchExpression() ); - Status s = _parseTreeList( e.Obj(), temp.get(), level ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - root->add( temp.release() ); - } - else if ( mongoutils::str::equals( "nor", rest ) ) { - if ( e.type() != Array ) - return StatusWithMatchExpression( ErrorCodes::BadValue, - "and needs an array" ); - std::unique_ptr<NorMatchExpression> temp( new NorMatchExpression() ); - Status s = _parseTreeList( e.Obj(), temp.get(), level ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - root->add( temp.release() ); - } - else if ( mongoutils::str::equals( "atomic", rest ) || - mongoutils::str::equals( "isolated", rest ) ) { - if ( !topLevel ) - return StatusWithMatchExpression( ErrorCodes::BadValue, - "$atomic/$isolated has to be at the top level" ); - if ( e.trueValue() ) - root->add( new AtomicMatchExpression() ); - } - else if ( mongoutils::str::equals( "where", rest ) ) { - StatusWithMatchExpression s = _whereCallback->parseWhere(e); - if ( !s.isOK() ) - return s; - root->add( s.getValue() ); - } - else if ( mongoutils::str::equals( "text", rest ) ) { - if ( e.type() != Object ) { - return StatusWithMatchExpression( ErrorCodes::BadValue, - "$text expects an object" ); - } - StatusWithMatchExpression s = expressionParserTextCallback( e.Obj() ); - if ( !s.isOK() ) { - return s; - } - root->add( s.getValue() ); - } - else if ( mongoutils::str::equals( "comment", rest ) ) { - } - else if ( mongoutils::str::equals( "ref", rest ) || - mongoutils::str::equals( "id", rest ) || - mongoutils::str::equals( "db", rest ) ) { - // DBRef fields. - std::unique_ptr<ComparisonMatchExpression> eq( new EqualityMatchExpression() ); - Status s = eq->init( e.fieldName(), e ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - - root->add( eq.release() ); + std::unique_ptr<AndMatchExpression> root(new AndMatchExpression()); + + bool topLevel = (level == 0); + level++; + + BSONObjIterator i(obj); + while (i.more()) { + BSONElement e = i.next(); + if (e.fieldName()[0] == '$') { + const char* rest = e.fieldName() + 1; + + // TODO: optimize if block? + if (mongoutils::str::equals("or", rest)) { + if (e.type() != Array) + return StatusWithMatchExpression(ErrorCodes::BadValue, "$or needs an array"); + std::unique_ptr<OrMatchExpression> temp(new OrMatchExpression()); + Status s = _parseTreeList(e.Obj(), temp.get(), level); + if (!s.isOK()) + return StatusWithMatchExpression(s); + root->add(temp.release()); + } else if (mongoutils::str::equals("and", rest)) { + if (e.type() != Array) + return StatusWithMatchExpression(ErrorCodes::BadValue, "and needs an array"); + std::unique_ptr<AndMatchExpression> temp(new AndMatchExpression()); + Status s = _parseTreeList(e.Obj(), temp.get(), level); + if (!s.isOK()) + return StatusWithMatchExpression(s); + root->add(temp.release()); + } else if (mongoutils::str::equals("nor", rest)) { + if (e.type() != Array) + return StatusWithMatchExpression(ErrorCodes::BadValue, "and needs an array"); + std::unique_ptr<NorMatchExpression> temp(new NorMatchExpression()); + Status s = _parseTreeList(e.Obj(), temp.get(), level); + if (!s.isOK()) + return StatusWithMatchExpression(s); + root->add(temp.release()); + } else if (mongoutils::str::equals("atomic", rest) || + mongoutils::str::equals("isolated", rest)) { + if (!topLevel) + return StatusWithMatchExpression( + ErrorCodes::BadValue, "$atomic/$isolated has to be at the top level"); + if (e.trueValue()) + root->add(new AtomicMatchExpression()); + } else if (mongoutils::str::equals("where", rest)) { + StatusWithMatchExpression s = _whereCallback->parseWhere(e); + if (!s.isOK()) + return s; + root->add(s.getValue()); + } else if (mongoutils::str::equals("text", rest)) { + if (e.type() != Object) { + return StatusWithMatchExpression(ErrorCodes::BadValue, + "$text expects an object"); } - else { - return StatusWithMatchExpression( ErrorCodes::BadValue, - mongoutils::str::stream() - << "unknown top level operator: " - << e.fieldName() ); + StatusWithMatchExpression s = expressionParserTextCallback(e.Obj()); + if (!s.isOK()) { + return s; } - - continue; - } - - if ( _isExpressionDocument( e, false ) ) { - Status s = _parseSub( e.fieldName(), e.Obj(), root.get(), level ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - continue; + root->add(s.getValue()); + } else if (mongoutils::str::equals("comment", rest)) { + } else if (mongoutils::str::equals("ref", rest) || + mongoutils::str::equals("id", rest) || mongoutils::str::equals("db", rest)) { + // DBRef fields. + std::unique_ptr<ComparisonMatchExpression> eq(new EqualityMatchExpression()); + Status s = eq->init(e.fieldName(), e); + if (!s.isOK()) + return StatusWithMatchExpression(s); + + root->add(eq.release()); + } else { + return StatusWithMatchExpression( + ErrorCodes::BadValue, + mongoutils::str::stream() << "unknown top level operator: " << e.fieldName()); } - if ( e.type() == RegEx ) { - StatusWithMatchExpression result = _parseRegexElement( e.fieldName(), e ); - if ( !result.isOK() ) - return result; - root->add( result.getValue() ); - continue; - } - - std::unique_ptr<ComparisonMatchExpression> eq( new EqualityMatchExpression() ); - Status s = eq->init( e.fieldName(), e ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); + continue; + } - root->add( eq.release() ); + if (_isExpressionDocument(e, false)) { + Status s = _parseSub(e.fieldName(), e.Obj(), root.get(), level); + if (!s.isOK()) + return StatusWithMatchExpression(s); + continue; } - if ( root->numChildren() == 1 ) { - const MatchExpression* real = root->getChild(0); - root->clearAndRelease(); - return StatusWithMatchExpression( const_cast<MatchExpression*>(real) ); + if (e.type() == RegEx) { + StatusWithMatchExpression result = _parseRegexElement(e.fieldName(), e); + if (!result.isOK()) + return result; + root->add(result.getValue()); + continue; } - return StatusWithMatchExpression( root.release() ); + std::unique_ptr<ComparisonMatchExpression> eq(new EqualityMatchExpression()); + Status s = eq->init(e.fieldName(), e); + if (!s.isOK()) + return StatusWithMatchExpression(s); + + root->add(eq.release()); } - Status MatchExpressionParser::_parseSub( const char* name, - const BSONObj& sub, - AndMatchExpression* root, - int level ) { - // The one exception to {field : {fully contained argument} } is, of course, geo. Example: - // sub == { field : {$near[Sphere]: [0,0], $maxDistance: 1000, $minDistance: 10 } } - // We peek inside of 'sub' to see if it's possibly a $near. If so, we can't iterate over - // its subfields and parse them one at a time (there is no $maxDistance without $near), so - // we hand the entire object over to the geo parsing routines. - - if (level > kMaximumTreeDepth) { - mongoutils::str::stream ss; - ss << "exceeded maximum query tree depth of " << kMaximumTreeDepth - << " at " << sub.toString(); - return Status( ErrorCodes::BadValue, ss ); - } + if (root->numChildren() == 1) { + const MatchExpression* real = root->getChild(0); + root->clearAndRelease(); + return StatusWithMatchExpression(const_cast<MatchExpression*>(real)); + } + + return StatusWithMatchExpression(root.release()); +} - level++; - - BSONObjIterator geoIt(sub); - if (geoIt.more()) { - BSONElement firstElt = geoIt.next(); - if (firstElt.isABSONObj()) { - const char* fieldName = firstElt.fieldName(); - // TODO: Having these $fields here isn't ideal but we don't want to pull in anything - // from db/geo at this point, since it may not actually be linked in... - if (mongoutils::str::equals(fieldName, "$near") - || mongoutils::str::equals(fieldName, "$nearSphere") - || mongoutils::str::equals(fieldName, "$geoNear") - || mongoutils::str::equals(fieldName, "$maxDistance") - || mongoutils::str::equals(fieldName, "$minDistance")) { - - StatusWithMatchExpression s = expressionParserGeoCallback(name, - firstElt.getGtLtOp(), - sub); - if (s.isOK()) { - root->add(s.getValue()); - } - - // Propagate geo parsing result to caller. - return s.getStatus(); +Status MatchExpressionParser::_parseSub(const char* name, + const BSONObj& sub, + AndMatchExpression* root, + int level) { + // The one exception to {field : {fully contained argument} } is, of course, geo. Example: + // sub == { field : {$near[Sphere]: [0,0], $maxDistance: 1000, $minDistance: 10 } } + // We peek inside of 'sub' to see if it's possibly a $near. If so, we can't iterate over + // its subfields and parse them one at a time (there is no $maxDistance without $near), so + // we hand the entire object over to the geo parsing routines. + + if (level > kMaximumTreeDepth) { + mongoutils::str::stream ss; + ss << "exceeded maximum query tree depth of " << kMaximumTreeDepth << " at " + << sub.toString(); + return Status(ErrorCodes::BadValue, ss); + } + + level++; + + BSONObjIterator geoIt(sub); + if (geoIt.more()) { + BSONElement firstElt = geoIt.next(); + if (firstElt.isABSONObj()) { + const char* fieldName = firstElt.fieldName(); + // TODO: Having these $fields here isn't ideal but we don't want to pull in anything + // from db/geo at this point, since it may not actually be linked in... + if (mongoutils::str::equals(fieldName, "$near") || + mongoutils::str::equals(fieldName, "$nearSphere") || + mongoutils::str::equals(fieldName, "$geoNear") || + mongoutils::str::equals(fieldName, "$maxDistance") || + mongoutils::str::equals(fieldName, "$minDistance")) { + StatusWithMatchExpression s = + expressionParserGeoCallback(name, firstElt.getGtLtOp(), sub); + if (s.isOK()) { + root->add(s.getValue()); } + + // Propagate geo parsing result to caller. + return s.getStatus(); } } + } - BSONObjIterator j( sub ); - while ( j.more() ) { - BSONElement deep = j.next(); - - StatusWithMatchExpression s = _parseSubField( sub, root, name, deep, level ); - if ( !s.isOK() ) - return s.getStatus(); + BSONObjIterator j(sub); + while (j.more()) { + BSONElement deep = j.next(); - if ( s.getValue() ) - root->add( s.getValue() ); - } + StatusWithMatchExpression s = _parseSubField(sub, root, name, deep, level); + if (!s.isOK()) + return s.getStatus(); - return Status::OK(); + if (s.getValue()) + root->add(s.getValue()); } - bool MatchExpressionParser::_isExpressionDocument( const BSONElement& e, - bool allowIncompleteDBRef ) { - if ( e.type() != Object ) - return false; + return Status::OK(); +} - BSONObj o = e.Obj(); - if ( o.isEmpty() ) - return false; +bool MatchExpressionParser::_isExpressionDocument(const BSONElement& e, bool allowIncompleteDBRef) { + if (e.type() != Object) + return false; - const char* name = o.firstElement().fieldName(); - if ( name[0] != '$' ) - return false; + BSONObj o = e.Obj(); + if (o.isEmpty()) + return false; - if ( _isDBRefDocument( o, allowIncompleteDBRef ) ) { - return false; - } + const char* name = o.firstElement().fieldName(); + if (name[0] != '$') + return false; - return true; + if (_isDBRefDocument(o, allowIncompleteDBRef)) { + return false; } - /** - * DBRef fields are ordered in the collection. - * In the query, we consider an embedded object a query on - * a DBRef as long as it contains $ref and $id. - * Required fields: $ref and $id (if incomplete DBRefs are not allowed) - * - * If incomplete DBRefs are allowed, we accept the BSON object as long as it - * contains $ref, $id or $db. - * - * Field names are checked but not field types. - */ - bool MatchExpressionParser::_isDBRefDocument( const BSONObj& obj, bool allowIncompleteDBRef ) { - bool hasRef = false; - bool hasID = false; - bool hasDB = false; - - BSONObjIterator i( obj ); - while ( i.more() && !( hasRef && hasID ) ) { - BSONElement element = i.next(); - const char *fieldName = element.fieldName(); - // $ref - if ( !hasRef && mongoutils::str::equals( "$ref", fieldName ) ) { - hasRef = true; - } - // $id - else if ( !hasID && mongoutils::str::equals( "$id", fieldName ) ) { - hasID = true; - } - // $db - else if ( !hasDB && mongoutils::str::equals( "$db", fieldName ) ) { - hasDB = true; - } - } + return true; +} - if (allowIncompleteDBRef) { - return hasRef || hasID || hasDB; +/** + * DBRef fields are ordered in the collection. + * In the query, we consider an embedded object a query on + * a DBRef as long as it contains $ref and $id. + * Required fields: $ref and $id (if incomplete DBRefs are not allowed) + * + * If incomplete DBRefs are allowed, we accept the BSON object as long as it + * contains $ref, $id or $db. + * + * Field names are checked but not field types. + */ +bool MatchExpressionParser::_isDBRefDocument(const BSONObj& obj, bool allowIncompleteDBRef) { + bool hasRef = false; + bool hasID = false; + bool hasDB = false; + + BSONObjIterator i(obj); + while (i.more() && !(hasRef && hasID)) { + BSONElement element = i.next(); + const char* fieldName = element.fieldName(); + // $ref + if (!hasRef && mongoutils::str::equals("$ref", fieldName)) { + hasRef = true; + } + // $id + else if (!hasID && mongoutils::str::equals("$id", fieldName)) { + hasID = true; + } + // $db + else if (!hasDB && mongoutils::str::equals("$db", fieldName)) { + hasDB = true; } - - return hasRef && hasID; } - StatusWithMatchExpression MatchExpressionParser::_parseMOD( const char* name, - const BSONElement& e ) { - - if ( e.type() != Array ) - return StatusWithMatchExpression( ErrorCodes::BadValue, "malformed mod, needs to be an array" ); - - BSONObjIterator i( e.Obj() ); - - if ( !i.more() ) - return StatusWithMatchExpression( ErrorCodes::BadValue, "malformed mod, not enough elements" ); - BSONElement d = i.next(); - if ( !d.isNumber() ) - return StatusWithMatchExpression( ErrorCodes::BadValue, "malformed mod, divisor not a number" ); - - if ( !i.more() ) - return StatusWithMatchExpression( ErrorCodes::BadValue, "malformed mod, not enough elements" ); - BSONElement r = i.next(); - if ( !d.isNumber() ) - return StatusWithMatchExpression( ErrorCodes::BadValue, "malformed mod, remainder not a number" ); + if (allowIncompleteDBRef) { + return hasRef || hasID || hasDB; + } - if ( i.more() ) - return StatusWithMatchExpression( ErrorCodes::BadValue, "malformed mod, too many elements" ); + return hasRef && hasID; +} - std::unique_ptr<ModMatchExpression> temp( new ModMatchExpression() ); - Status s = temp->init( name, d.numberInt(), r.numberInt() ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - return StatusWithMatchExpression( temp.release() ); - } +StatusWithMatchExpression MatchExpressionParser::_parseMOD(const char* name, const BSONElement& e) { + if (e.type() != Array) + return StatusWithMatchExpression(ErrorCodes::BadValue, + "malformed mod, needs to be an array"); + + BSONObjIterator i(e.Obj()); + + if (!i.more()) + return StatusWithMatchExpression(ErrorCodes::BadValue, + "malformed mod, not enough elements"); + BSONElement d = i.next(); + if (!d.isNumber()) + return StatusWithMatchExpression(ErrorCodes::BadValue, + "malformed mod, divisor not a number"); + + if (!i.more()) + return StatusWithMatchExpression(ErrorCodes::BadValue, + "malformed mod, not enough elements"); + BSONElement r = i.next(); + if (!d.isNumber()) + return StatusWithMatchExpression(ErrorCodes::BadValue, + "malformed mod, remainder not a number"); + + if (i.more()) + return StatusWithMatchExpression(ErrorCodes::BadValue, "malformed mod, too many elements"); + + std::unique_ptr<ModMatchExpression> temp(new ModMatchExpression()); + Status s = temp->init(name, d.numberInt(), r.numberInt()); + if (!s.isOK()) + return StatusWithMatchExpression(s); + return StatusWithMatchExpression(temp.release()); +} - StatusWithMatchExpression MatchExpressionParser::_parseRegexElement( const char* name, - const BSONElement& e ) { - if ( e.type() != RegEx ) - return StatusWithMatchExpression( ErrorCodes::BadValue, "not a regex" ); +StatusWithMatchExpression MatchExpressionParser::_parseRegexElement(const char* name, + const BSONElement& e) { + if (e.type() != RegEx) + return StatusWithMatchExpression(ErrorCodes::BadValue, "not a regex"); - std::unique_ptr<RegexMatchExpression> temp( new RegexMatchExpression() ); - Status s = temp->init( name, e.regex(), e.regexFlags() ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - return StatusWithMatchExpression( temp.release() ); - } + std::unique_ptr<RegexMatchExpression> temp(new RegexMatchExpression()); + Status s = temp->init(name, e.regex(), e.regexFlags()); + if (!s.isOK()) + return StatusWithMatchExpression(s); + return StatusWithMatchExpression(temp.release()); +} - StatusWithMatchExpression MatchExpressionParser::_parseRegexDocument( const char* name, - const BSONObj& doc ) { - string regex; - string regexOptions; +StatusWithMatchExpression MatchExpressionParser::_parseRegexDocument(const char* name, + const BSONObj& doc) { + string regex; + string regexOptions; - BSONObjIterator i( doc ); - while ( i.more() ) { - BSONElement e = i.next(); - switch ( e.getGtLtOp() ) { + BSONObjIterator i(doc); + while (i.more()) { + BSONElement e = i.next(); + switch (e.getGtLtOp()) { case BSONObj::opREGEX: - if ( e.type() == String ) { + if (e.type() == String) { regex = e.String(); - } - else if ( e.type() == RegEx ) { + } else if (e.type() == RegEx) { regex = e.regex(); regexOptions = e.regexFlags(); - } - else { - return StatusWithMatchExpression( ErrorCodes::BadValue, - "$regex has to be a string" ); + } else { + return StatusWithMatchExpression(ErrorCodes::BadValue, + "$regex has to be a string"); } break; case BSONObj::opOPTIONS: - if ( e.type() != String ) - return StatusWithMatchExpression( ErrorCodes::BadValue, - "$options has to be a string" ); + if (e.type() != String) + return StatusWithMatchExpression(ErrorCodes::BadValue, + "$options has to be a string"); regexOptions = e.String(); break; default: break; - } - } - - std::unique_ptr<RegexMatchExpression> temp( new RegexMatchExpression() ); - Status s = temp->init( name, regex, regexOptions ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - return StatusWithMatchExpression( temp.release() ); - } - Status MatchExpressionParser::_parseArrayFilterEntries( ArrayFilterEntries* entries, - const BSONObj& theArray ) { - - BSONObjIterator i( theArray ); - while ( i.more() ) { - BSONElement e = i.next(); + std::unique_ptr<RegexMatchExpression> temp(new RegexMatchExpression()); + Status s = temp->init(name, regex, regexOptions); + if (!s.isOK()) + return StatusWithMatchExpression(s); + return StatusWithMatchExpression(temp.release()); +} - // allow DBRefs but reject all fields with names starting wiht $ - if ( _isExpressionDocument( e, false ) ) { - return Status( ErrorCodes::BadValue, "cannot nest $ under $in" ); - } +Status MatchExpressionParser::_parseArrayFilterEntries(ArrayFilterEntries* entries, + const BSONObj& theArray) { + BSONObjIterator i(theArray); + while (i.more()) { + BSONElement e = i.next(); - if ( e.type() == RegEx ) { - std::unique_ptr<RegexMatchExpression> r( new RegexMatchExpression() ); - Status s = r->init( "", e ); - if ( !s.isOK() ) - return s; - s = entries->addRegex( r.release() ); - if ( !s.isOK() ) - return s; - } - else { - Status s = entries->addEquality( e ); - if ( !s.isOK() ) - return s; - } + // allow DBRefs but reject all fields with names starting wiht $ + if (_isExpressionDocument(e, false)) { + return Status(ErrorCodes::BadValue, "cannot nest $ under $in"); } - return Status::OK(); + if (e.type() == RegEx) { + std::unique_ptr<RegexMatchExpression> r(new RegexMatchExpression()); + Status s = r->init("", e); + if (!s.isOK()) + return s; + s = entries->addRegex(r.release()); + if (!s.isOK()) + return s; + } else { + Status s = entries->addEquality(e); + if (!s.isOK()) + return s; + } } + return Status::OK(); +} - StatusWithMatchExpression MatchExpressionParser::_parseElemMatch( const char* name, - const BSONElement& e, - int level ) { - if ( e.type() != Object ) - return StatusWithMatchExpression( ErrorCodes::BadValue, "$elemMatch needs an Object" ); - - BSONObj obj = e.Obj(); - - // $elemMatch value case applies when the children all - // work on the field 'name'. - // This is the case when: - // 1) the argument is an expression document; and - // 2) expression is not a AND/NOR/OR logical operator. Children of - // these logical operators are initialized with field names. - // 3) expression is not a WHERE operator. WHERE works on objects instead - // of specific field. - bool isElemMatchValue = false; - if ( _isExpressionDocument( e, true ) ) { - BSONObj o = e.Obj(); - BSONElement elt = o.firstElement(); - invariant( !elt.eoo() ); - - isElemMatchValue = !mongoutils::str::equals( "$and", elt.fieldName() ) && - !mongoutils::str::equals( "$nor", elt.fieldName() ) && - !mongoutils::str::equals( "$or", elt.fieldName() ) && - !mongoutils::str::equals( "$where", elt.fieldName() ); - } +StatusWithMatchExpression MatchExpressionParser::_parseElemMatch(const char* name, + const BSONElement& e, + int level) { + if (e.type() != Object) + return StatusWithMatchExpression(ErrorCodes::BadValue, "$elemMatch needs an Object"); + + BSONObj obj = e.Obj(); + + // $elemMatch value case applies when the children all + // work on the field 'name'. + // This is the case when: + // 1) the argument is an expression document; and + // 2) expression is not a AND/NOR/OR logical operator. Children of + // these logical operators are initialized with field names. + // 3) expression is not a WHERE operator. WHERE works on objects instead + // of specific field. + bool isElemMatchValue = false; + if (_isExpressionDocument(e, true)) { + BSONObj o = e.Obj(); + BSONElement elt = o.firstElement(); + invariant(!elt.eoo()); - if ( isElemMatchValue ) { - // value case + isElemMatchValue = !mongoutils::str::equals("$and", elt.fieldName()) && + !mongoutils::str::equals("$nor", elt.fieldName()) && + !mongoutils::str::equals("$or", elt.fieldName()) && + !mongoutils::str::equals("$where", elt.fieldName()); + } - AndMatchExpression theAnd; - Status s = _parseSub( "", obj, &theAnd, level ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); + if (isElemMatchValue) { + // value case - std::unique_ptr<ElemMatchValueMatchExpression> temp( new ElemMatchValueMatchExpression() ); - s = temp->init( name ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); + AndMatchExpression theAnd; + Status s = _parseSub("", obj, &theAnd, level); + if (!s.isOK()) + return StatusWithMatchExpression(s); - for ( size_t i = 0; i < theAnd.numChildren(); i++ ) { - temp->add( theAnd.getChild( i ) ); - } - theAnd.clearAndRelease(); + std::unique_ptr<ElemMatchValueMatchExpression> temp(new ElemMatchValueMatchExpression()); + s = temp->init(name); + if (!s.isOK()) + return StatusWithMatchExpression(s); - return StatusWithMatchExpression( temp.release() ); + for (size_t i = 0; i < theAnd.numChildren(); i++) { + temp->add(theAnd.getChild(i)); } + theAnd.clearAndRelease(); - // DBRef value case - // A DBRef document under a $elemMatch should be treated as an object case - // because it may contain non-DBRef fields in addition to $ref, $id and $db. - - // object case + return StatusWithMatchExpression(temp.release()); + } - StatusWithMatchExpression subRaw = _parse( obj, level ); - if ( !subRaw.isOK() ) - return subRaw; - std::unique_ptr<MatchExpression> sub( subRaw.getValue() ); + // DBRef value case + // A DBRef document under a $elemMatch should be treated as an object case + // because it may contain non-DBRef fields in addition to $ref, $id and $db. - // $where is not supported under $elemMatch because $where - // applies to top-level document, not array elements in a field. - if ( hasNode( sub.get(), MatchExpression::WHERE ) ) { - return StatusWithMatchExpression( ErrorCodes::BadValue, - "$elemMatch cannot contain $where expression" ); - } + // object case - std::unique_ptr<ElemMatchObjectMatchExpression> temp( new ElemMatchObjectMatchExpression() ); - Status status = temp->init( name, sub.release() ); - if ( !status.isOK() ) - return StatusWithMatchExpression( status ); + StatusWithMatchExpression subRaw = _parse(obj, level); + if (!subRaw.isOK()) + return subRaw; + std::unique_ptr<MatchExpression> sub(subRaw.getValue()); - return StatusWithMatchExpression( temp.release() ); + // $where is not supported under $elemMatch because $where + // applies to top-level document, not array elements in a field. + if (hasNode(sub.get(), MatchExpression::WHERE)) { + return StatusWithMatchExpression(ErrorCodes::BadValue, + "$elemMatch cannot contain $where expression"); } - StatusWithMatchExpression MatchExpressionParser::_parseAll( const char* name, - const BSONElement& e, - int level ) { - if ( e.type() != Array ) - return StatusWithMatchExpression( ErrorCodes::BadValue, "$all needs an array" ); - - BSONObj arr = e.Obj(); - std::unique_ptr<AndMatchExpression> myAnd( new AndMatchExpression() ); - BSONObjIterator i( arr ); - - if ( arr.firstElement().type() == Object && - mongoutils::str::equals( "$elemMatch", - arr.firstElement().Obj().firstElement().fieldName() ) ) { - // $all : [ { $elemMatch : {} } ... ] - - while ( i.more() ) { - BSONElement hopefullyElemMatchElement = i.next(); - - if ( hopefullyElemMatchElement.type() != Object ) { - // $all : [ { $elemMatch : ... }, 5 ] - return StatusWithMatchExpression( ErrorCodes::BadValue, - "$all/$elemMatch has to be consistent" ); - } + std::unique_ptr<ElemMatchObjectMatchExpression> temp(new ElemMatchObjectMatchExpression()); + Status status = temp->init(name, sub.release()); + if (!status.isOK()) + return StatusWithMatchExpression(status); - BSONObj hopefullyElemMatchObj = hopefullyElemMatchElement.Obj(); - if ( !mongoutils::str::equals( "$elemMatch", - hopefullyElemMatchObj.firstElement().fieldName() ) ) { - // $all : [ { $elemMatch : ... }, { x : 5 } ] - return StatusWithMatchExpression( ErrorCodes::BadValue, - "$all/$elemMatch has to be consistent" ); - } + return StatusWithMatchExpression(temp.release()); +} - StatusWithMatchExpression inner = - _parseElemMatch( name, hopefullyElemMatchObj.firstElement(), level ); - if ( !inner.isOK() ) - return inner; - myAnd->add( inner.getValue() ); - } +StatusWithMatchExpression MatchExpressionParser::_parseAll(const char* name, + const BSONElement& e, + int level) { + if (e.type() != Array) + return StatusWithMatchExpression(ErrorCodes::BadValue, "$all needs an array"); - return StatusWithMatchExpression( myAnd.release() ); - } + BSONObj arr = e.Obj(); + std::unique_ptr<AndMatchExpression> myAnd(new AndMatchExpression()); + BSONObjIterator i(arr); - while ( i.more() ) { - BSONElement e = i.next(); + if (arr.firstElement().type() == Object && + mongoutils::str::equals("$elemMatch", + arr.firstElement().Obj().firstElement().fieldName())) { + // $all : [ { $elemMatch : {} } ... ] - if ( e.type() == RegEx ) { - std::unique_ptr<RegexMatchExpression> r( new RegexMatchExpression() ); - Status s = r->init( name, e ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - myAnd->add( r.release() ); - } - else if ( e.type() == Object && e.Obj().firstElement().getGtLtOp(-1) != -1 ) { - return StatusWithMatchExpression( ErrorCodes::BadValue, "no $ expressions in $all" ); + while (i.more()) { + BSONElement hopefullyElemMatchElement = i.next(); + + if (hopefullyElemMatchElement.type() != Object) { + // $all : [ { $elemMatch : ... }, 5 ] + return StatusWithMatchExpression(ErrorCodes::BadValue, + "$all/$elemMatch has to be consistent"); } - else { - std::unique_ptr<EqualityMatchExpression> x( new EqualityMatchExpression() ); - Status s = x->init( name, e ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - myAnd->add( x.release() ); + + BSONObj hopefullyElemMatchObj = hopefullyElemMatchElement.Obj(); + if (!mongoutils::str::equals("$elemMatch", + hopefullyElemMatchObj.firstElement().fieldName())) { + // $all : [ { $elemMatch : ... }, { x : 5 } ] + return StatusWithMatchExpression(ErrorCodes::BadValue, + "$all/$elemMatch has to be consistent"); } - } - if ( myAnd->numChildren() == 0 ) { - return StatusWithMatchExpression( new FalseMatchExpression() ); + StatusWithMatchExpression inner = + _parseElemMatch(name, hopefullyElemMatchObj.firstElement(), level); + if (!inner.isOK()) + return inner; + myAnd->add(inner.getValue()); } - return StatusWithMatchExpression( myAnd.release() ); + return StatusWithMatchExpression(myAnd.release()); } - StatusWithMatchExpression MatchExpressionParser::WhereCallback::parseWhere( - const BSONElement& where) const { - return StatusWithMatchExpression(ErrorCodes::NoWhereParseContext, - "no context for parsing $where"); + while (i.more()) { + BSONElement e = i.next(); + + if (e.type() == RegEx) { + std::unique_ptr<RegexMatchExpression> r(new RegexMatchExpression()); + Status s = r->init(name, e); + if (!s.isOK()) + return StatusWithMatchExpression(s); + myAnd->add(r.release()); + } else if (e.type() == Object && e.Obj().firstElement().getGtLtOp(-1) != -1) { + return StatusWithMatchExpression(ErrorCodes::BadValue, "no $ expressions in $all"); + } else { + std::unique_ptr<EqualityMatchExpression> x(new EqualityMatchExpression()); + Status s = x->init(name, e); + if (!s.isOK()) + return StatusWithMatchExpression(s); + myAnd->add(x.release()); + } } - // Geo - StatusWithMatchExpression expressionParserGeoCallbackDefault( const char* name, - int type, - const BSONObj& section ) { - return StatusWithMatchExpression( ErrorCodes::BadValue, "geo not linked in" ); + if (myAnd->numChildren() == 0) { + return StatusWithMatchExpression(new FalseMatchExpression()); } - MatchExpressionParserGeoCallback expressionParserGeoCallback = - expressionParserGeoCallbackDefault; + return StatusWithMatchExpression(myAnd.release()); +} - // Text - StatusWithMatchExpression expressionParserTextCallbackDefault( const BSONObj& queryObj ) { - return StatusWithMatchExpression( ErrorCodes::BadValue, "$text not linked in" ); - } +StatusWithMatchExpression MatchExpressionParser::WhereCallback::parseWhere( + const BSONElement& where) const { + return StatusWithMatchExpression(ErrorCodes::NoWhereParseContext, + "no context for parsing $where"); +} - MatchExpressionParserTextCallback expressionParserTextCallback = - expressionParserTextCallbackDefault; +// Geo +StatusWithMatchExpression expressionParserGeoCallbackDefault(const char* name, + int type, + const BSONObj& section) { + return StatusWithMatchExpression(ErrorCodes::BadValue, "geo not linked in"); +} + +MatchExpressionParserGeoCallback expressionParserGeoCallback = expressionParserGeoCallbackDefault; + +// Text +StatusWithMatchExpression expressionParserTextCallbackDefault(const BSONObj& queryObj) { + return StatusWithMatchExpression(ErrorCodes::BadValue, "$text not linked in"); +} +MatchExpressionParserTextCallback expressionParserTextCallback = + expressionParserTextCallbackDefault; } |