// expression_parser.cpp /** * Copyright (C) 2013 10gen Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the GNU Affero General Public License in all respects for * all of the code used other than as permitted herein. If you modify file(s) * with this exception, you may extend this exception to your version of the * file(s), but you are not obligated to do so. If you do not wish to do so, * delete this exception statement from your version. If you delete this * exception statement from all source files in the program, then also delete * it in the license file. */ #include "mongo/platform/basic.h" #include "mongo/db/matcher/expression_parser.h" #include "mongo/base/init.h" #include "mongo/bson/bsonmisc.h" #include "mongo/bson/bsonobj.h" #include "mongo/bson/bsonobjbuilder.h" #include "mongo/db/matcher/expression_always_boolean.h" #include "mongo/db/matcher/expression_array.h" #include "mongo/db/matcher/expression_geo.h" #include "mongo/db/matcher/expression_leaf.h" #include "mongo/db/matcher/expression_tree.h" #include "mongo/db/matcher/expression_with_placeholder.h" #include "mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.h" #include "mongo/db/matcher/schema/expression_internal_schema_cond.h" #include "mongo/db/matcher/schema/expression_internal_schema_fmod.h" #include "mongo/db/matcher/schema/expression_internal_schema_match_array_index.h" #include "mongo/db/matcher/schema/expression_internal_schema_max_items.h" #include "mongo/db/matcher/schema/expression_internal_schema_max_length.h" #include "mongo/db/matcher/schema/expression_internal_schema_max_properties.h" #include "mongo/db/matcher/schema/expression_internal_schema_min_items.h" #include "mongo/db/matcher/schema/expression_internal_schema_min_length.h" #include "mongo/db/matcher/schema/expression_internal_schema_min_properties.h" #include "mongo/db/matcher/schema/expression_internal_schema_object_match.h" #include "mongo/db/matcher/schema/expression_internal_schema_unique_items.h" #include "mongo/db/matcher/schema/expression_internal_schema_xor.h" #include "mongo/db/matcher/schema/json_schema_parser.h" #include "mongo/db/namespace_string.h" #include "mongo/stdx/memory.h" #include "mongo/util/mongoutils/str.h" #include "mongo/util/string_map.h" namespace { using namespace mongo; /** * 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; } } return false; } } // namespace namespace mongo { constexpr StringData MatchExpressionParser::kAggExpression; using std::string; using stdx::make_unique; const double MatchExpressionParser::kLongLongMaxPlusOneAsDouble = scalbn(1, std::numeric_limits::digits); StatusWithMatchExpression MatchExpressionParser::_parseComparison( const char* name, ComparisonMatchExpression* cmp, const BSONElement& e, const CollatorInterface* collator, const boost::intrusive_ptr& expCtx) { std::unique_ptr 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()) { mongoutils::str::stream ss; ss << "Can't have RegEx as arg to predicate over field '" << name << "'."; return {Status(ErrorCodes::BadValue, ss)}; } if (_isAggExpression(e, expCtx)) { auto expr = _parseAggExpression(e, expCtx); auto s = temp->init(name, expr); if (!s.isOK()) { return s; } } else { auto s = temp->init(name, e); if (!s.isOK()) { return s; } } temp->setCollator(collator); return {std::move(temp)}; } StatusWithMatchExpression MatchExpressionParser::_parseSubField( const BSONObj& context, const AndMatchExpression* andSoFar, const char* name, const BSONElement& e, const CollatorInterface* collator, const boost::intrusive_ptr& expCtx, bool topLevel) { if (mongoutils::str::equals("$eq", e.fieldName())) return _parseComparison(name, new EqualityMatchExpression(), e, collator, expCtx); if (mongoutils::str::equals("$not", e.fieldName())) { return _parseNot(name, e, collator, expCtx, topLevel); } auto parseExpMatchType = MatchExpressionParser::parsePathAcceptingKeyword(e); if (!parseExpMatchType) { // $where cannot be a sub-expression because it works on top-level documents only. if (mongoutils::str::equals("$where", e.fieldName())) { return {Status(ErrorCodes::BadValue, "$where cannot be applied to a field")}; } return {Status(ErrorCodes::BadValue, mongoutils::str::stream() << "unknown operator: " << e.fieldName())}; } switch (*parseExpMatchType) { case PathAcceptingKeyword::LESS_THAN: return _parseComparison(name, new LTMatchExpression(), e, collator, expCtx); case PathAcceptingKeyword::LESS_THAN_OR_EQUAL: return _parseComparison(name, new LTEMatchExpression(), e, collator, expCtx); case PathAcceptingKeyword::GREATER_THAN: return _parseComparison(name, new GTMatchExpression(), e, collator, expCtx); case PathAcceptingKeyword::GREATER_THAN_OR_EQUAL: return _parseComparison(name, new GTEMatchExpression(), e, collator, expCtx); case PathAcceptingKeyword::NOT_EQUAL: { 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 {Status(ErrorCodes::BadValue, "Can't have regex as arg to $ne.")}; } StatusWithMatchExpression s = _parseComparison(name, new EqualityMatchExpression(), e, collator, expCtx); if (!s.isOK()) return s; std::unique_ptr n = stdx::make_unique(); Status s2 = n->init(s.getValue().release()); if (!s2.isOK()) return s2; return {std::move(n)}; } case PathAcceptingKeyword::EQUALITY: return _parseComparison(name, new EqualityMatchExpression(), e, collator, expCtx); case PathAcceptingKeyword::IN_EXPR: { if (e.type() != Array) return {Status(ErrorCodes::BadValue, "$in needs an array")}; std::unique_ptr temp = stdx::make_unique(); Status s = temp->init(name); if (!s.isOK()) return s; s = _parseInExpression(temp.get(), e.Obj(), collator, expCtx); if (!s.isOK()) return s; return {std::move(temp)}; } case PathAcceptingKeyword::NOT_IN: { if (e.type() != Array) return {Status(ErrorCodes::BadValue, "$nin needs an array")}; std::unique_ptr temp = stdx::make_unique(); Status s = temp->init(name); if (!s.isOK()) return s; s = _parseInExpression(temp.get(), e.Obj(), collator, expCtx); if (!s.isOK()) return s; std::unique_ptr temp2 = stdx::make_unique(); s = temp2->init(temp.release()); if (!s.isOK()) return s; return {std::move(temp2)}; } case PathAcceptingKeyword::SIZE: { int size = 0; if (e.type() == NumberInt) { size = e.numberInt(); } else if (e.type() == NumberLong) { if (e.numberInt() == e.numberLong()) { size = e.numberInt(); } else { return {Status(ErrorCodes::BadValue, "$size must be representable as a 32-bit integer")}; } } else if (e.type() == NumberDouble) { if (e.numberInt() == e.numberDouble()) { size = e.numberInt(); } else { return {Status(ErrorCodes::BadValue, "$size must be a whole number")}; } } else { return {Status(ErrorCodes::BadValue, "$size needs a number")}; } if (size < 0) { return {Status(ErrorCodes::BadValue, "$size may not be negative")}; } std::unique_ptr temp = stdx::make_unique(); Status s = temp->init(name, size); if (!s.isOK()) return s; return {std::move(temp)}; } case PathAcceptingKeyword::EXISTS: { if (e.eoo()) return {Status(ErrorCodes::BadValue, "$exists can't be eoo")}; std::unique_ptr temp = stdx::make_unique(); Status s = temp->init(name); if (!s.isOK()) return s; if (e.trueValue()) return {std::move(temp)}; std::unique_ptr temp2 = stdx::make_unique(); s = temp2->init(temp.release()); if (!s.isOK()) return s; return {std::move(temp2)}; } case PathAcceptingKeyword::TYPE: return _parseType(name, e, expCtx); case PathAcceptingKeyword::MOD: return _parseMOD(name, e, expCtx); case PathAcceptingKeyword::OPTIONS: { // 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()) { BSONElement temp = i.next(); if (MatchExpressionParser::parsePathAcceptingKeyword(temp) == PathAcceptingKeyword::REGEX) return {nullptr}; } return {Status(ErrorCodes::BadValue, "$options needs a $regex")}; } case PathAcceptingKeyword::REGEX: { return _parseRegexDocument(name, context, expCtx); } case PathAcceptingKeyword::ELEM_MATCH: return _parseElemMatch(name, e, collator, expCtx, topLevel); case PathAcceptingKeyword::ALL: return _parseAll(name, e, collator, expCtx, topLevel); case PathAcceptingKeyword::WITHIN: case PathAcceptingKeyword::GEO_INTERSECTS: return _parseGeo(name, *parseExpMatchType, context); case PathAcceptingKeyword::GEO_NEAR: return {Status(ErrorCodes::BadValue, mongoutils::str::stream() << "near must be first in: " << context)}; // Handles bitwise query operators. case PathAcceptingKeyword::BITS_ALL_SET: { return _parseBitTest(name, e, expCtx); } case PathAcceptingKeyword::BITS_ALL_CLEAR: { return _parseBitTest(name, e, expCtx); } case PathAcceptingKeyword::BITS_ANY_SET: { return _parseBitTest(name, e, expCtx); } case PathAcceptingKeyword::BITS_ANY_CLEAR: { return _parseBitTest(name, e, expCtx); } case PathAcceptingKeyword::INTERNAL_SCHEMA_FMOD: return _parseInternalSchemaFmod(name, e); case PathAcceptingKeyword::INTERNAL_SCHEMA_MIN_ITEMS: { return _parseInternalSchemaSingleIntegerArgument( name, e); } case PathAcceptingKeyword::INTERNAL_SCHEMA_MAX_ITEMS: { return _parseInternalSchemaSingleIntegerArgument( name, e); } case PathAcceptingKeyword::INTERNAL_SCHEMA_OBJECT_MATCH: { if (e.type() != BSONType::Object) { return Status(ErrorCodes::FailedToParse, str::stream() << "$_internalSchemaObjectMatch must be an object"); } auto parsedSubObjExpr = _parse(e.Obj(), collator, expCtx, topLevel); if (!parsedSubObjExpr.isOK()) { return parsedSubObjExpr; } auto expr = stdx::make_unique(); auto status = expr->init(std::move(parsedSubObjExpr.getValue()), name); if (!status.isOK()) { return status; } return {std::move(expr)}; } case PathAcceptingKeyword::INTERNAL_SCHEMA_UNIQUE_ITEMS: { if (!e.isBoolean() || !e.boolean()) { return {ErrorCodes::FailedToParse, str::stream() << name << " must be a boolean of value true"}; } auto expr = stdx::make_unique(); auto status = expr->init(name); if (!status.isOK()) { return status; } return {std::move(expr)}; } case PathAcceptingKeyword::INTERNAL_SCHEMA_MIN_LENGTH: { return _parseInternalSchemaSingleIntegerArgument< InternalSchemaMinLengthMatchExpression>(name, e); } case PathAcceptingKeyword::INTERNAL_SCHEMA_MAX_LENGTH: { return _parseInternalSchemaSingleIntegerArgument< InternalSchemaMaxLengthMatchExpression>(name, e); } case PathAcceptingKeyword::INTERNAL_SCHEMA_MATCH_ARRAY_INDEX: { return _parseInternalSchemaMatchArrayIndex(name, e, collator); } case PathAcceptingKeyword::INTERNAL_SCHEMA_ALL_ELEM_MATCH_FROM_INDEX: { if (e.type() != BSONType::Array) { return Status(ErrorCodes::FailedToParse, str::stream() << InternalSchemaAllElemMatchFromIndexMatchExpression::kName << " must be an array"); } auto elemMatchObj = e.embeddedObject(); auto iter = elemMatchObj.begin(); if (!iter.more()) { return Status(ErrorCodes::FailedToParse, str::stream() << InternalSchemaAllElemMatchFromIndexMatchExpression::kName << " must be an array of size 2"); } auto first = iter.next(); auto parsedIndex = parseIntegerElementToNonNegativeLong(first); if (!parsedIndex.isOK()) { return Status(ErrorCodes::TypeMismatch, str::stream() << "first element of " << InternalSchemaAllElemMatchFromIndexMatchExpression::kName << " must be a non-negative integer"); } if (!iter.more()) { return Status(ErrorCodes::FailedToParse, str::stream() << InternalSchemaAllElemMatchFromIndexMatchExpression::kName << " must be an array of size 2"); } auto second = iter.next(); if (iter.more()) { return Status(ErrorCodes::FailedToParse, str::stream() << InternalSchemaAllElemMatchFromIndexMatchExpression::kName << " has too many elements, must be an array of size 2"); } if (second.type() != BSONType::Object) { return Status(ErrorCodes::TypeMismatch, str::stream() << "second element of " << InternalSchemaAllElemMatchFromIndexMatchExpression::kName << "must be an object"); } StatusWithMatchExpression query = _parse(second.embeddedObject(), collator, expCtx, topLevel); if (!query.isOK()) { return query.getStatus(); } auto expr = stdx::make_unique(); auto status = expr->init(name, parsedIndex.getValue(), std::move(query.getValue())); if (!status.isOK()) { return status; } return {std::move(expr)}; } } return {Status(ErrorCodes::BadValue, mongoutils::str::stream() << "not handled: " << e.fieldName())}; } StatusWithMatchExpression MatchExpressionParser::_parse( const BSONObj& obj, const CollatorInterface* collator, const boost::intrusive_ptr& expCtx, bool topLevel) { std::unique_ptr root = stdx::make_unique(); const bool childIsTopLevel = false; 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 {Status(ErrorCodes::BadValue, "$or must be an array")}; std::unique_ptr temp = stdx::make_unique(); Status s = _parseTreeList(e.Obj(), temp.get(), collator, expCtx, childIsTopLevel); if (!s.isOK()) return s; root->add(temp.release()); } else if (mongoutils::str::equals("and", rest)) { if (e.type() != Array) return {Status(ErrorCodes::BadValue, "$and must be an array")}; std::unique_ptr temp = stdx::make_unique(); Status s = _parseTreeList(e.Obj(), temp.get(), collator, expCtx, childIsTopLevel); if (!s.isOK()) return s; root->add(temp.release()); } else if (mongoutils::str::equals("nor", rest)) { if (e.type() != Array) return {Status(ErrorCodes::BadValue, "$nor must be an array")}; std::unique_ptr temp = stdx::make_unique(); Status s = _parseTreeList(e.Obj(), temp.get(), collator, expCtx, childIsTopLevel); if (!s.isOK()) return s; root->add(temp.release()); } else if (mongoutils::str::equals("atomic", rest) || mongoutils::str::equals("isolated", rest)) { if (!topLevel) return {Status(ErrorCodes::BadValue, "$atomic/$isolated has to be at the top level")}; // Don't do anything with the expression; CanonicalQuery::init() will look through // the BSONObj again for a $atomic/$isolated. } else if (mongoutils::str::equals("where", rest)) { StatusWithMatchExpression s = _extensionsCallback->parseWhere(e); if (!s.isOK()) return s; root->add(s.getValue().release()); } else if (mongoutils::str::equals("text", rest)) { StatusWithMatchExpression s = _extensionsCallback->parseText(e); if (!s.isOK()) { return s; } root->add(s.getValue().release()); } 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 eq = stdx::make_unique(); Status s = eq->init(e.fieldName(), e); if (!s.isOK()) return s; // 'id' is collation-aware. 'ref' and 'db' are compared using binary comparison. eq->setCollator(str::equals("id", rest) ? collator : nullptr); root->add(eq.release()); } else if (mongoutils::str::equals("_internalSchemaCond", rest)) { auto condExpr = _parseInternalSchemaFixedArityArgument( InternalSchemaCondMatchExpression::kName, e, collator, expCtx); if (!condExpr.isOK()) { return condExpr.getStatus(); } root->add(condExpr.getValue().release()); } else if (mongoutils::str::equals("_internalSchemaXor", rest)) { if (e.type() != BSONType::Array) return { Status(ErrorCodes::TypeMismatch, "$_internalSchemaXor must be an array")}; auto xorExpr = stdx::make_unique(); Status s = _parseTreeList(e.Obj(), xorExpr.get(), collator, expCtx, childIsTopLevel); if (!s.isOK()) return s; root->add(xorExpr.release()); } else if (mongoutils::str::equals("_internalSchemaMinProperties", rest)) { return _parseTopLevelInternalSchemaSingleIntegerArgument< InternalSchemaMinPropertiesMatchExpression>(e); } else if (mongoutils::str::equals("_internalSchemaMaxProperties", rest)) { return _parseTopLevelInternalSchemaSingleIntegerArgument< InternalSchemaMaxPropertiesMatchExpression>(e); } else if (mongoutils::str::equals("jsonSchema", rest)) { if (e.type() != BSONType::Object) { return {Status(ErrorCodes::TypeMismatch, "$jsonSchema must be an object")}; } return JSONSchemaParser::parse(e.Obj()); } else if (mongoutils::str::equals("alwaysFalse", rest)) { auto statusWithLong = MatchExpressionParser::parseIntegerElementToLong(e); if (!statusWithLong.isOK()) { return statusWithLong.getStatus(); } if (statusWithLong.getValue() != 1) { return {Status(ErrorCodes::FailedToParse, "$alwaysFalse must be an integer value of 1")}; } return {stdx::make_unique()}; } else if (mongoutils::str::equals("alwaysTrue", rest)) { auto statusWithLong = MatchExpressionParser::parseIntegerElementToLong(e); if (!statusWithLong.isOK()) { return statusWithLong.getStatus(); } if (statusWithLong.getValue() != 1) { return {Status(ErrorCodes::FailedToParse, "$alwaysTrue must be an integer value of 1")}; } return {stdx::make_unique()}; } else { return {Status(ErrorCodes::BadValue, mongoutils::str::stream() << "unknown top level operator: " << e.fieldName())}; } continue; } if (_isExpressionDocument(e, false, expCtx)) { Status s = _parseSub(e.fieldName(), e.Obj(), root.get(), collator, expCtx, childIsTopLevel); if (!s.isOK()) return s; continue; } if (e.type() == RegEx) { StatusWithMatchExpression result = _parseRegexElement(e.fieldName(), e, expCtx); if (!result.isOK()) return result; root->add(result.getValue().release()); continue; } auto eq = _parseComparison(e.fieldName(), new EqualityMatchExpression(), e, collator, expCtx); if (!eq.isOK()) return eq; root->add(eq.getValue().release()); } if (root->numChildren() == 1) { std::unique_ptr real(root->getChild(0)); root->clearAndRelease(); return {std::move(real)}; } return {std::move(root)}; } Status MatchExpressionParser::_parseSub(const char* name, const BSONObj& sub, AndMatchExpression* root, const CollatorInterface* collator, const boost::intrusive_ptr& expCtx, bool topLevel) { // 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. // Special case parsing for geoNear. This is necessary in order to support query formats like // {$near: , $maxDistance: }. No other query operators allow $-prefixed // modifiers as sibling BSON elements. 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")) { StatusWithMatchExpression s = _parseGeo(name, *MatchExpressionParser::parsePathAcceptingKeyword( firstElt, PathAcceptingKeyword::EQUALITY), sub); if (s.isOK()) { root->add(s.getValue().release()); } // Propagate geo parsing result to caller. return s.getStatus(); } } } BSONObjIterator j(sub); while (j.more()) { BSONElement deep = j.next(); const bool childIsTopLevel = false; StatusWithMatchExpression s = _parseSubField(sub, root, name, deep, collator, expCtx, childIsTopLevel); if (!s.isOK()) return s.getStatus(); if (s.getValue()) root->add(s.getValue().release()); } return Status::OK(); } bool MatchExpressionParser::_isExpressionDocument( const BSONElement& e, bool allowIncompleteDBRef, const boost::intrusive_ptr& expCtx) { if (e.type() != Object) return false; BSONObj o = e.Obj(); if (o.isEmpty()) return false; const char* name = o.firstElement().fieldName(); if (name[0] != '$') return false; if (_isDBRefDocument(o, allowIncompleteDBRef)) { return false; } if (_isAggExpression(e, expCtx)) { return false; } return true; } /** * 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; } } if (allowIncompleteDBRef) { return hasRef || hasID || hasDB; } return hasRef && hasID; } StatusWithMatchExpression MatchExpressionParser::_parseMOD( const char* name, const BSONElement& e, const boost::intrusive_ptr& expCtx) { if (e.type() != Array) return {Status(ErrorCodes::BadValue, "malformed mod, needs to be an array")}; BSONObjIterator i(e.Obj()); if (!i.more()) return {Status(ErrorCodes::BadValue, "malformed mod, not enough elements")}; BSONElement d = i.next(); if (!d.isNumber()) return {Status(ErrorCodes::BadValue, "malformed mod, divisor not a number")}; if (!i.more()) return {Status(ErrorCodes::BadValue, "malformed mod, not enough elements")}; BSONElement r = i.next(); if (!d.isNumber()) return {Status(ErrorCodes::BadValue, "malformed mod, remainder not a number")}; if (i.more()) return {Status(ErrorCodes::BadValue, "malformed mod, too many elements")}; std::unique_ptr temp = stdx::make_unique(); Status s = temp->init(name, d.numberInt(), r.numberInt()); if (!s.isOK()) return s; return {std::move(temp)}; } StatusWithMatchExpression MatchExpressionParser::_parseRegexElement( const char* name, const BSONElement& e, const boost::intrusive_ptr& expCtx) { if (e.type() != RegEx) return {Status(ErrorCodes::BadValue, "not a regex")}; std::unique_ptr temp = stdx::make_unique(); Status s = temp->init(name, e.regex(), e.regexFlags()); if (!s.isOK()) return s; return {std::move(temp)}; } StatusWithMatchExpression MatchExpressionParser::_parseRegexDocument( const char* name, const BSONObj& doc, const boost::intrusive_ptr& expCtx) { string regex; string regexOptions; BSONObjIterator i(doc); while (i.more()) { BSONElement e = i.next(); auto matchType = MatchExpressionParser::parsePathAcceptingKeyword(e); if (!matchType) { continue; } switch (*matchType) { case PathAcceptingKeyword::REGEX: if (e.type() == String) { regex = e.String(); } else if (e.type() == RegEx) { regex = e.regex(); regexOptions = e.regexFlags(); } else { return {Status(ErrorCodes::BadValue, "$regex has to be a string")}; } break; case PathAcceptingKeyword::OPTIONS: if (e.type() != String) return {Status(ErrorCodes::BadValue, "$options has to be a string")}; regexOptions = e.String(); break; default: break; } } std::unique_ptr temp = stdx::make_unique(); Status s = temp->init(name, regex, regexOptions); if (!s.isOK()) return s; return {std::move(temp)}; } Status MatchExpressionParser::_parseInExpression( InMatchExpression* inExpression, const BSONObj& theArray, const CollatorInterface* collator, const boost::intrusive_ptr& expCtx) { inExpression->setCollator(collator); std::vector equalities; BSONObjIterator i(theArray); while (i.more()) { BSONElement e = i.next(); // Allow DBRefs, but reject all fields with names starting with $. if (_isExpressionDocument(e, false, expCtx)) { return Status(ErrorCodes::BadValue, "cannot nest $ under $in"); } if (e.type() == RegEx) { std::unique_ptr r = stdx::make_unique(); Status s = r->init("", e); if (!s.isOK()) return s; s = inExpression->addRegex(std::move(r)); if (!s.isOK()) return s; } else { if (_isAggExpression(e, expCtx)) { return Status(ErrorCodes::BadValue, "$expr not supported for $in"); } equalities.push_back(e); } } return inExpression->setEqualities(std::move(equalities)); } StatusWith> MatchExpressionParser::parseTypeFromAlias( StringData path, StringData typeAlias) { auto typeExpr = stdx::make_unique(); TypeMatchExpression::Type type; if (typeAlias == TypeMatchExpression::kMatchesAllNumbersAlias) { type.allNumbers = true; Status status = typeExpr->init(path, type); if (!status.isOK()) { return status; } return {std::move(typeExpr)}; } auto it = TypeMatchExpression::typeAliasMap.find(typeAlias.toString()); if (it == TypeMatchExpression::typeAliasMap.end()) { return Status(ErrorCodes::BadValue, str::stream() << "Unknown string alias for $type: " << typeAlias); } type.bsonType = it->second; Status status = typeExpr->init(path, type); if (!status.isOK()) { return status; } return {std::move(typeExpr)}; } StatusWithMatchExpression MatchExpressionParser::_parseType( const char* name, const BSONElement& elt, const boost::intrusive_ptr& expCtx) { if (!elt.isNumber() && elt.type() != BSONType::String) { return Status(ErrorCodes::TypeMismatch, "argument to $type is not a number or a string"); } if (elt.type() == BSONType::String) { auto typeExpr = parseTypeFromAlias(name, elt.valueStringData()); if (!typeExpr.isOK()) { return typeExpr.getStatus(); } return {std::move(typeExpr.getValue())}; } invariant(elt.isNumber()); int typeInt = elt.numberInt(); if (elt.type() != BSONType::NumberInt && typeInt != elt.number()) { typeInt = -1; } if (!isValidBSONType(typeInt)) { return Status(ErrorCodes::BadValue, str::stream() << "Invalid numerical $type code: " << typeInt); } auto typeExpr = stdx::make_unique(); auto status = typeExpr->init(name, static_cast(typeInt)); if (!status.isOK()) { return status; } return {std::move(typeExpr)}; } StatusWithMatchExpression MatchExpressionParser::_parseElemMatch( const char* name, const BSONElement& e, const CollatorInterface* collator, const boost::intrusive_ptr& expCtx, bool topLevel) { if (e.type() != Object) return {Status(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, expCtx)) { 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("$_internalSchemaXor", elt.fieldName()) && !mongoutils::str::equals("$or", elt.fieldName()) && !mongoutils::str::equals("$where", elt.fieldName()) && !mongoutils::str::equals("$_internalSchemaMinProperties", elt.fieldName()) && !mongoutils::str::equals("$_internalSchemaMaxProperties", elt.fieldName()); } if (isElemMatchValue) { // value case AndMatchExpression theAnd; Status s = _parseSub("", obj, &theAnd, collator, expCtx, topLevel); if (!s.isOK()) return s; std::unique_ptr temp = stdx::make_unique(); s = temp->init(name); if (!s.isOK()) return s; for (size_t i = 0; i < theAnd.numChildren(); i++) { temp->add(theAnd.getChild(i)); } theAnd.clearAndRelease(); return {std::move(temp)}; } // 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 StatusWithMatchExpression subRaw = _parse(obj, collator, expCtx, topLevel); if (!subRaw.isOK()) return subRaw; std::unique_ptr sub = std::move(subRaw.getValue()); // $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 {Status(ErrorCodes::BadValue, "$elemMatch cannot contain $where expression")}; } std::unique_ptr temp = stdx::make_unique(); Status status = temp->init(name, sub.release()); if (!status.isOK()) return status; return {std::move(temp)}; } StatusWithMatchExpression MatchExpressionParser::_parseAll( const char* name, const BSONElement& e, const CollatorInterface* collator, const boost::intrusive_ptr& expCtx, bool topLevel) { if (e.type() != Array) return {Status(ErrorCodes::BadValue, "$all needs an array")}; BSONObj arr = e.Obj(); std::unique_ptr myAnd = stdx::make_unique(); 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 {Status(ErrorCodes::BadValue, "$all/$elemMatch has to be consistent")}; } BSONObj hopefullyElemMatchObj = hopefullyElemMatchElement.Obj(); if (!mongoutils::str::equals("$elemMatch", hopefullyElemMatchObj.firstElement().fieldName())) { // $all : [ { $elemMatch : ... }, { x : 5 } ] return {Status(ErrorCodes::BadValue, "$all/$elemMatch has to be consistent")}; } const bool childIsTopLevel = false; StatusWithMatchExpression inner = _parseElemMatch( name, hopefullyElemMatchObj.firstElement(), collator, expCtx, childIsTopLevel); if (!inner.isOK()) return inner; myAnd->add(inner.getValue().release()); } return {std::move(myAnd)}; } while (i.more()) { BSONElement e = i.next(); if (e.type() == RegEx) { std::unique_ptr r = stdx::make_unique(); Status s = r->init(name, e); if (!s.isOK()) return s; myAnd->add(r.release()); } else if (e.type() == Object && MatchExpressionParser::parsePathAcceptingKeyword(e.Obj().firstElement())) { return {Status(ErrorCodes::BadValue, "no $ expressions in $all")}; } else { std::unique_ptr x = stdx::make_unique(); Status s = x->init(name, e); if (!s.isOK()) return s; x->setCollator(collator); myAnd->add(x.release()); } } if (myAnd->numChildren() == 0) { return {stdx::make_unique()}; } return {std::move(myAnd)}; } template StatusWithMatchExpression MatchExpressionParser::_parseBitTest( const char* name, const BSONElement& e, const boost::intrusive_ptr& expCtx) { std::unique_ptr bitTestMatchExpression = stdx::make_unique(); if (e.type() == BSONType::Array) { // Array of bit positions provided as value. auto statusWithBitPositions = _parseBitPositionsArray(e.Obj()); if (!statusWithBitPositions.isOK()) { return statusWithBitPositions.getStatus(); } std::vector bitPositions = statusWithBitPositions.getValue(); Status s = bitTestMatchExpression->init(name, bitPositions); if (!s.isOK()) { return s; } } else if (e.isNumber()) { // Integer bitmask provided as value. auto bitMask = parseIntegerElementToNonNegativeLong(e); if (!bitMask.isOK()) { return bitMask.getStatus(); } Status s = bitTestMatchExpression->init(name, bitMask.getValue()); if (!s.isOK()) { return s; } } else if (e.type() == BSONType::BinData) { // Binary bitmask provided as value. int eBinaryLen; const char* eBinary = e.binData(eBinaryLen); Status s = bitTestMatchExpression->init(name, eBinary, eBinaryLen); if (!s.isOK()) { return s; } } else { mongoutils::str::stream ss; ss << name << " takes an Array, a number, or a BinData but received: " << e; return Status(ErrorCodes::BadValue, ss); } return {std::move(bitTestMatchExpression)}; } StatusWith> MatchExpressionParser::_parseBitPositionsArray( const BSONObj& theArray) { std::vector bitPositions; // Fill temporary bit position array with integers read from the BSON array. for (const BSONElement& e : theArray) { if (!e.isNumber()) { mongoutils::str::stream ss; ss << "bit positions must be an integer but got: " << e; return Status(ErrorCodes::BadValue, ss); } if (e.type() == BSONType::NumberDouble) { double eDouble = e.numberDouble(); // NaN doubles are rejected. if (std::isnan(eDouble)) { mongoutils::str::stream ss; ss << "bit positions cannot take a NaN: " << e; return Status(ErrorCodes::BadValue, ss); } // This makes sure e does not overflow a 32-bit integer container. if (eDouble > std::numeric_limits::max() || eDouble < std::numeric_limits::min()) { mongoutils::str::stream ss; ss << "bit positions cannot be represented as a 32-bit signed integer: " << e; return Status(ErrorCodes::BadValue, ss); } // This checks if e is integral. if (eDouble != static_cast(static_cast(eDouble))) { mongoutils::str::stream ss; ss << "bit positions must be an integer but got: " << e; return Status(ErrorCodes::BadValue, ss); } } if (e.type() == BSONType::NumberLong) { long long eLong = e.numberLong(); // This makes sure e does not overflow a 32-bit integer container. if (eLong > std::numeric_limits::max() || eLong < std::numeric_limits::min()) { mongoutils::str::stream ss; ss << "bit positions cannot be represented as a 32-bit signed integer: " << e; return Status(ErrorCodes::BadValue, ss); } } int eValue = e.numberInt(); // No negatives. if (eValue < 0) { mongoutils::str::stream ss; ss << "bit positions must be >= 0 but got: " << e; return Status(ErrorCodes::BadValue, ss); } bitPositions.push_back(eValue); } return bitPositions; } StatusWith MatchExpressionParser::parseIntegerElementToNonNegativeLong( BSONElement elem) { auto number = parseIntegerElementToLong(elem); if (!number.isOK()) { return number; } if (number.getValue() < 0) { return Status(ErrorCodes::FailedToParse, str::stream() << "Expected a positive number in: " << elem); } return number; } StatusWith MatchExpressionParser::parseIntegerElementToLong(BSONElement elem) { if (!elem.isNumber()) { return Status(ErrorCodes::FailedToParse, str::stream() << "Expected a number in: " << elem); } long long number = 0; if (elem.type() == BSONType::NumberDouble) { double eDouble = elem.numberDouble(); // NaN doubles are rejected. if (std::isnan(eDouble)) { return Status(ErrorCodes::FailedToParse, str::stream() << "Expected an integer, but found NaN in: " << elem); } // No integral doubles that are too large to be represented as a 64 bit signed integer. // We use 'kLongLongMaxAsDouble' because if we just did eDouble > 2^63-1, it would be // compared against 2^63. eDouble=2^63 would not get caught that way. if (eDouble >= MatchExpressionParser::kLongLongMaxPlusOneAsDouble || eDouble < std::numeric_limits::min()) { return Status(ErrorCodes::FailedToParse, str::stream() << "Cannot represent as a 64-bit integer: " << elem); } // This checks if elem is an integral double. if (eDouble != static_cast(static_cast(eDouble))) { return Status(ErrorCodes::FailedToParse, str::stream() << "Expected an integer: " << elem); } number = elem.numberLong(); } else if (elem.type() == BSONType::NumberDecimal) { uint32_t signalingFlags = Decimal128::kNoFlag; number = elem.numberDecimal().toLongExact(&signalingFlags); if (signalingFlags != Decimal128::kNoFlag) { return Status(ErrorCodes::FailedToParse, str::stream() << "Cannot represent as a 64-bit integer: " << elem); } } else { number = elem.numberLong(); } return number; } StatusWithMatchExpression MatchExpressionParser::_parseInternalSchemaFmod(const char* name, const BSONElement& elem) { StringData path(name); if (elem.type() != Array) return {ErrorCodes::BadValue, str::stream() << path << " must be an array, but got type " << elem.type()}; BSONObjIterator i(elem.embeddedObject()); if (!i.more()) return {ErrorCodes::BadValue, str::stream() << path << " does not have enough elements"}; BSONElement d = i.next(); if (!d.isNumber()) return {ErrorCodes::TypeMismatch, str::stream() << path << " does not have a numeric divisor"}; if (!i.more()) return {ErrorCodes::BadValue, str::stream() << path << " does not have enough elements"}; BSONElement r = i.next(); if (!d.isNumber()) return {ErrorCodes::TypeMismatch, str::stream() << path << " does not have a numeric remainder"}; if (i.more()) return {ErrorCodes::BadValue, str::stream() << path << " has too many elements"}; std::unique_ptr result = stdx::make_unique(); Status s = result->init(name, d.numberDecimal(), r.numberDecimal()); if (!s.isOK()) return s; return {std::move(result)}; } template StatusWithMatchExpression MatchExpressionParser::_parseInternalSchemaFixedArityArgument( StringData name, const BSONElement& input, const CollatorInterface* collator, const boost::intrusive_ptr& expCtx) { constexpr auto arity = T::arity(); if (input.type() != BSONType::Array) { return {ErrorCodes::FailedToParse, str::stream() << name << " must be an array of " << arity << " MatchExpressions"}; } auto inputObj = input.embeddedObject(); if (static_cast(inputObj.nFields()) != arity) { return {ErrorCodes::FailedToParse, str::stream() << name << " requires exactly " << arity << " MatchExpressions, but got " << inputObj.nFields()}; } // Fill out 'expressions' with all of the parsed subexpressions contained in the array, tracking // our location in the array with 'position'. std::array, arity> expressions; auto position = expressions.begin(); for (const auto& elem : inputObj) { if (elem.type() != BSONType::Object) { return {ErrorCodes::FailedToParse, str::stream() << name << " must be an array of objects, but found an element of type " << elem.type()}; } const bool isTopLevel = false; auto subexpr = _parse(elem.embeddedObject(), collator, expCtx, isTopLevel); if (!subexpr.isOK()) { return subexpr.getStatus(); } *position = std::move(subexpr.getValue()); ++position; } auto parsedExpression = stdx::make_unique(); parsedExpression->init(std::move(expressions)); return {std::move(parsedExpression)}; } template StatusWithMatchExpression MatchExpressionParser::_parseInternalSchemaSingleIntegerArgument( const char* name, const BSONElement& elem) const { auto parsedInt = parseIntegerElementToNonNegativeLong(elem); if (!parsedInt.isOK()) { return parsedInt.getStatus(); } auto matchExpression = stdx::make_unique(); auto status = matchExpression->init(name, parsedInt.getValue()); if (!status.isOK()) { return status; } return {std::move(matchExpression)}; } template StatusWithMatchExpression MatchExpressionParser::_parseTopLevelInternalSchemaSingleIntegerArgument( const BSONElement& elem) const { auto parsedInt = parseIntegerElementToNonNegativeLong(elem); if (!parsedInt.isOK()) { return parsedInt.getStatus(); } auto matchExpression = stdx::make_unique(); auto status = matchExpression->init(parsedInt.getValue()); if (!status.isOK()) { return status; } return {std::move(matchExpression)}; } StatusWithMatchExpression MatchExpressionParser::_parseInternalSchemaMatchArrayIndex( const char* path, const BSONElement& elem, const CollatorInterface* collator) { if (elem.type() != BSONType::Object) { return {ErrorCodes::TypeMismatch, str::stream() << InternalSchemaMatchArrayIndexMatchExpression::kName << " must be an object"}; } auto subobj = elem.embeddedObject(); if (subobj.nFields() != 3) { return {ErrorCodes::FailedToParse, str::stream() << InternalSchemaMatchArrayIndexMatchExpression::kName << " requires exactly three fields: 'index', " "'namePlaceholder' and 'expression'"}; } auto index = parseIntegerElementToNonNegativeLong(subobj["index"]); if (!index.isOK()) { return index.getStatus(); } auto namePlaceholderElem = subobj["namePlaceholder"]; if (!namePlaceholderElem) { return {ErrorCodes::FailedToParse, str::stream() << InternalSchemaMatchArrayIndexMatchExpression::kName << " requires a 'namePlaceholder'"}; } else if (namePlaceholderElem.type() != BSONType::String) { return {ErrorCodes::TypeMismatch, str::stream() << InternalSchemaMatchArrayIndexMatchExpression::kName << " requires 'namePlaceholder' to be a string, not " << namePlaceholderElem.type()}; } auto expressionElem = subobj["expression"]; if (!expressionElem) { return {ErrorCodes::FailedToParse, str::stream() << InternalSchemaMatchArrayIndexMatchExpression::kName << " requires an 'expression'"}; } else if (expressionElem.type() != BSONType::Object) { return {ErrorCodes::TypeMismatch, str::stream() << InternalSchemaMatchArrayIndexMatchExpression::kName << " requires 'expression' to be an object, not " << expressionElem.type()}; } auto expressionWithPlaceholder = ExpressionWithPlaceholder::parse(expressionElem.embeddedObject(), collator); if (!expressionWithPlaceholder.isOK()) { return expressionWithPlaceholder.getStatus(); } if (namePlaceholderElem.valueStringData() != expressionWithPlaceholder.getValue()->getPlaceholder()) { return {ErrorCodes::FailedToParse, str::stream() << InternalSchemaMatchArrayIndexMatchExpression::kName << " has a 'namePlaceholder' of '" << namePlaceholderElem.valueStringData() << "', but 'expression' has a mismatching placeholder '" << expressionWithPlaceholder.getValue()->getPlaceholder() << "'"}; } auto matchArrayIndexExpr = stdx::make_unique(); auto initStatus = matchArrayIndexExpr->init( path, index.getValue(), std::move(expressionWithPlaceholder.getValue())); if (!initStatus.isOK()) { return initStatus; } return {std::move(matchArrayIndexExpr)}; } StatusWithMatchExpression MatchExpressionParser::_parseGeo(const char* name, PathAcceptingKeyword type, const BSONObj& section) { if (PathAcceptingKeyword::WITHIN == type || PathAcceptingKeyword::GEO_INTERSECTS == type) { std::unique_ptr gq = stdx::make_unique(name); Status parseStatus = gq->parseFrom(section); if (!parseStatus.isOK()) return StatusWithMatchExpression(parseStatus); std::unique_ptr e = stdx::make_unique(); Status s = e->init(name, gq.release(), section); if (!s.isOK()) return StatusWithMatchExpression(s); return {std::move(e)}; } else { invariant(PathAcceptingKeyword::GEO_NEAR == type); std::unique_ptr nq = stdx::make_unique(name); Status s = nq->parseFrom(section); if (!s.isOK()) { return StatusWithMatchExpression(s); } std::unique_ptr e = stdx::make_unique(); s = e->init(name, nq.release(), section); if (!s.isOK()) return StatusWithMatchExpression(s); return {std::move(e)}; } } bool MatchExpressionParser::_isAggExpression( BSONElement elem, const boost::intrusive_ptr& expCtx) { if (!expCtx) { return false; } if (BSONType::Object != elem.type()) { return false; } auto obj = elem.embeddedObject(); if (obj.nFields() != 1) { return false; } return obj.firstElementFieldName() == kAggExpression; } boost::intrusive_ptr MatchExpressionParser::_parseAggExpression( BSONElement elem, const boost::intrusive_ptr& expCtx) { invariant(expCtx); auto expr = Expression::parseOperand( expCtx, elem.embeddedObject().firstElement(), expCtx->variablesParseState); return expr->optimize(); } namespace { // Maps from query operator string name to operator PathAcceptingKeyword. std::unique_ptr> queryOperatorMap; MONGO_INITIALIZER(MatchExpressionParser)(InitializerContext* context) { queryOperatorMap = stdx::make_unique>(StringMap{ // TODO: SERVER-19565 Add $eq after auditing callers. {"lt", PathAcceptingKeyword::LESS_THAN}, {"lte", PathAcceptingKeyword::LESS_THAN_OR_EQUAL}, {"gte", PathAcceptingKeyword::GREATER_THAN_OR_EQUAL}, {"gt", PathAcceptingKeyword::GREATER_THAN}, {"in", PathAcceptingKeyword::IN_EXPR}, {"ne", PathAcceptingKeyword::NOT_EQUAL}, {"size", PathAcceptingKeyword::SIZE}, {"all", PathAcceptingKeyword::ALL}, {"nin", PathAcceptingKeyword::NOT_IN}, {"exists", PathAcceptingKeyword::EXISTS}, {"mod", PathAcceptingKeyword::MOD}, {"type", PathAcceptingKeyword::TYPE}, {"regex", PathAcceptingKeyword::REGEX}, {"options", PathAcceptingKeyword::OPTIONS}, {"elemMatch", PathAcceptingKeyword::ELEM_MATCH}, {"near", PathAcceptingKeyword::GEO_NEAR}, {"nearSphere", PathAcceptingKeyword::GEO_NEAR}, {"geoNear", PathAcceptingKeyword::GEO_NEAR}, {"within", PathAcceptingKeyword::WITHIN}, {"geoWithin", PathAcceptingKeyword::WITHIN}, {"geoIntersects", PathAcceptingKeyword::GEO_INTERSECTS}, {"bitsAllSet", PathAcceptingKeyword::BITS_ALL_SET}, {"bitsAllClear", PathAcceptingKeyword::BITS_ALL_CLEAR}, {"bitsAnySet", PathAcceptingKeyword::BITS_ANY_SET}, {"bitsAnyClear", PathAcceptingKeyword::BITS_ANY_CLEAR}, {"_internalSchemaAllElemMatchFromIndex", PathAcceptingKeyword::INTERNAL_SCHEMA_ALL_ELEM_MATCH_FROM_INDEX}, {"_internalSchemaFmod", PathAcceptingKeyword::INTERNAL_SCHEMA_FMOD}, {"_internalSchemaMinItems", PathAcceptingKeyword::INTERNAL_SCHEMA_MIN_ITEMS}, {"_internalSchemaMaxItems", PathAcceptingKeyword::INTERNAL_SCHEMA_MAX_ITEMS}, {"_internalSchemaUniqueItems", PathAcceptingKeyword::INTERNAL_SCHEMA_UNIQUE_ITEMS}, {"_internalSchemaObjectMatch", PathAcceptingKeyword::INTERNAL_SCHEMA_OBJECT_MATCH}, {"_internalSchemaMinLength", PathAcceptingKeyword::INTERNAL_SCHEMA_MIN_LENGTH}, {"_internalSchemaMaxLength", PathAcceptingKeyword::INTERNAL_SCHEMA_MAX_LENGTH}, {"_internalSchemaMatchArrayIndex", PathAcceptingKeyword::INTERNAL_SCHEMA_MATCH_ARRAY_INDEX}}); return Status::OK(); } } // anonymous namespace boost::optional MatchExpressionParser::parsePathAcceptingKeyword( BSONElement typeElem, boost::optional defaultKeyword) { auto fieldName = typeElem.fieldName(); if (fieldName[0] == '$' && fieldName[1]) { auto opName = typeElem.fieldNameStringData().substr(1); auto queryOp = queryOperatorMap->find(opName); if (queryOp == queryOperatorMap->end()) { return defaultKeyword; } return queryOp->second; } return defaultKeyword; } } // namespace mongo