diff options
author | James Wahlin <james@mongodb.com> | 2017-06-29 13:41:44 -0400 |
---|---|---|
committer | James Wahlin <james@mongodb.com> | 2017-07-21 11:18:54 -0400 |
commit | a7164c0527ac1f231d12a889bf6d16b264af338e (patch) | |
tree | 47994590d41924ca1c6cff09f6f1aafe25e8792c /src/mongo | |
parent | e6032315026a446d1c8eef647b0bd6f2e82f2edc (diff) | |
download | mongo-a7164c0527ac1f231d12a889bf6d16b264af338e.tar.gz |
SERVER-29814 Move BSONObj::MatchType/BSONElement::getGtLtOp() to matcher
Diffstat (limited to 'src/mongo')
-rw-r--r-- | src/mongo/bson/bsonelement.cpp | 50 | ||||
-rw-r--r-- | src/mongo/bson/bsonelement.h | 3 | ||||
-rw-r--r-- | src/mongo/bson/bsonmisc.cpp | 8 | ||||
-rw-r--r-- | src/mongo/bson/bsonmisc.h | 2 | ||||
-rw-r--r-- | src/mongo/bson/bsonobj.h | 32 | ||||
-rw-r--r-- | src/mongo/db/fts/SConscript | 65 | ||||
-rw-r--r-- | src/mongo/db/fts/fts_spec.cpp | 4 | ||||
-rw-r--r-- | src/mongo/db/index/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/matcher/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_geo.cpp | 12 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_parser.cpp | 171 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_parser.h | 44 | ||||
-rw-r--r-- | src/mongo/db/matcher/path_accepting_keyword_test.cpp | 140 | ||||
-rw-r--r-- | src/mongo/db/ops/modifier_pull.cpp | 4 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_match.cpp | 62 | ||||
-rw-r--r-- | src/mongo/db/update/pull_node.cpp | 3 |
16 files changed, 377 insertions, 225 deletions
diff --git a/src/mongo/bson/bsonelement.cpp b/src/mongo/bson/bsonelement.cpp index ecb1f932eef..de5d1e6d303 100644 --- a/src/mongo/bson/bsonelement.cpp +++ b/src/mongo/bson/bsonelement.cpp @@ -306,42 +306,6 @@ string BSONElement::jsonString(JsonStringFormat format, bool includeFieldNames, namespace { -// Map from query operator string name to operator MatchType. Used in BSONElement::getGtLtOp(). -const StringMap<BSONObj::MatchType> queryOperatorMap{ - // TODO: SERVER-19565 Add $eq after auditing callers. - {"lt", BSONObj::LT}, - {"lte", BSONObj::LTE}, - {"gte", BSONObj::GTE}, - {"gt", BSONObj::GT}, - {"in", BSONObj::opIN}, - {"ne", BSONObj::NE}, - {"size", BSONObj::opSIZE}, - {"all", BSONObj::opALL}, - {"nin", BSONObj::NIN}, - {"exists", BSONObj::opEXISTS}, - {"mod", BSONObj::opMOD}, - {"type", BSONObj::opTYPE}, - {"regex", BSONObj::opREGEX}, - {"options", BSONObj::opOPTIONS}, - {"elemMatch", BSONObj::opELEM_MATCH}, - {"near", BSONObj::opNEAR}, - {"nearSphere", BSONObj::opNEAR}, - {"geoNear", BSONObj::opNEAR}, - {"within", BSONObj::opWITHIN}, - {"geoWithin", BSONObj::opWITHIN}, - {"geoIntersects", BSONObj::opGEO_INTERSECTS}, - {"bitsAllSet", BSONObj::opBITS_ALL_SET}, - {"bitsAllClear", BSONObj::opBITS_ALL_CLEAR}, - {"bitsAnySet", BSONObj::opBITS_ANY_SET}, - {"bitsAnyClear", BSONObj::opBITS_ANY_CLEAR}, - {"_internalSchemaMinItems", BSONObj::opINTERNAL_SCHEMA_MIN_ITEMS}, - {"_internalSchemaMaxItems", BSONObj::opINTERNAL_SCHEMA_MAX_ITEMS}, - {"_internalSchemaUniqueItems", BSONObj::opINTERNAL_SCHEMA_UNIQUE_ITEMS}, - {"_internalSchemaObjectMatch", BSONObj::opINTERNAL_SCHEMA_OBJECT_MATCH}, - {"_internalSchemaMinLength", BSONObj::opINTERNAL_SCHEMA_MIN_LENGTH}, - {"_internalSchemaMaxLength", BSONObj::opINTERNAL_SCHEMA_MAX_LENGTH}, -}; - // Compares two string elements using a simple binary compare. int compareElementStringValues(const BSONElement& leftStr, const BSONElement& rightStr) { // we use memcmp as we allow zeros in UTF8 strings @@ -357,20 +321,6 @@ int compareElementStringValues(const BSONElement& leftStr, const BSONElement& ri } // namespace -int BSONElement::getGtLtOp(int def) const { - const char* fn = fieldName(); - if (fn[0] == '$' && fn[1]) { - StringData opName = fieldNameStringData().substr(1); - - StringMap<BSONObj::MatchType>::const_iterator queryOp = queryOperatorMap.find(opName); - if (queryOp == queryOperatorMap.end()) { - return def; - } - return queryOp->second; - } - return def; -} - /** transform a BSON array into a vector of BSONElements. we match array # positions with their vector position, and ignore any fields with non-numeric field names. diff --git a/src/mongo/bson/bsonelement.h b/src/mongo/bson/bsonelement.h index 060e9c2c491..add268f1a1d 100644 --- a/src/mongo/bson/bsonelement.h +++ b/src/mongo/bson/bsonelement.h @@ -557,9 +557,6 @@ public: return data; } - /** 0 == Equality, just not defined yet */ - int getGtLtOp(int def = 0) const; - /** Constructs an empty element */ BSONElement(); diff --git a/src/mongo/bson/bsonmisc.cpp b/src/mongo/bson/bsonmisc.cpp index 28e88690b29..4aab5b6265d 100644 --- a/src/mongo/bson/bsonmisc.cpp +++ b/src/mongo/bson/bsonmisc.cpp @@ -31,14 +31,6 @@ namespace mongo { -int getGtLtOp(const BSONElement& e) { - if (e.type() != Object) - return BSONObj::Equality; - - BSONElement fe = e.embeddedObject().firstElement(); - return fe.getGtLtOp(); -} - bool fieldsMatch(const BSONObj& lhs, const BSONObj& rhs) { BSONObjIterator l(lhs); BSONObjIterator r(rhs); diff --git a/src/mongo/bson/bsonmisc.h b/src/mongo/bson/bsonmisc.h index 2ca9d39c805..cd8e973687f 100644 --- a/src/mongo/bson/bsonmisc.h +++ b/src/mongo/bson/bsonmisc.h @@ -34,8 +34,6 @@ namespace mongo { -int getGtLtOp(const BSONElement& e); - class BSONElementCmpWithoutField { public: /** diff --git a/src/mongo/bson/bsonobj.h b/src/mongo/bson/bsonobj.h index 8a6e41f88f9..4344995b23c 100644 --- a/src/mongo/bson/bsonobj.h +++ b/src/mongo/bson/bsonobj.h @@ -512,38 +512,6 @@ public: */ bool valid(BSONVersion version) const; - enum MatchType { - Equality = 0, - LT = 0x1, - LTE = 0x3, - GTE = 0x6, - GT = 0x4, - opIN = 0x8, // { x : { $in : [1,2,3] } } - NE = 0x9, - opSIZE = 0x0A, - opALL = 0x0B, - NIN = 0x0C, - opEXISTS = 0x0D, - opMOD = 0x0E, - opTYPE = 0x0F, - opREGEX = 0x10, - opOPTIONS = 0x11, - opELEM_MATCH = 0x12, - opNEAR = 0x13, - opWITHIN = 0x14, - opGEO_INTERSECTS = 0x16, - opBITS_ALL_SET = 0x17, - opBITS_ALL_CLEAR = 0x18, - opBITS_ANY_SET = 0x19, - opBITS_ANY_CLEAR = 0x1A, - opINTERNAL_SCHEMA_MIN_ITEMS = 0x1B, - opINTERNAL_SCHEMA_MAX_ITEMS = 0x1C, - opINTERNAL_SCHEMA_UNIQUE_ITEMS = 0x1D, - opINTERNAL_SCHEMA_OBJECT_MATCH = 0x1E, - opINTERNAL_SCHEMA_MIN_LENGTH = 0x1F, - opINTERNAL_SCHEMA_MAX_LENGTH = 0x20, - }; - /** add all elements of the object to the specified vector */ void elems(std::vector<BSONElement>&) const; /** add all elements of the object to the specified list */ diff --git a/src/mongo/db/fts/SConscript b/src/mongo/db/fts/SConscript index 6111a897f0c..8b9a7c1c103 100644 --- a/src/mongo/db/fts/SConscript +++ b/src/mongo/db/fts/SConscript @@ -60,6 +60,7 @@ baseEnv.Library('base', [ "$BUILD_DIR/mongo/db/bson/dotted_path_support", "$BUILD_DIR/mongo/db/common", "$BUILD_DIR/mongo/db/fts/unicode/unicode", + "$BUILD_DIR/mongo/db/matcher/expressions", "$BUILD_DIR/mongo/util/md5", "$BUILD_DIR/third_party/shim_stemmer", ]) @@ -78,44 +79,26 @@ env.Library('ftsmongod', [ 'ftsmongod.cpp', ], LIBDEPS=["base","$BUILD_DIR/mongo/base"]) -env.CppUnitTest( "fts_basic_phrase_matcher_test", "fts_basic_phrase_matcher_test.cpp", - LIBDEPS=["base"] ) - -env.CppUnitTest( "fts_basic_tokenizer_test", "fts_basic_tokenizer_test.cpp", - LIBDEPS=["base"] ) - -env.CppUnitTest( "fts_element_iterator_test", "fts_element_iterator_test.cpp", - LIBDEPS=["base"] ) - -env.CppUnitTest( "fts_index_format_test", "fts_index_format_test.cpp", - LIBDEPS=["base"] ) - -env.CppUnitTest( "fts_language_test", "fts_language_test.cpp", - LIBDEPS=["base"] ) - -env.CppUnitTest( "fts_matcher_test", "fts_matcher_test.cpp", - LIBDEPS=["base"] ) - -env.CppUnitTest( "fts_query_impl_test", "fts_query_impl_test.cpp", - LIBDEPS=["base"] ) - -env.CppUnitTest( "fts_query_noop_test", "fts_query_noop_test.cpp", - LIBDEPS=["fts_query_noop"] ) - -env.CppUnitTest( "fts_spec_test", "fts_spec_test.cpp", - LIBDEPS=["base"] ) - -env.CppUnitTest( "fts_stemmer_test", "stemmer_test.cpp", - LIBDEPS=["base"] ) - -env.CppUnitTest( "fts_stop_words_test", "stop_words_test.cpp", - LIBDEPS=["base"] ) - -env.CppUnitTest( "fts_tokenizer_test", "tokenizer_test.cpp", - LIBDEPS=["base"] ) - -env.CppUnitTest( "fts_unicode_phrase_matcher_test", "fts_unicode_phrase_matcher_test.cpp", - LIBDEPS=["base"] ) - -env.CppUnitTest( "fts_unicode_tokenizer_test", "fts_unicode_tokenizer_test.cpp", - LIBDEPS=["base"] ) +env.CppUnitTest(target='fts_test', + source=[ + "fts_basic_phrase_matcher_test.cpp", + "fts_basic_tokenizer_test.cpp", + "fts_element_iterator_test.cpp", + "fts_index_format_test.cpp", + "fts_language_test.cpp", + "fts_matcher_test.cpp", + "fts_query_impl_test.cpp", + "fts_query_noop_test.cpp", + "fts_spec_test.cpp", + "fts_unicode_phrase_matcher_test.cpp", + "fts_unicode_tokenizer_test.cpp", + "stemmer_test.cpp", + "stop_words_test.cpp", + "tokenizer_test.cpp", + ], + LIBDEPS=[ + "$BUILD_DIR/mongo/db/fts/fts_query_noop", + "$BUILD_DIR/mongo/db/matcher/expressions", + "base", + ], +)
\ No newline at end of file diff --git a/src/mongo/db/fts/fts_spec.cpp b/src/mongo/db/fts/fts_spec.cpp index c9216b0bb45..9022b72bb0b 100644 --- a/src/mongo/db/fts/fts_spec.cpp +++ b/src/mongo/db/fts/fts_spec.cpp @@ -36,6 +36,7 @@ #include "mongo/db/fts/fts_element_iterator.h" #include "mongo/db/fts/fts_tokenizer.h" #include "mongo/db/fts/fts_util.h" +#include "mongo/db/matcher/expression_parser.h" #include "mongo/util/mongoutils/str.h" #include "mongo/util/stringutils.h" @@ -249,7 +250,8 @@ Status FTSSpec::getIndexPrefix(const BSONObj& query, BSONObj* out) const { return Status(ErrorCodes::BadValue, str::stream() << "need have an equality filter on: " << extraBefore(i)); - if (e.isABSONObj() && e.Obj().firstElement().getGtLtOp(-1) != -1) + if (e.isABSONObj() && + MatchExpressionParser::parsePathAcceptingKeyword(e.Obj().firstElement())) return Status(ErrorCodes::BadValue, str::stream() << "need have an equality filter on: " << extraBefore(i)); diff --git a/src/mongo/db/index/SConscript b/src/mongo/db/index/SConscript index 92edf813f18..63c69e1763d 100644 --- a/src/mongo/db/index/SConscript +++ b/src/mongo/db/index/SConscript @@ -73,6 +73,7 @@ env.CppUnitTest( ], LIBDEPS=[ 'key_generator', + "$BUILD_DIR/mongo/db/matcher/expressions", '$BUILD_DIR/mongo/db/mongohasher', '$BUILD_DIR/mongo/db/query/collation/collator_interface_mock', ], diff --git a/src/mongo/db/matcher/SConscript b/src/mongo/db/matcher/SConscript index 9585af33806..47868a55921 100644 --- a/src/mongo/db/matcher/SConscript +++ b/src/mongo/db/matcher/SConscript @@ -75,6 +75,7 @@ env.CppUnitTest( 'expression_parser_geo_test.cpp', 'expression_test.cpp', 'expression_tree_test.cpp', + 'path_accepting_keyword_test.cpp', 'schema/expression_internal_schema_max_items_test.cpp', 'schema/expression_internal_schema_max_length_test.cpp', 'schema/expression_internal_schema_min_items_test.cpp', diff --git a/src/mongo/db/matcher/expression_geo.cpp b/src/mongo/db/matcher/expression_geo.cpp index 104eefec513..c15d1f564a7 100644 --- a/src/mongo/db/matcher/expression_geo.cpp +++ b/src/mongo/db/matcher/expression_geo.cpp @@ -31,8 +31,10 @@ #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kDefault #include "mongo/db/matcher/expression_geo.h" + #include "mongo/bson/simple_bsonobj_comparator.h" #include "mongo/db/geo/geoparser.h" +#include "mongo/db/matcher/expression_parser.h" #include "mongo/platform/basic.h" #include "mongo/util/log.h" #include "mongo/util/mongoutils/str.h" @@ -59,10 +61,10 @@ Status GeoExpression::parseQuery(const BSONObj& obj) { str::stream() << "can't parse extra field: " << outerIt.next()); } - BSONObj::MatchType matchType = static_cast<BSONObj::MatchType>(queryElt.getGtLtOp()); - if (BSONObj::opGEO_INTERSECTS == matchType) { + auto keyword = MatchExpressionParser::parsePathAcceptingKeyword(queryElt); + if (PathAcceptingKeyword::GEO_INTERSECTS == keyword) { predicate = GeoExpression::INTERSECT; - } else if (BSONObj::opWITHIN == matchType) { + } else if (PathAcceptingKeyword::WITHIN == keyword) { predicate = GeoExpression::WITHIN; } else { // eoo() or unknown query predicate. @@ -230,8 +232,8 @@ Status GeoNearExpression::parseNewQuery(const BSONObj& obj) { if (!e.isABSONObj()) { return Status(ErrorCodes::BadValue, "geo near query argument is not an object"); } - BSONObj::MatchType matchType = static_cast<BSONObj::MatchType>(e.getGtLtOp()); - if (BSONObj::opNEAR != matchType) { + + if (PathAcceptingKeyword::GEO_NEAR != MatchExpressionParser::parsePathAcceptingKeyword(e)) { return Status(ErrorCodes::BadValue, mongoutils::str::stream() << "invalid geo near query operator: " << e.fieldName()); diff --git a/src/mongo/db/matcher/expression_parser.cpp b/src/mongo/db/matcher/expression_parser.cpp index 9263862e16c..92639890e08 100644 --- a/src/mongo/db/matcher/expression_parser.cpp +++ b/src/mongo/db/matcher/expression_parser.cpp @@ -30,6 +30,7 @@ #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" @@ -48,6 +49,7 @@ #include "mongo/db/namespace_string.h" #include "mongo/stdx/memory.h" #include "mongo/util/mongoutils/str.h" +#include "mongo/util/string_map.h" namespace { @@ -107,8 +109,6 @@ StatusWithMatchExpression MatchExpressionParser::_parseSubField(const BSONObj& c const BSONElement& e, const CollatorInterface* collator, bool topLevel) { - // TODO: these should move to getGtLtOp, or its replacement - if (mongoutils::str::equals("$eq", e.fieldName())) return _parseComparison(name, new EqualityMatchExpression(), e, collator); @@ -116,25 +116,27 @@ StatusWithMatchExpression MatchExpressionParser::_parseSubField(const BSONObj& c return _parseNot(name, e, collator, topLevel); } - 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 {Status(ErrorCodes::BadValue, "$where cannot be applied to a field")}; - } + 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())}; - case BSONObj::LT: + return {Status(ErrorCodes::BadValue, + mongoutils::str::stream() << "unknown operator: " << e.fieldName())}; + } + + switch (*parseExpMatchType) { + case PathAcceptingKeyword::LESS_THAN: return _parseComparison(name, new LTMatchExpression(), e, collator); - case BSONObj::LTE: + case PathAcceptingKeyword::LESS_THAN_OR_EQUAL: return _parseComparison(name, new LTEMatchExpression(), e, collator); - case BSONObj::GT: + case PathAcceptingKeyword::GREATER_THAN: return _parseComparison(name, new GTMatchExpression(), e, collator); - case BSONObj::GTE: + case PathAcceptingKeyword::GREATER_THAN_OR_EQUAL: return _parseComparison(name, new GTEMatchExpression(), e, collator); - case BSONObj::NE: { + 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. @@ -150,10 +152,10 @@ StatusWithMatchExpression MatchExpressionParser::_parseSubField(const BSONObj& c return s2; return {std::move(n)}; } - case BSONObj::Equality: + case PathAcceptingKeyword::EQUALITY: return _parseComparison(name, new EqualityMatchExpression(), e, collator); - case BSONObj::opIN: { + case PathAcceptingKeyword::IN_EXPR: { if (e.type() != Array) return {Status(ErrorCodes::BadValue, "$in needs an array")}; std::unique_ptr<InMatchExpression> temp = stdx::make_unique<InMatchExpression>(); @@ -166,7 +168,7 @@ StatusWithMatchExpression MatchExpressionParser::_parseSubField(const BSONObj& c return {std::move(temp)}; } - case BSONObj::NIN: { + case PathAcceptingKeyword::NOT_IN: { if (e.type() != Array) return {Status(ErrorCodes::BadValue, "$nin needs an array")}; std::unique_ptr<InMatchExpression> temp = stdx::make_unique<InMatchExpression>(); @@ -185,7 +187,7 @@ StatusWithMatchExpression MatchExpressionParser::_parseSubField(const BSONObj& c return {std::move(temp2)}; } - case BSONObj::opSIZE: { + case PathAcceptingKeyword::SIZE: { int size = 0; if (e.type() == NumberInt) { size = e.numberInt(); @@ -216,7 +218,7 @@ StatusWithMatchExpression MatchExpressionParser::_parseSubField(const BSONObj& c return {std::move(temp)}; } - case BSONObj::opEXISTS: { + case PathAcceptingKeyword::EXISTS: { if (e.eoo()) return {Status(ErrorCodes::BadValue, "$exists can't be eoo")}; std::unique_ptr<ExistsMatchExpression> temp = @@ -233,73 +235,74 @@ StatusWithMatchExpression MatchExpressionParser::_parseSubField(const BSONObj& c return {std::move(temp2)}; } - case BSONObj::opTYPE: + case PathAcceptingKeyword::TYPE: return _parseType(name, e); - case BSONObj::opMOD: + case PathAcceptingKeyword::MOD: return _parseMOD(name, e); - case BSONObj::opOPTIONS: { + 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 (temp.getGtLtOp(-1) == BSONObj::opREGEX) + if (MatchExpressionParser::parsePathAcceptingKeyword(temp) == + PathAcceptingKeyword::REGEX) return {nullptr}; } return {Status(ErrorCodes::BadValue, "$options needs a $regex")}; } - case BSONObj::opREGEX: { + case PathAcceptingKeyword::REGEX: { return _parseRegexDocument(name, context); } - case BSONObj::opELEM_MATCH: + case PathAcceptingKeyword::ELEM_MATCH: return _parseElemMatch(name, e, collator, topLevel); - case BSONObj::opALL: + case PathAcceptingKeyword::ALL: return _parseAll(name, e, collator, topLevel); - case BSONObj::opWITHIN: - case BSONObj::opGEO_INTERSECTS: - return _parseGeo(name, x, context); + case PathAcceptingKeyword::WITHIN: + case PathAcceptingKeyword::GEO_INTERSECTS: + return _parseGeo(name, *parseExpMatchType, context); - case BSONObj::opNEAR: + case PathAcceptingKeyword::GEO_NEAR: return {Status(ErrorCodes::BadValue, mongoutils::str::stream() << "near must be first in: " << context)}; // Handles bitwise query operators. - case BSONObj::opBITS_ALL_SET: { + case PathAcceptingKeyword::BITS_ALL_SET: { return _parseBitTest<BitsAllSetMatchExpression>(name, e); } - case BSONObj::opBITS_ALL_CLEAR: { + case PathAcceptingKeyword::BITS_ALL_CLEAR: { return _parseBitTest<BitsAllClearMatchExpression>(name, e); } - case BSONObj::opBITS_ANY_SET: { + case PathAcceptingKeyword::BITS_ANY_SET: { return _parseBitTest<BitsAnySetMatchExpression>(name, e); } - case BSONObj::opBITS_ANY_CLEAR: { + case PathAcceptingKeyword::BITS_ANY_CLEAR: { return _parseBitTest<BitsAnyClearMatchExpression>(name, e); } - case BSONObj::opINTERNAL_SCHEMA_MIN_ITEMS: { + case PathAcceptingKeyword::INTERNAL_SCHEMA_MIN_ITEMS: { return _parseInternalSchemaSingleIntegerArgument<InternalSchemaMinItemsMatchExpression>( name, e); } - case BSONObj::opINTERNAL_SCHEMA_MAX_ITEMS: { + case PathAcceptingKeyword::INTERNAL_SCHEMA_MAX_ITEMS: { return _parseInternalSchemaSingleIntegerArgument<InternalSchemaMaxItemsMatchExpression>( name, e); } - case BSONObj::opINTERNAL_SCHEMA_OBJECT_MATCH: { + case PathAcceptingKeyword::INTERNAL_SCHEMA_OBJECT_MATCH: { if (e.type() != BSONType::Object) { return Status(ErrorCodes::FailedToParse, str::stream() << "$_internalSchemaObjectMatch must be an object"); @@ -318,7 +321,7 @@ StatusWithMatchExpression MatchExpressionParser::_parseSubField(const BSONObj& c return {std::move(expr)}; } - case BSONObj::opINTERNAL_SCHEMA_UNIQUE_ITEMS: { + case PathAcceptingKeyword::INTERNAL_SCHEMA_UNIQUE_ITEMS: { if (!e.isBoolean() || !e.boolean()) { return {ErrorCodes::FailedToParse, str::stream() << name << " must be a boolean of value true"}; @@ -332,12 +335,12 @@ StatusWithMatchExpression MatchExpressionParser::_parseSubField(const BSONObj& c return {std::move(expr)}; } - case BSONObj::opINTERNAL_SCHEMA_MIN_LENGTH: { + case PathAcceptingKeyword::INTERNAL_SCHEMA_MIN_LENGTH: { return _parseInternalSchemaSingleIntegerArgument< InternalSchemaMinLengthMatchExpression>(name, e); } - case BSONObj::opINTERNAL_SCHEMA_MAX_LENGTH: { + case PathAcceptingKeyword::INTERNAL_SCHEMA_MAX_LENGTH: { return _parseInternalSchemaSingleIntegerArgument< InternalSchemaMaxLengthMatchExpression>(name, e); } @@ -497,7 +500,11 @@ Status MatchExpressionParser::_parseSub(const char* name, if (mongoutils::str::equals(fieldName, "$near") || mongoutils::str::equals(fieldName, "$nearSphere") || mongoutils::str::equals(fieldName, "$geoNear")) { - StatusWithMatchExpression s = _parseGeo(name, firstElt.getGtLtOp(), sub); + StatusWithMatchExpression s = + _parseGeo(name, + *MatchExpressionParser::parsePathAcceptingKeyword( + firstElt, PathAcceptingKeyword::EQUALITY), + sub); if (s.isOK()) { root->add(s.getValue().release()); } @@ -633,8 +640,13 @@ StatusWithMatchExpression MatchExpressionParser::_parseRegexDocument(const char* BSONObjIterator i(doc); while (i.more()) { BSONElement e = i.next(); - switch (e.getGtLtOp()) { - case BSONObj::opREGEX: + 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) { @@ -645,7 +657,7 @@ StatusWithMatchExpression MatchExpressionParser::_parseRegexDocument(const char* } break; - case BSONObj::opOPTIONS: + case PathAcceptingKeyword::OPTIONS: if (e.type() != String) return {Status(ErrorCodes::BadValue, "$options has to be a string")}; regexOptions = e.String(); @@ -885,7 +897,8 @@ StatusWithMatchExpression MatchExpressionParser::_parseAll(const char* name, if (!s.isOK()) return s; myAnd->add(r.release()); - } else if (e.type() == Object && e.Obj().firstElement().getGtLtOp(-1) != -1) { + } else if (e.type() == Object && + MatchExpressionParser::parsePathAcceptingKeyword(e.Obj().firstElement())) { return {Status(ErrorCodes::BadValue, "no $ expressions in $all")}; } else { std::unique_ptr<EqualityMatchExpression> x = @@ -1096,9 +1109,9 @@ StatusWithMatchExpression MatchExpressionParser::_parseInternalSchemaSingleInteg } StatusWithMatchExpression MatchExpressionParser::_parseGeo(const char* name, - int type, + PathAcceptingKeyword type, const BSONObj& section) { - if (BSONObj::opWITHIN == type || BSONObj::opGEO_INTERSECTS == type) { + if (PathAcceptingKeyword::WITHIN == type || PathAcceptingKeyword::GEO_INTERSECTS == type) { std::unique_ptr<GeoExpression> gq = stdx::make_unique<GeoExpression>(name); Status parseStatus = gq->parseFrom(section); @@ -1112,7 +1125,7 @@ StatusWithMatchExpression MatchExpressionParser::_parseGeo(const char* name, return StatusWithMatchExpression(s); return {std::move(e)}; } else { - invariant(BSONObj::opNEAR == type); + invariant(PathAcceptingKeyword::GEO_NEAR == type); std::unique_ptr<GeoNearExpression> nq = stdx::make_unique<GeoNearExpression>(name); Status s = nq->parseFrom(section); if (!s.isOK()) { @@ -1125,4 +1138,62 @@ StatusWithMatchExpression MatchExpressionParser::_parseGeo(const char* name, return {std::move(e)}; } } + +namespace { +// Maps from query operator string name to operator PathAcceptingKeyword. +std::unique_ptr<StringMap<PathAcceptingKeyword>> queryOperatorMap; + +MONGO_INITIALIZER(MatchExpressionParser)(InitializerContext* context) { + queryOperatorMap = + stdx::make_unique<StringMap<PathAcceptingKeyword>>(StringMap<PathAcceptingKeyword>{ + // 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}, + {"_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}}); + return Status::OK(); +} +} // anonymous namespace + +boost::optional<PathAcceptingKeyword> MatchExpressionParser::parsePathAcceptingKeyword( + BSONElement typeElem, boost::optional<PathAcceptingKeyword> 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 diff --git a/src/mongo/db/matcher/expression_parser.h b/src/mongo/db/matcher/expression_parser.h index c41e93ae5b1..b661693efa8 100644 --- a/src/mongo/db/matcher/expression_parser.h +++ b/src/mongo/db/matcher/expression_parser.h @@ -43,6 +43,38 @@ namespace mongo { class CollatorInterface; class OperationContext; +enum class PathAcceptingKeyword { + EQUALITY, + LESS_THAN, + LESS_THAN_OR_EQUAL, + GREATER_THAN_OR_EQUAL, + GREATER_THAN, + IN_EXPR, + NOT_EQUAL, + SIZE, + ALL, + NOT_IN, + EXISTS, + MOD, + TYPE, + REGEX, + OPTIONS, + ELEM_MATCH, + GEO_NEAR, + WITHIN, + GEO_INTERSECTS, + BITS_ALL_SET, + BITS_ALL_CLEAR, + BITS_ANY_SET, + BITS_ANY_CLEAR, + INTERNAL_SCHEMA_MIN_ITEMS, + INTERNAL_SCHEMA_MAX_ITEMS, + INTERNAL_SCHEMA_UNIQUE_ITEMS, + INTERNAL_SCHEMA_OBJECT_MATCH, + INTERNAL_SCHEMA_MIN_LENGTH, + INTERNAL_SCHEMA_MAX_LENGTH +}; + class MatchExpressionParser { public: /** @@ -51,6 +83,14 @@ public: static const double kLongLongMaxPlusOneAsDouble; /** + * Parses PathAcceptingKeyword from 'typeElem'. Returns 'defaultKeyword' if 'typeElem' + * doesn't represent a known type, or represents PathAcceptingKeyword::EQUALITY which is not + * handled by this parser (see SERVER-19565). + */ + static boost::optional<PathAcceptingKeyword> parsePathAcceptingKeyword( + BSONElement typeElem, boost::optional<PathAcceptingKeyword> defaultKeyword = boost::none); + + /** * caller has to maintain ownership obj * the tree has views (BSONElement) into obj */ @@ -167,7 +207,9 @@ private: StatusWithMatchExpression _parseType(const char* name, const BSONElement& elt); - StatusWithMatchExpression _parseGeo(const char* name, int type, const BSONObj& section); + StatusWithMatchExpression _parseGeo(const char* name, + PathAcceptingKeyword type, + const BSONObj& section); // arrays diff --git a/src/mongo/db/matcher/path_accepting_keyword_test.cpp b/src/mongo/db/matcher/path_accepting_keyword_test.cpp new file mode 100644 index 00000000000..4830dd77505 --- /dev/null +++ b/src/mongo/db/matcher/path_accepting_keyword_test.cpp @@ -0,0 +1,140 @@ +/** + * Copyright (C) 2017 MongoDB 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 <http://www.gnu.org/licenses/>. + * + * 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/jsobj.h" +#include "mongo/db/matcher/expression_parser.h" +#include "mongo/unittest/unittest.h" + +namespace mongo { +namespace { + +TEST(PathAcceptingKeyword, CanParseKnownMatchTypes) { + ASSERT_TRUE(PathAcceptingKeyword::LESS_THAN == + MatchExpressionParser::parsePathAcceptingKeyword(BSON("$lt" << 1).firstElement())); + ASSERT_TRUE(PathAcceptingKeyword::LESS_THAN_OR_EQUAL == + MatchExpressionParser::parsePathAcceptingKeyword(BSON("$lte" << 1).firstElement())); + ASSERT_TRUE(PathAcceptingKeyword::GREATER_THAN_OR_EQUAL == + MatchExpressionParser::parsePathAcceptingKeyword(BSON("$gte" << 1).firstElement())); + ASSERT_TRUE(PathAcceptingKeyword::GREATER_THAN == + MatchExpressionParser::parsePathAcceptingKeyword(BSON("$gt" << 1).firstElement())); + ASSERT_TRUE(PathAcceptingKeyword::IN_EXPR == + MatchExpressionParser::parsePathAcceptingKeyword(BSON("$in" << 1).firstElement())); + ASSERT_TRUE(PathAcceptingKeyword::NOT_EQUAL == + MatchExpressionParser::parsePathAcceptingKeyword(BSON("$ne" << 1).firstElement())); + ASSERT_TRUE(PathAcceptingKeyword::SIZE == MatchExpressionParser::parsePathAcceptingKeyword( + BSON("$size" << 1).firstElement())); + ASSERT_TRUE(PathAcceptingKeyword::ALL == + MatchExpressionParser::parsePathAcceptingKeyword(BSON("$all" << 1).firstElement())); + ASSERT_TRUE(PathAcceptingKeyword::NOT_IN == + MatchExpressionParser::parsePathAcceptingKeyword(BSON("$nin" << 1).firstElement())); + ASSERT_TRUE(PathAcceptingKeyword::EXISTS == MatchExpressionParser::parsePathAcceptingKeyword( + BSON("$exists" << 1).firstElement())); + ASSERT_TRUE(PathAcceptingKeyword::MOD == + MatchExpressionParser::parsePathAcceptingKeyword(BSON("$mod" << 1).firstElement())); + ASSERT_TRUE(PathAcceptingKeyword::TYPE == MatchExpressionParser::parsePathAcceptingKeyword( + BSON("$type" << 1).firstElement())); + ASSERT_TRUE(PathAcceptingKeyword::REGEX == MatchExpressionParser::parsePathAcceptingKeyword( + BSON("$regex" << 1).firstElement())); + ASSERT_TRUE(PathAcceptingKeyword::OPTIONS == MatchExpressionParser::parsePathAcceptingKeyword( + BSON("$options" << 1).firstElement())); + ASSERT_TRUE( + PathAcceptingKeyword::ELEM_MATCH == + MatchExpressionParser::parsePathAcceptingKeyword(BSON("$elemMatch" << 1).firstElement())); + ASSERT_TRUE(PathAcceptingKeyword::GEO_NEAR == MatchExpressionParser::parsePathAcceptingKeyword( + BSON("$near" << 1).firstElement())); + ASSERT_TRUE(PathAcceptingKeyword::GEO_NEAR == MatchExpressionParser::parsePathAcceptingKeyword( + BSON("$geoNear" << 1).firstElement())); + ASSERT_TRUE(PathAcceptingKeyword::WITHIN == MatchExpressionParser::parsePathAcceptingKeyword( + BSON("$within" << 1).firstElement())); + ASSERT_TRUE(PathAcceptingKeyword::WITHIN == MatchExpressionParser::parsePathAcceptingKeyword( + BSON("$geoWithin" << 1).firstElement())); + ASSERT_TRUE(PathAcceptingKeyword::GEO_INTERSECTS == + MatchExpressionParser::parsePathAcceptingKeyword( + BSON("$geoIntersects" << 1).firstElement())); + ASSERT_TRUE( + PathAcceptingKeyword::BITS_ALL_SET == + MatchExpressionParser::parsePathAcceptingKeyword(BSON("$bitsAllSet" << 1).firstElement())); + ASSERT_TRUE(PathAcceptingKeyword::BITS_ALL_CLEAR == + MatchExpressionParser::parsePathAcceptingKeyword( + BSON("$bitsAllClear" << 1).firstElement())); + ASSERT_TRUE( + PathAcceptingKeyword::BITS_ANY_SET == + MatchExpressionParser::parsePathAcceptingKeyword(BSON("$bitsAnySet" << 1).firstElement())); + ASSERT_TRUE(PathAcceptingKeyword::BITS_ANY_CLEAR == + MatchExpressionParser::parsePathAcceptingKeyword( + BSON("$bitsAnyClear" << 1).firstElement())); + ASSERT_TRUE(PathAcceptingKeyword::INTERNAL_SCHEMA_MIN_ITEMS == + MatchExpressionParser::parsePathAcceptingKeyword( + BSON("$_internalSchemaMinItems" << 1).firstElement())); + ASSERT_TRUE(PathAcceptingKeyword::INTERNAL_SCHEMA_MAX_ITEMS == + MatchExpressionParser::parsePathAcceptingKeyword( + BSON("$_internalSchemaMaxItems" << 1).firstElement())); + ASSERT_TRUE(PathAcceptingKeyword::INTERNAL_SCHEMA_UNIQUE_ITEMS == + MatchExpressionParser::parsePathAcceptingKeyword( + BSON("$_internalSchemaUniqueItems" << 1).firstElement())); + ASSERT_TRUE(PathAcceptingKeyword::INTERNAL_SCHEMA_OBJECT_MATCH == + MatchExpressionParser::parsePathAcceptingKeyword( + BSON("$_internalSchemaObjectMatch" << 1).firstElement())); + ASSERT_TRUE(PathAcceptingKeyword::INTERNAL_SCHEMA_MIN_LENGTH == + MatchExpressionParser::parsePathAcceptingKeyword( + BSON("$_internalSchemaMinLength" << 1).firstElement())); + ASSERT_TRUE(PathAcceptingKeyword::INTERNAL_SCHEMA_MAX_LENGTH == + MatchExpressionParser::parsePathAcceptingKeyword( + BSON("$_internalSchemaMaxLength" << 1).firstElement())); +} + +TEST(PathAcceptingKeyword, EqualityMatchReturnsDefault) { + // 'boost::none' is the default when none specified. + ASSERT_TRUE(boost::none == + MatchExpressionParser::parsePathAcceptingKeyword(BSON("$eq" << 1).firstElement())); + // Should return default specified by caller. + ASSERT_TRUE(PathAcceptingKeyword::GEO_NEAR == + MatchExpressionParser::parsePathAcceptingKeyword(BSON("$eq" << 1).firstElement(), + PathAcceptingKeyword::GEO_NEAR)); +} + +TEST(PathAcceptingKeyword, UnknownExpressionReturnsDefault) { + // Non-existent expression starting with '$'. + ASSERT_TRUE(PathAcceptingKeyword::GEO_INTERSECTS == + MatchExpressionParser::parsePathAcceptingKeyword( + BSON("$foo" << 1).firstElement(), PathAcceptingKeyword::GEO_INTERSECTS)); + // Existing expression but missing leading '$'. + ASSERT_TRUE(PathAcceptingKeyword::NOT_IN == + MatchExpressionParser::parsePathAcceptingKeyword(BSON("size" << 1).firstElement(), + PathAcceptingKeyword::NOT_IN)); +} + +TEST(PathAcceptingKeyword, EmptyBSONElemReturnsDefault) { + BSONElement emptyElem; + ASSERT_TRUE(boost::none == MatchExpressionParser::parsePathAcceptingKeyword(emptyElem)); +} + +} // namespace +} // namespace mongo diff --git a/src/mongo/db/ops/modifier_pull.cpp b/src/mongo/db/ops/modifier_pull.cpp index 70d5442a716..f3adc0e3538 100644 --- a/src/mongo/db/ops/modifier_pull.cpp +++ b/src/mongo/db/ops/modifier_pull.cpp @@ -109,7 +109,9 @@ Status ModifierPull::init(const BSONElement& modExpr, const Options& opts, bool* _exprObj = _exprElt.embeddedObject(); // If not is not a query operator, then it is a primitive. - _matcherOnPrimitive = (_exprObj.firstElement().getGtLtOp() != 0); + _matcherOnPrimitive = (MatchExpressionParser::parsePathAcceptingKeyword( + _exprObj.firstElement(), PathAcceptingKeyword::EQUALITY) != + PathAcceptingKeyword::EQUALITY); // If the object is primitive then wrap it up into an object. if (_matcherOnPrimitive) diff --git a/src/mongo/db/pipeline/document_source_match.cpp b/src/mongo/db/pipeline/document_source_match.cpp index 8cef470c453..e34d9f2d1a5 100644 --- a/src/mongo/db/pipeline/document_source_match.cpp +++ b/src/mongo/db/pipeline/document_source_match.cpp @@ -34,6 +34,7 @@ #include "mongo/db/matcher/expression_algo.h" #include "mongo/db/matcher/expression_array.h" #include "mongo/db/matcher/expression_leaf.h" +#include "mongo/db/matcher/expression_parser.h" #include "mongo/db/matcher/extensions_callback_noop.h" #include "mongo/db/pipeline/document.h" #include "mongo/db/pipeline/expression.h" @@ -196,30 +197,31 @@ Document redactSafePortionDollarOps(BSONObj expr) { continue; } - switch (BSONObj::MatchType(field.getGtLtOp(BSONObj::Equality))) { + switch (*MatchExpressionParser::parsePathAcceptingKeyword(field, + PathAcceptingKeyword::EQUALITY)) { // These are always ok - case BSONObj::opTYPE: - case BSONObj::opREGEX: - case BSONObj::opOPTIONS: - case BSONObj::opMOD: - case BSONObj::opBITS_ALL_SET: - case BSONObj::opBITS_ALL_CLEAR: - case BSONObj::opBITS_ANY_SET: - case BSONObj::opBITS_ANY_CLEAR: + case PathAcceptingKeyword::TYPE: + case PathAcceptingKeyword::REGEX: + case PathAcceptingKeyword::OPTIONS: + case PathAcceptingKeyword::MOD: + case PathAcceptingKeyword::BITS_ALL_SET: + case PathAcceptingKeyword::BITS_ALL_CLEAR: + case PathAcceptingKeyword::BITS_ANY_SET: + case PathAcceptingKeyword::BITS_ANY_CLEAR: output[field.fieldNameStringData()] = Value(field); break; // These are ok if the type of the rhs is allowed in comparisons - case BSONObj::LTE: - case BSONObj::GTE: - case BSONObj::LT: - case BSONObj::GT: + case PathAcceptingKeyword::LESS_THAN_OR_EQUAL: + case PathAcceptingKeyword::GREATER_THAN_OR_EQUAL: + case PathAcceptingKeyword::LESS_THAN: + case PathAcceptingKeyword::GREATER_THAN: if (isTypeRedactSafeInComparison(field.type())) output[field.fieldNameStringData()] = Value(field); break; // $in must be all-or-nothing (like $or). Can't include subset of elements. - case BSONObj::opIN: { + case PathAcceptingKeyword::IN_EXPR: { bool allOk = true; BSONForEach(elem, field.Obj()) { if (!isTypeRedactSafeInComparison(elem.type())) { @@ -234,7 +236,7 @@ Document redactSafePortionDollarOps(BSONObj expr) { break; } - case BSONObj::opALL: { + case PathAcceptingKeyword::ALL: { // $all can include subset of elements (like $and). vector<Value> matches; BSONForEach(elem, field.Obj()) { @@ -249,7 +251,7 @@ Document redactSafePortionDollarOps(BSONObj expr) { break; } - case BSONObj::opELEM_MATCH: { + case PathAcceptingKeyword::ELEM_MATCH: { BSONObj subIn = field.Obj(); Document subOut; if (subIn.firstElementFieldName()[0] == '$') { @@ -265,20 +267,20 @@ Document redactSafePortionDollarOps(BSONObj expr) { } // These are never allowed - case BSONObj::Equality: // This actually means unknown - case BSONObj::opNEAR: - case BSONObj::NE: - case BSONObj::opSIZE: - case BSONObj::NIN: - case BSONObj::opEXISTS: - case BSONObj::opWITHIN: - case BSONObj::opGEO_INTERSECTS: - case BSONObj::opINTERNAL_SCHEMA_MIN_ITEMS: - case BSONObj::opINTERNAL_SCHEMA_MAX_ITEMS: - case BSONObj::opINTERNAL_SCHEMA_UNIQUE_ITEMS: - case BSONObj::opINTERNAL_SCHEMA_OBJECT_MATCH: - case BSONObj::opINTERNAL_SCHEMA_MIN_LENGTH: - case BSONObj::opINTERNAL_SCHEMA_MAX_LENGTH: + case PathAcceptingKeyword::EQUALITY: // This actually means unknown + case PathAcceptingKeyword::GEO_NEAR: + case PathAcceptingKeyword::NOT_EQUAL: + case PathAcceptingKeyword::SIZE: + case PathAcceptingKeyword::NOT_IN: + case PathAcceptingKeyword::EXISTS: + case PathAcceptingKeyword::WITHIN: + case PathAcceptingKeyword::GEO_INTERSECTS: + case PathAcceptingKeyword::INTERNAL_SCHEMA_MIN_ITEMS: + case PathAcceptingKeyword::INTERNAL_SCHEMA_MAX_ITEMS: + case PathAcceptingKeyword::INTERNAL_SCHEMA_UNIQUE_ITEMS: + case PathAcceptingKeyword::INTERNAL_SCHEMA_OBJECT_MATCH: + case PathAcceptingKeyword::INTERNAL_SCHEMA_MIN_LENGTH: + case PathAcceptingKeyword::INTERNAL_SCHEMA_MAX_LENGTH: continue; } } diff --git a/src/mongo/db/update/pull_node.cpp b/src/mongo/db/update/pull_node.cpp index 45052c0c0f9..4d9c9c2fa09 100644 --- a/src/mongo/db/update/pull_node.cpp +++ b/src/mongo/db/update/pull_node.cpp @@ -129,7 +129,8 @@ Status PullNode::init(BSONElement modExpr, const CollatorInterface* collator) { try { if (modExpr.type() == mongo::Object && - modExpr.embeddedObject().firstElement().getGtLtOp(-1) == -1) { + !MatchExpressionParser::parsePathAcceptingKeyword( + modExpr.embeddedObject().firstElement())) { _matcher = stdx::make_unique<ObjectMatcher>(modExpr.embeddedObject(), collator); } else if (modExpr.type() == mongo::Object || modExpr.type() == mongo::RegEx) { _matcher = stdx::make_unique<WrappedObjectMatcher>(modExpr, collator); |