diff options
| author | Lucas de Castro Borges <lucas@gnuabordo.com.br> | 2025-02-11 15:07:35 -0300 |
|---|---|---|
| committer | Lucas de Castro Borges <lucas@gnuabordo.com.br> | 2025-02-11 15:07:35 -0300 |
| commit | 4cb8841196d0625dfa3825aa326f071cd27c7b8b (patch) | |
| tree | 1682a647d4463397c119183369ae6f750d5fdcff /src/mongo/db/matcher | |
| parent | aa03c6362cbaa767638e6eed9b031d86dd2643d1 (diff) | |
| parent | 8f0827553e09872941945a093b647a4211a9db7f (diff) | |
Update upstream source from tag 'upstream/6.0.0'master
Update to upstream version '6.0.0'
with Debian dir 5604a80ec1c96ca76f25f40d78e6ef855abec322
Diffstat (limited to 'src/mongo/db/matcher')
66 files changed, 737 insertions, 2691 deletions
diff --git a/src/mongo/db/matcher/SConscript b/src/mongo/db/matcher/SConscript index 27ae56fac0e..4d484b0fadb 100644 --- a/src/mongo/db/matcher/SConscript +++ b/src/mongo/db/matcher/SConscript @@ -27,7 +27,6 @@ env.Library( 'expression_array.cpp', 'expression_expr.cpp', 'expression_geo.cpp', - 'expression_geo_serializer.cpp', 'expression_internal_bucket_geo_within.cpp', 'expression_leaf.cpp', 'expression_parameterization.cpp', @@ -74,7 +73,6 @@ env.Library( '$BUILD_DIR/mongo/db/pipeline/expression_context', '$BUILD_DIR/mongo/db/query/collation/collator_interface', '$BUILD_DIR/mongo/db/query/query_knobs', - '$BUILD_DIR/mongo/db/query/query_shape/query_shape_common', '$BUILD_DIR/mongo/db/stats/counters', '$BUILD_DIR/mongo/idl/idl_parser', '$BUILD_DIR/mongo/util/regex_util', diff --git a/src/mongo/db/matcher/expression.h b/src/mongo/db/matcher/expression.h index fb6f5f6296a..33ea3ce8790 100644 --- a/src/mongo/db/matcher/expression.h +++ b/src/mongo/db/matcher/expression.h @@ -41,7 +41,6 @@ #include "mongo/db/matcher/match_details.h" #include "mongo/db/matcher/matchable.h" #include "mongo/db/pipeline/dependencies.h" -#include "mongo/db/query/query_shape/serialization_options.h" #include "mongo/util/fail_point.h" namespace mongo { @@ -478,42 +477,19 @@ public: void addDependencies(DepsTracker* deps) const; /** - * Serialize the MatchExpression to BSON, appending to 'out'. - * - * See 'SerializationOptions' for some options. - * - * Generally, the output of this method is expected to be a valid query object that, when - * parsed, produces a logically equivalent MatchExpression. However, if special options are set, - * this no longer holds. - * - * If 'options.literalPolicy' is set to 'kToDebugTypeString', the result is no longer expected - * to re-parse, since we will put strings in places where strings may not be accpeted - * syntactically (e.g. a number is always expected, as in with the $mod expression). - * - * includePath: - * If set to false, serializes without including the path. For example {a: {$gt: 2}} would - * serialize as just {$gt: 2}. - * - * It is expected that most callers want to set 'includePath' to true to get a correct - * serialization. Internally, we may set this to false if we have a situation where an outer - * expression serializes a path and we don't want to repeat the path in the inner expression. - - * For example in {a: {$elemMatch: {$eq: 2}}} the "a" is serialized by the $elemMatch, and - * should not be serialized by the EQ child. - * The $elemMatch will serialize {a: {$elemMatch: <recurse>}} and the EQ will serialize just - * {$eq: 2} instead of its usual {a: {$eq: 2}}. + * Serialize the MatchExpression to BSON, appending to 'out'. Output of this method is expected + * to be a valid query object, that, when parsed, produces a logically equivalent + * MatchExpression. If 'includePath' is false then the serialization should assume it's in a + * context where the path has been serialized elsewhere, such as within an $elemMatch value. */ - virtual void serialize(BSONObjBuilder* out, - const SerializationOptions& options = {}, - bool includePath = true) const = 0; + virtual void serialize(BSONObjBuilder* out, bool includePath = true) const = 0; /** - * Convenience method which serializes this MatchExpression to a BSONObj. See the override with - * a BSONObjBuilder* argument for details. + * Convenience method which serializes this MatchExpression to a BSONObj. */ - BSONObj serialize(const SerializationOptions& options = {}, bool includePath = true) const { + BSONObj serialize(bool includePath = true) const { BSONObjBuilder bob; - serialize(&bob, options, includePath); + serialize(&bob, includePath); return bob.obj(); } diff --git a/src/mongo/db/matcher/expression_algo.cpp b/src/mongo/db/matcher/expression_algo.cpp index 36ae0ecbc92..5d56608227d 100644 --- a/src/mongo/db/matcher/expression_algo.cpp +++ b/src/mongo/db/matcher/expression_algo.cpp @@ -31,7 +31,6 @@ #include "mongo/platform/basic.h" #include "mongo/base/checked_cast.h" -#include "mongo/bson/unordered_fields_bsonobj_comparator.h" #include "mongo/db/matcher/expression.h" #include "mongo/db/matcher/expression_algo.h" #include "mongo/db/matcher/expression_array.h" @@ -375,7 +374,7 @@ unique_ptr<MatchExpression> createNorOfNodes(std::vector<unique_ptr<MatchExpress */ std::pair<unique_ptr<MatchExpression>, unique_ptr<MatchExpression>> splitMatchExpressionByFunction( unique_ptr<MatchExpression> expr, - const OrderedPathSet& fields, + const std::set<std::string>& fields, expression::ShouldSplitExprFunc shouldSplitOut) { if (shouldSplitOut(*expr, fields)) { // 'expr' satisfies our split condition and can be completely split out. @@ -441,7 +440,7 @@ std::pair<unique_ptr<MatchExpression>, unique_ptr<MatchExpression>> splitMatchEx bool pathDependenciesAreExact(StringData key, const MatchExpression* expr) { DepsTracker columnDeps; expr->addDependencies(&columnDeps); - return !columnDeps.needWholeDocument && columnDeps.fields == OrderedPathSet{key.toString()}; + return !columnDeps.needWholeDocument && columnDeps.fields == std::set{key.toString()}; } bool tryAddExprHelper(StringData path, @@ -770,6 +769,7 @@ bool isSubsetOf(const MatchExpression* lhs, const MatchExpression* rhs) { return false; } +// Checks if 'expr' has any children which do not have renaming implemented. bool hasOnlyRenameableMatchExpressionChildren(const MatchExpression& expr) { if (expr.matchType() == MatchExpression::MatchType::EXPRESSION) { return true; @@ -786,33 +786,7 @@ bool hasOnlyRenameableMatchExpressionChildren(const MatchExpression& expr) { return true; } -bool containsDependency(const OrderedPathSet& testSet, const OrderedPathSet& prefixCandidates) { - if (testSet.empty()) { - return false; - } - - PathComparator pathComparator; - auto i2 = testSet.begin(); - for (auto p1 : prefixCandidates) { - while (pathComparator(*i2, p1)) { - ++i2; - if (i2 == testSet.end()) { - return false; - } - } - // At this point we know that p1 <= *i2, so it may be identical or a path prefix. - if (p1 == *i2 || isPathPrefixOf(p1, *i2)) { - return true; - } - } - return false; -} - -bool areIndependent(const OrderedPathSet& pathSet1, const OrderedPathSet& pathSet2) { - return !containsDependency(pathSet1, pathSet2) && !containsDependency(pathSet2, pathSet1); -} - -bool isIndependentOf(const MatchExpression& expr, const OrderedPathSet& pathSet) { +bool isIndependentOf(const MatchExpression& expr, const std::set<std::string>& pathSet) { // Any expression types that do not have renaming implemented cannot have their independence // evaluated here. See applyRenamesToExpression(). if (!hasOnlyRenameableMatchExpressionChildren(expr)) { @@ -821,43 +795,35 @@ bool isIndependentOf(const MatchExpression& expr, const OrderedPathSet& pathSet) auto depsTracker = DepsTracker{}; expr.addDependencies(&depsTracker); - // Match expressions that generate random numbers can't be safely split out and pushed down. - if (depsTracker.needRandomGenerator || depsTracker.needWholeDocument) { - return false; - } - return areIndependent(pathSet, depsTracker.fields); + return std::none_of( + depsTracker.fields.begin(), depsTracker.fields.end(), [&pathSet](auto&& field) { + return pathSet.find(field) != pathSet.end() || + std::any_of(pathSet.begin(), pathSet.end(), [&field](auto&& path) { + return expression::isPathPrefixOf(field, path) || + expression::isPathPrefixOf(path, field); + }); + }); } -bool isOnlyDependentOn(const MatchExpression& expr, const OrderedPathSet& pathSet) { +bool isOnlyDependentOn(const MatchExpression& expr, const std::set<std::string>& pathSet) { // Any expression types that do not have renaming implemented cannot have their independence // evaluated here. See applyRenamesToExpression(). if (!hasOnlyRenameableMatchExpressionChildren(expr)) { return false; } - // The approach below takes only O(n log n) time. - - // Find the unique dependencies of pathSet. - auto pathsDeps = - DepsTracker::simplifyDependencies(pathSet, DepsTracker::TruncateToRootLevel::no); - auto pathsDepsCopy = OrderedPathSet(pathsDeps.begin(), pathsDeps.end()); - - // Now add the match expression's paths and see if the dependencies are the same. - auto exprDepsTracker = DepsTracker{}; - expr.addDependencies(&exprDepsTracker); - // Match expressions that generate random numbers can't be safely split out and pushed down. - if (exprDepsTracker.needRandomGenerator) { - return false; - } - pathsDepsCopy.insert(exprDepsTracker.fields.begin(), exprDepsTracker.fields.end()); - - return pathsDeps == - DepsTracker::simplifyDependencies(pathsDepsCopy, DepsTracker::TruncateToRootLevel::no); + auto depsTracker = DepsTracker{}; + expr.addDependencies(&depsTracker); + return std::all_of(depsTracker.fields.begin(), depsTracker.fields.end(), [&](auto&& field) { + return std::any_of(pathSet.begin(), pathSet.end(), [&](auto&& path) { + return path == field || isPathPrefixOf(path, field); + }); + }); } std::pair<unique_ptr<MatchExpression>, unique_ptr<MatchExpression>> splitMatchExpressionBy( unique_ptr<MatchExpression> expr, - const OrderedPathSet& fields, + const std::set<std::string>& fields, const StringMap<std::string>& renames, ShouldSplitExprFunc func /*= isIndependentOf */) { auto splitExpr = splitMatchExpressionByFunction(std::move(expr), fields, func); @@ -913,6 +879,11 @@ bool isPathPrefixOf(StringData first, StringData second) { return second.startsWith(first) && second[first.size()] == '.'; } +bool bidirectionalPathPrefixOf(StringData first, StringData second) { + return first == second || expression::isPathPrefixOf(first, second) || + expression::isPathPrefixOf(second, first); +} + boost::optional<StringMap<std::unique_ptr<MatchExpression>>> splitMatchExpressionForColumns( const MatchExpression* me) { boost::optional<StringMap<std::unique_ptr<MatchExpression>>> out; diff --git a/src/mongo/db/matcher/expression_algo.h b/src/mongo/db/matcher/expression_algo.h index 8c7c9b71c21..0fdbac756a0 100644 --- a/src/mongo/db/matcher/expression_algo.h +++ b/src/mongo/db/matcher/expression_algo.h @@ -34,7 +34,6 @@ #include <set> #include "mongo/base/string_data.h" -#include "mongo/db/pipeline/dependencies.h" #include "mongo/util/string_map.h" namespace mongo { @@ -54,11 +53,6 @@ using NodeTraversalFunc = std::function<void(MatchExpression*, std::string)>; bool hasExistencePredicateOnPath(const MatchExpression& expr, StringData path); /** - * Checks if 'expr' has any children which do not have renaming implemented. - */ -bool hasOnlyRenameableMatchExpressionChildren(const MatchExpression& expr); - -/** * Returns true if the documents matched by 'lhs' are a subset of the documents matched by * 'rhs', i.e. a document matched by 'lhs' must also be matched by 'rhs', and false otherwise. * @@ -91,32 +85,17 @@ bool isSubsetOf(const MatchExpression* lhs, const MatchExpression* rhs); * For example, {a: "foo", b: "bar"} is splittable by "b", while * {$or: [{a: {$eq: "foo"}}, {b: {$eq: "bar"}}]} is not splittable by "b", due to the $or. */ -bool isSplittableBy(const MatchExpression& expr, const OrderedPathSet& pathSet); - -/** - * True if no path in either set is contained by a path in the other. Does not check for - * dependencies within each of the sets, just across sets. Runs in 0(n) time. - * - * areIndependent([a.b, b, a], [c]) --> true - * areIndependent([a.b, b, a], [a.b.f]) --> false - */ -bool areIndependent(const OrderedPathSet& pathSet1, const OrderedPathSet& pathSet2); - -/** - * Return true if any of the fieldPaths in prefixCandidates are identical to or an ancestor of any - * of the fieldpaths in testSet. The order of the parameters matters -- it's not commutative. - */ -bool containsDependency(const OrderedPathSet& testSet, const OrderedPathSet& prefixCandidates); +bool isSplittableBy(const MatchExpression& expr, const std::set<std::string>& pathSet); /** * Determine if 'expr' is reliant upon any path from 'pathSet'. */ -bool isIndependentOf(const MatchExpression& expr, const OrderedPathSet& pathSet); +bool isIndependentOf(const MatchExpression& expr, const std::set<std::string>& pathSet); /** * Determine if 'expr' is reliant only upon paths from 'pathSet'. */ -bool isOnlyDependentOn(const MatchExpression& expr, const OrderedPathSet& pathSet); +bool isOnlyDependentOn(const MatchExpression& expr, const std::set<std::string>& pathSet); /** * Returns whether the path represented by 'first' is an prefix of the path represented by 'second'. @@ -131,13 +110,20 @@ bool isOnlyDependentOn(const MatchExpression& expr, const OrderedPathSet& pathSe bool isPathPrefixOf(StringData first, StringData second); /** + * Returns true if the first path is equal to the second path or if either is a prefix + * of the other. + */ +bool bidirectionalPathPrefixOf(StringData first, StringData second); + +/** * Applies 'func' to each node of 'expr', where the first argument is a pointer to that actual node * (not a copy), and the second argument is the path to that node. Callers should not depend on the * order of the traversal of the nodes. */ void mapOver(MatchExpression* expr, NodeTraversalFunc func, std::string path = ""); -using ShouldSplitExprFunc = std::function<bool(const MatchExpression&, const OrderedPathSet&)>; +using ShouldSplitExprFunc = + std::function<bool(const MatchExpression&, const std::set<std::string>&)>; /** * Attempt to split 'expr' into two MatchExpressions according to 'func'. 'func' describes the @@ -160,7 +146,7 @@ using ShouldSplitExprFunc = std::function<bool(const MatchExpression&, const Ord */ std::pair<std::unique_ptr<MatchExpression>, std::unique_ptr<MatchExpression>> splitMatchExpressionBy(std::unique_ptr<MatchExpression> expr, - const OrderedPathSet& fields, + const std::set<std::string>& fields, const StringMap<std::string>& renames, ShouldSplitExprFunc func = isIndependentOf); @@ -169,8 +155,6 @@ splitMatchExpressionBy(std::unique_ptr<MatchExpression> expr, * to the new values of those paths. For example, suppose the original match expression is * {old: {$gt: 3}} and 'renames' contains the mapping "old" => "new". At the end, 'expr' will be * {new: {$gt: 3}}. - * - * The caller should make sure that `expr` is renamable as a whole. */ void applyRenamesToExpression(MatchExpression* expr, const StringMap<std::string>& renames); diff --git a/src/mongo/db/matcher/expression_algo_test.cpp b/src/mongo/db/matcher/expression_algo_test.cpp index 23d3e2f4606..8679c759cd5 100644 --- a/src/mongo/db/matcher/expression_algo_test.cpp +++ b/src/mongo/db/matcher/expression_algo_test.cpp @@ -36,7 +36,6 @@ #include "mongo/db/matcher/expression.h" #include "mongo/db/matcher/expression_algo.h" #include "mongo/db/matcher/expression_parser.h" -#include "mongo/db/matcher/parsed_match_expression_for_test.h" #include "mongo/db/pipeline/expression_context_for_test.h" #include "mongo/db/query/collation/collator_interface_mock.h" #include "mongo/platform/decimal128.h" @@ -45,16 +44,30 @@ namespace mongo { using std::unique_ptr; +/** + * A MatchExpression does not hold the memory for BSONElements, so use ParsedMatchExpression to + * ensure that the BSONObj outlives the MatchExpression. + */ +class ParsedMatchExpression { +public: + ParsedMatchExpression(const std::string& str, const CollatorInterface* collator = nullptr) + : _obj(fromjson(str)) { + _expCtx = make_intrusive<ExpressionContextForTest>(); + _expCtx->setCollator(CollatorInterface::cloneCollator(collator)); + StatusWithMatchExpression result = MatchExpressionParser::parse(_obj, _expCtx); + ASSERT_OK(result.getStatus()); + _expr = std::move(result.getValue()); + } -void assertMatchesEqual(const ParsedMatchExpressionForTest& expected, - const std::unique_ptr<MatchExpression>& actual) { - if (expected.get() == nullptr) { - ASSERT(actual == nullptr); - return; + const MatchExpression* get() const { + return _expr.get(); } - ASSERT(actual != nullptr); - ASSERT_EQ(expected.get()->toString(), actual.get()->toString()); -} + +private: + const BSONObj _obj; + std::unique_ptr<MatchExpression> _expr; + boost::intrusive_ptr<ExpressionContext> _expCtx; +}; TEST(ExpressionAlgoIsSubsetOf, NullAndOmittedField) { // Verify that the ComparisonMatchExpression constructor prohibits creating a match expression @@ -64,29 +77,29 @@ TEST(ExpressionAlgoIsSubsetOf, NullAndOmittedField) { ASSERT_EQUALS(ErrorCodes::BadValue, MatchExpressionParser::parse(undefined, std::move(expCtx)).getStatus()); - ParsedMatchExpressionForTest empty("{}"); - ParsedMatchExpressionForTest null("{a: null}"); + ParsedMatchExpression empty("{}"); + ParsedMatchExpression null("{a: null}"); ASSERT_TRUE(expression::isSubsetOf(null.get(), empty.get())); ASSERT_FALSE(expression::isSubsetOf(empty.get(), null.get())); - ParsedMatchExpressionForTest b1("{b: 1}"); - ParsedMatchExpressionForTest aNullB1("{a: null, b: 1}"); + ParsedMatchExpression b1("{b: 1}"); + ParsedMatchExpression aNullB1("{a: null, b: 1}"); ASSERT_TRUE(expression::isSubsetOf(aNullB1.get(), b1.get())); ASSERT_FALSE(expression::isSubsetOf(b1.get(), aNullB1.get())); - ParsedMatchExpressionForTest a1C3("{a: 1, c: 3}"); - ParsedMatchExpressionForTest a1BNullC3("{a: 1, b: null, c: 3}"); + ParsedMatchExpression a1C3("{a: 1, c: 3}"); + ParsedMatchExpression a1BNullC3("{a: 1, b: null, c: 3}"); ASSERT_TRUE(expression::isSubsetOf(a1BNullC3.get(), a1C3.get())); ASSERT_FALSE(expression::isSubsetOf(a1C3.get(), a1BNullC3.get())); } TEST(ExpressionAlgoIsSubsetOf, NullAndIn) { - ParsedMatchExpressionForTest eqNull("{x: null}"); - ParsedMatchExpressionForTest inNull("{x: {$in: [null]}}"); - ParsedMatchExpressionForTest inNullOr2("{x: {$in: [null, 2]}}"); + ParsedMatchExpression eqNull("{x: null}"); + ParsedMatchExpression inNull("{x: {$in: [null]}}"); + ParsedMatchExpression inNullOr2("{x: {$in: [null, 2]}}"); ASSERT_TRUE(expression::isSubsetOf(inNull.get(), eqNull.get())); ASSERT_FALSE(expression::isSubsetOf(inNullOr2.get(), eqNull.get())); @@ -96,19 +109,19 @@ TEST(ExpressionAlgoIsSubsetOf, NullAndIn) { } TEST(ExpressionAlgoIsSubsetOf, NullAndExists) { - ParsedMatchExpressionForTest null("{x: null}"); - ParsedMatchExpressionForTest exists("{x: {$exists: true}}"); + ParsedMatchExpression null("{x: null}"); + ParsedMatchExpression exists("{x: {$exists: true}}"); ASSERT_FALSE(expression::isSubsetOf(null.get(), exists.get())); ASSERT_FALSE(expression::isSubsetOf(exists.get(), null.get())); } TEST(ExpressionAlgoIsSubsetOf, Compare_NaN) { - ParsedMatchExpressionForTest nan("{x: NaN}"); - ParsedMatchExpressionForTest lt("{x: {$lt: 5}}"); - ParsedMatchExpressionForTest lte("{x: {$lte: 5}}"); - ParsedMatchExpressionForTest gte("{x: {$gte: 5}}"); - ParsedMatchExpressionForTest gt("{x: {$gt: 5}}"); - ParsedMatchExpressionForTest in("{x: {$in: [5]}}"); + ParsedMatchExpression nan("{x: NaN}"); + ParsedMatchExpression lt("{x: {$lt: 5}}"); + ParsedMatchExpression lte("{x: {$lte: 5}}"); + ParsedMatchExpression gte("{x: {$gte: 5}}"); + ParsedMatchExpression gt("{x: {$gt: 5}}"); + ParsedMatchExpression in("{x: {$in: [5]}}"); ASSERT_TRUE(expression::isSubsetOf(nan.get(), nan.get())); ASSERT_FALSE(expression::isSubsetOf(nan.get(), lt.get())); @@ -122,7 +135,7 @@ TEST(ExpressionAlgoIsSubsetOf, Compare_NaN) { ASSERT_FALSE(expression::isSubsetOf(nan.get(), in.get())); ASSERT_FALSE(expression::isSubsetOf(in.get(), nan.get())); - ParsedMatchExpressionForTest decNan("{x : NumberDecimal(\"NaN\") }"); + ParsedMatchExpression decNan("{x : NumberDecimal(\"NaN\") }"); ASSERT_TRUE(expression::isSubsetOf(decNan.get(), decNan.get())); ASSERT_TRUE(expression::isSubsetOf(nan.get(), decNan.get())); ASSERT_TRUE(expression::isSubsetOf(decNan.get(), nan.get())); @@ -137,9 +150,9 @@ TEST(ExpressionAlgoIsSubsetOf, Compare_NaN) { } TEST(ExpressionAlgoIsSubsetOf, Compare_EQ) { - ParsedMatchExpressionForTest a5("{a: 5}"); - ParsedMatchExpressionForTest a6("{a: 6}"); - ParsedMatchExpressionForTest b5("{b: 5}"); + ParsedMatchExpression a5("{a: 5}"); + ParsedMatchExpression a6("{a: 6}"); + ParsedMatchExpression b5("{b: 5}"); ASSERT_TRUE(expression::isSubsetOf(a5.get(), a5.get())); ASSERT_FALSE(expression::isSubsetOf(a5.get(), a6.get())); @@ -147,10 +160,10 @@ TEST(ExpressionAlgoIsSubsetOf, Compare_EQ) { } TEST(ExpressionAlgoIsSubsetOf, CompareAnd_EQ) { - ParsedMatchExpressionForTest a1B2("{a: 1, b: 2}"); - ParsedMatchExpressionForTest a1B7("{a: 1, b: 7}"); - ParsedMatchExpressionForTest a1("{a: 1}"); - ParsedMatchExpressionForTest b2("{b: 2}"); + ParsedMatchExpression a1B2("{a: 1, b: 2}"); + ParsedMatchExpression a1B7("{a: 1, b: 7}"); + ParsedMatchExpression a1("{a: 1}"); + ParsedMatchExpression b2("{b: 2}"); ASSERT_TRUE(expression::isSubsetOf(a1B2.get(), a1B2.get())); ASSERT_FALSE(expression::isSubsetOf(a1B2.get(), a1B7.get())); @@ -161,74 +174,66 @@ TEST(ExpressionAlgoIsSubsetOf, CompareAnd_EQ) { } TEST(ExpressionAlgoIsSubsetOf, CompareAnd_GT) { - ParsedMatchExpressionForTest filter("{a: {$gt: 5}, b: {$gt: 6}}"); - ParsedMatchExpressionForTest query("{a: {$gt: 5}, b: {$gt: 6}, c: {$gt: 7}}"); - - ASSERT_TRUE(expression::isSubsetOf(query.get(), filter.get())); - ASSERT_FALSE(expression::isSubsetOf(filter.get(), query.get())); -} - -TEST(ExpressionAlgoIsSubsetOf, CompareAnd_SingleField) { - ParsedMatchExpressionForTest filter("{a: {$gt: 5, $lt: 7}}"); - ParsedMatchExpressionForTest query("{a: {$gt: 5, $lt: 6}}"); + ParsedMatchExpression filter("{a: {$gt: 5}, b: {$gt: 6}}"); + ParsedMatchExpression query("{a: {$gt: 5}, b: {$gt: 6}, c: {$gt: 7}}"); ASSERT_TRUE(expression::isSubsetOf(query.get(), filter.get())); ASSERT_FALSE(expression::isSubsetOf(filter.get(), query.get())); } TEST(ExpressionAlgoIsSubsetOf, CompareOr_LT) { - ParsedMatchExpressionForTest lt5("{a: {$lt: 5}}"); - ParsedMatchExpressionForTest eq2OrEq3("{$or: [{a: 2}, {a: 3}]}"); - ParsedMatchExpressionForTest eq4OrEq5("{$or: [{a: 4}, {a: 5}]}"); - ParsedMatchExpressionForTest eq4OrEq6("{$or: [{a: 4}, {a: 6}]}"); + ParsedMatchExpression lt5("{a: {$lt: 5}}"); + ParsedMatchExpression eq2OrEq3("{$or: [{a: 2}, {a: 3}]}"); + ParsedMatchExpression eq4OrEq5("{$or: [{a: 4}, {a: 5}]}"); + ParsedMatchExpression eq4OrEq6("{$or: [{a: 4}, {a: 6}]}"); ASSERT_TRUE(expression::isSubsetOf(eq2OrEq3.get(), lt5.get())); ASSERT_FALSE(expression::isSubsetOf(eq4OrEq5.get(), lt5.get())); ASSERT_FALSE(expression::isSubsetOf(eq4OrEq6.get(), lt5.get())); - ParsedMatchExpressionForTest lt4OrLt5("{$or: [{a: {$lt: 4}}, {a: {$lt: 5}}]}"); + ParsedMatchExpression lt4OrLt5("{$or: [{a: {$lt: 4}}, {a: {$lt: 5}}]}"); ASSERT_TRUE(expression::isSubsetOf(lt4OrLt5.get(), lt5.get())); ASSERT_TRUE(expression::isSubsetOf(lt5.get(), lt4OrLt5.get())); - ParsedMatchExpressionForTest lt7OrLt8("{$or: [{a: {$lt: 7}}, {a: {$lt: 8}}]}"); + ParsedMatchExpression lt7OrLt8("{$or: [{a: {$lt: 7}}, {a: {$lt: 8}}]}"); ASSERT_FALSE(expression::isSubsetOf(lt7OrLt8.get(), lt5.get())); ASSERT_TRUE(expression::isSubsetOf(lt5.get(), lt7OrLt8.get())); } TEST(ExpressionAlgoIsSubsetOf, CompareOr_GTE) { - ParsedMatchExpressionForTest gte5("{a: {$gte: 5}}"); - ParsedMatchExpressionForTest eq4OrEq6("{$or: [{a: 4}, {a: 6}]}"); - ParsedMatchExpressionForTest eq5OrEq6("{$or: [{a: 5}, {a: 6}]}"); - ParsedMatchExpressionForTest eq7OrEq8("{$or: [{a: 7}, {a: 8}]}"); + ParsedMatchExpression gte5("{a: {$gte: 5}}"); + ParsedMatchExpression eq4OrEq6("{$or: [{a: 4}, {a: 6}]}"); + ParsedMatchExpression eq5OrEq6("{$or: [{a: 5}, {a: 6}]}"); + ParsedMatchExpression eq7OrEq8("{$or: [{a: 7}, {a: 8}]}"); ASSERT_FALSE(expression::isSubsetOf(eq4OrEq6.get(), gte5.get())); ASSERT_TRUE(expression::isSubsetOf(eq5OrEq6.get(), gte5.get())); ASSERT_TRUE(expression::isSubsetOf(eq7OrEq8.get(), gte5.get())); - ParsedMatchExpressionForTest gte5OrGte6("{$or: [{a: {$gte: 5}}, {a: {$gte: 6}}]}"); + ParsedMatchExpression gte5OrGte6("{$or: [{a: {$gte: 5}}, {a: {$gte: 6}}]}"); ASSERT_TRUE(expression::isSubsetOf(gte5OrGte6.get(), gte5.get())); ASSERT_TRUE(expression::isSubsetOf(gte5.get(), gte5OrGte6.get())); - ParsedMatchExpressionForTest gte3OrGte4("{$or: [{a: {$gte: 3}}, {a: {$gte: 4}}]}"); + ParsedMatchExpression gte3OrGte4("{$or: [{a: {$gte: 3}}, {a: {$gte: 4}}]}"); ASSERT_FALSE(expression::isSubsetOf(gte3OrGte4.get(), gte5.get())); ASSERT_TRUE(expression::isSubsetOf(gte5.get(), gte3OrGte4.get())); } TEST(ExpressionAlgoIsSubsetOf, DifferentCanonicalTypes) { - ParsedMatchExpressionForTest number("{x: {$gt: 1}}"); - ParsedMatchExpressionForTest string("{x: {$gt: 'a'}}"); + ParsedMatchExpression number("{x: {$gt: 1}}"); + ParsedMatchExpression string("{x: {$gt: 'a'}}"); ASSERT_FALSE(expression::isSubsetOf(number.get(), string.get())); ASSERT_FALSE(expression::isSubsetOf(string.get(), number.get())); } TEST(ExpressionAlgoIsSubsetOf, DifferentNumberTypes) { - ParsedMatchExpressionForTest numberDouble("{x: 5.0}"); - ParsedMatchExpressionForTest numberInt("{x: NumberInt(5)}"); - ParsedMatchExpressionForTest numberLong("{x: NumberLong(5)}"); + ParsedMatchExpression numberDouble("{x: 5.0}"); + ParsedMatchExpression numberInt("{x: NumberInt(5)}"); + ParsedMatchExpression numberLong("{x: NumberLong(5)}"); ASSERT_TRUE(expression::isSubsetOf(numberDouble.get(), numberInt.get())); ASSERT_TRUE(expression::isSubsetOf(numberDouble.get(), numberLong.get())); @@ -239,15 +244,15 @@ TEST(ExpressionAlgoIsSubsetOf, DifferentNumberTypes) { } TEST(ExpressionAlgoIsSubsetOf, PointInUnboundedRange) { - ParsedMatchExpressionForTest a4("{a: 4}"); - ParsedMatchExpressionForTest a5("{a: 5}"); - ParsedMatchExpressionForTest a6("{a: 6}"); - ParsedMatchExpressionForTest b5("{b: 5}"); + ParsedMatchExpression a4("{a: 4}"); + ParsedMatchExpression a5("{a: 5}"); + ParsedMatchExpression a6("{a: 6}"); + ParsedMatchExpression b5("{b: 5}"); - ParsedMatchExpressionForTest lt5("{a: {$lt: 5}}"); - ParsedMatchExpressionForTest lte5("{a: {$lte: 5}}"); - ParsedMatchExpressionForTest gte5("{a: {$gte: 5}}"); - ParsedMatchExpressionForTest gt5("{a: {$gt: 5}}"); + ParsedMatchExpression lt5("{a: {$lt: 5}}"); + ParsedMatchExpression lte5("{a: {$lte: 5}}"); + ParsedMatchExpression gte5("{a: {$gte: 5}}"); + ParsedMatchExpression gt5("{a: {$gt: 5}}"); ASSERT_TRUE(expression::isSubsetOf(a4.get(), lte5.get())); ASSERT_TRUE(expression::isSubsetOf(a5.get(), lte5.get())); @@ -279,26 +284,26 @@ TEST(ExpressionAlgoIsSubsetOf, PointInUnboundedRange) { } TEST(ExpressionAlgoIsSubsetOf, PointInBoundedRange) { - ParsedMatchExpressionForTest filter("{a: {$gt: 5, $lt: 10}}"); - ParsedMatchExpressionForTest query("{a: 6}"); + ParsedMatchExpression filter("{a: {$gt: 5, $lt: 10}}"); + ParsedMatchExpression query("{a: 6}"); ASSERT_TRUE(expression::isSubsetOf(query.get(), filter.get())); ASSERT_FALSE(expression::isSubsetOf(filter.get(), query.get())); } TEST(ExpressionAlgoIsSubsetOf, PointInBoundedRange_FakeAnd) { - ParsedMatchExpressionForTest filter("{a: {$gt: 5, $lt: 10}}"); - ParsedMatchExpressionForTest query("{$and: [{a: 6}, {a: 6}]}"); + ParsedMatchExpression filter("{a: {$gt: 5, $lt: 10}}"); + ParsedMatchExpression query("{$and: [{a: 6}, {a: 6}]}"); ASSERT_TRUE(expression::isSubsetOf(query.get(), filter.get())); ASSERT_FALSE(expression::isSubsetOf(filter.get(), query.get())); } TEST(ExpressionAlgoIsSubsetOf, MultiplePointsInBoundedRange) { - ParsedMatchExpressionForTest filter("{a: {$gt: 5, $lt: 10}}"); - ParsedMatchExpressionForTest queryAllInside("{a: {$in: [6, 7, 8]}}"); - ParsedMatchExpressionForTest queryStraddleLower("{a: {$in: [4.9, 5.1]}}"); - ParsedMatchExpressionForTest queryStraddleUpper("{a: {$in: [9.9, 10.1]}}"); + ParsedMatchExpression filter("{a: {$gt: 5, $lt: 10}}"); + ParsedMatchExpression queryAllInside("{a: {$in: [6, 7, 8]}}"); + ParsedMatchExpression queryStraddleLower("{a: {$in: [4.9, 5.1]}}"); + ParsedMatchExpression queryStraddleUpper("{a: {$in: [9.9, 10.1]}}"); ASSERT_TRUE(expression::isSubsetOf(queryAllInside.get(), filter.get())); ASSERT_FALSE(expression::isSubsetOf(queryStraddleLower.get(), filter.get())); @@ -306,18 +311,18 @@ TEST(ExpressionAlgoIsSubsetOf, MultiplePointsInBoundedRange) { } TEST(ExpressionAlgoIsSubsetOf, PointInCompoundRange) { - ParsedMatchExpressionForTest filter("{a: {$gt: 5}, b: {$gt: 6}, c: {$gt: 7}}"); - ParsedMatchExpressionForTest query("{a: 10, b: 10, c: 10}"); + ParsedMatchExpression filter("{a: {$gt: 5}, b: {$gt: 6}, c: {$gt: 7}}"); + ParsedMatchExpression query("{a: 10, b: 10, c: 10}"); ASSERT_TRUE(expression::isSubsetOf(query.get(), filter.get())); ASSERT_FALSE(expression::isSubsetOf(filter.get(), query.get())); } TEST(ExpressionAlgoIsSubsetOf, Compare_LT_LTE) { - ParsedMatchExpressionForTest lte4("{x: {$lte: 4}}"); - ParsedMatchExpressionForTest lt5("{x: {$lt: 5}}"); - ParsedMatchExpressionForTest lte5("{x: {$lte: 5}}"); - ParsedMatchExpressionForTest lt6("{x: {$lt: 6}}"); + ParsedMatchExpression lte4("{x: {$lte: 4}}"); + ParsedMatchExpression lt5("{x: {$lt: 5}}"); + ParsedMatchExpression lte5("{x: {$lte: 5}}"); + ParsedMatchExpression lt6("{x: {$lt: 6}}"); ASSERT_TRUE(expression::isSubsetOf(lte4.get(), lte5.get())); ASSERT_TRUE(expression::isSubsetOf(lt5.get(), lte5.get())); @@ -331,10 +336,10 @@ TEST(ExpressionAlgoIsSubsetOf, Compare_LT_LTE) { } TEST(ExpressionAlgoIsSubsetOf, Compare_GT_GTE) { - ParsedMatchExpressionForTest gte6("{x: {$gte: 6}}"); - ParsedMatchExpressionForTest gt5("{x: {$gt: 5}}"); - ParsedMatchExpressionForTest gte5("{x: {$gte: 5}}"); - ParsedMatchExpressionForTest gt4("{x: {$gt: 4}}"); + ParsedMatchExpression gte6("{x: {$gte: 6}}"); + ParsedMatchExpression gt5("{x: {$gt: 5}}"); + ParsedMatchExpression gte5("{x: {$gte: 5}}"); + ParsedMatchExpression gt4("{x: {$gt: 4}}"); ASSERT_TRUE(expression::isSubsetOf(gte6.get(), gte5.get())); ASSERT_TRUE(expression::isSubsetOf(gt5.get(), gte5.get())); @@ -348,19 +353,18 @@ TEST(ExpressionAlgoIsSubsetOf, Compare_GT_GTE) { } TEST(ExpressionAlgoIsSubsetOf, BoundedRangeInUnboundedRange) { - ParsedMatchExpressionForTest filter("{a: {$gt: 1}}"); - ParsedMatchExpressionForTest query("{a: {$gt: 5, $lt: 10}}"); + ParsedMatchExpression filter("{a: {$gt: 1}}"); + ParsedMatchExpression query("{a: {$gt: 5, $lt: 10}}"); ASSERT_TRUE(expression::isSubsetOf(query.get(), filter.get())); ASSERT_FALSE(expression::isSubsetOf(filter.get(), query.get())); } TEST(ExpressionAlgoIsSubsetOf, MultipleRangesInUnboundedRange) { - ParsedMatchExpressionForTest filter("{a: {$gt: 1}}"); - ParsedMatchExpressionForTest negative("{$or: [{a: {$gt: 5, $lt: 10}}, {a: {$lt: 0}}]}"); - ParsedMatchExpressionForTest unbounded("{$or: [{a: {$gt: 5, $lt: 10}}, {a: {$gt: 15}}]}"); - ParsedMatchExpressionForTest bounded( - "{$or: [{a: {$gt: 5, $lt: 10}}, {a: {$gt: 20, $lt: 30}}]}"); + ParsedMatchExpression filter("{a: {$gt: 1}}"); + ParsedMatchExpression negative("{$or: [{a: {$gt: 5, $lt: 10}}, {a: {$lt: 0}}]}"); + ParsedMatchExpression unbounded("{$or: [{a: {$gt: 5, $lt: 10}}, {a: {$gt: 15}}]}"); + ParsedMatchExpression bounded("{$or: [{a: {$gt: 5, $lt: 10}}, {a: {$gt: 20, $lt: 30}}]}"); ASSERT_FALSE(expression::isSubsetOf(negative.get(), filter.get())); ASSERT_TRUE(expression::isSubsetOf(unbounded.get(), filter.get())); @@ -368,10 +372,10 @@ TEST(ExpressionAlgoIsSubsetOf, MultipleRangesInUnboundedRange) { } TEST(ExpressionAlgoIsSubsetOf, MultipleFields) { - ParsedMatchExpressionForTest filter("{a: {$gt: 5}, b: {$lt: 10}}"); - ParsedMatchExpressionForTest onlyA("{$or: [{a: 6, b: {$lt: 4}}, {a: {$gt: 11}}]}"); - ParsedMatchExpressionForTest onlyB("{$or: [{b: {$lt: 4}}, {a: {$gt: 11}, b: 9}]}"); - ParsedMatchExpressionForTest both("{$or: [{a: 6, b: {$lt: 4}}, {a: {$gt: 11}, b: 9}]}"); + ParsedMatchExpression filter("{a: {$gt: 5}, b: {$lt: 10}}"); + ParsedMatchExpression onlyA("{$or: [{a: 6, b: {$lt: 4}}, {a: {$gt: 11}}]}"); + ParsedMatchExpression onlyB("{$or: [{b: {$lt: 4}}, {a: {$gt: 11}, b: 9}]}"); + ParsedMatchExpression both("{$or: [{a: 6, b: {$lt: 4}}, {a: {$gt: 11}, b: 9}]}"); ASSERT_FALSE(expression::isSubsetOf(onlyA.get(), filter.get())); ASSERT_FALSE(expression::isSubsetOf(onlyB.get(), filter.get())); @@ -379,18 +383,18 @@ TEST(ExpressionAlgoIsSubsetOf, MultipleFields) { } TEST(ExpressionAlgoIsSubsetOf, Compare_LT_In) { - ParsedMatchExpressionForTest lt("{a: {$lt: 5}}"); + ParsedMatchExpression lt("{a: {$lt: 5}}"); - ParsedMatchExpressionForTest inLt("{a: {$in: [4.9]}}"); - ParsedMatchExpressionForTest inEq("{a: {$in: [5]}}"); - ParsedMatchExpressionForTest inGt("{a: {$in: [5.1]}}"); - ParsedMatchExpressionForTest inNull("{a: {$in: [null]}}"); + ParsedMatchExpression inLt("{a: {$in: [4.9]}}"); + ParsedMatchExpression inEq("{a: {$in: [5]}}"); + ParsedMatchExpression inGt("{a: {$in: [5.1]}}"); + ParsedMatchExpression inNull("{a: {$in: [null]}}"); - ParsedMatchExpressionForTest inAllEq("{a: {$in: [5, 5.0]}}"); - ParsedMatchExpressionForTest inAllLte("{a: {$in: [4.9, 5]}}"); - ParsedMatchExpressionForTest inAllLt("{a: {$in: [2, 3, 4]}}"); - ParsedMatchExpressionForTest inStraddle("{a: {$in: [4, 6]}}"); - ParsedMatchExpressionForTest inLtAndNull("{a: {$in: [1, null]}}"); + ParsedMatchExpression inAllEq("{a: {$in: [5, 5.0]}}"); + ParsedMatchExpression inAllLte("{a: {$in: [4.9, 5]}}"); + ParsedMatchExpression inAllLt("{a: {$in: [2, 3, 4]}}"); + ParsedMatchExpression inStraddle("{a: {$in: [4, 6]}}"); + ParsedMatchExpression inLtAndNull("{a: {$in: [1, null]}}"); ASSERT_TRUE(expression::isSubsetOf(inLt.get(), lt.get())); ASSERT_FALSE(expression::isSubsetOf(inEq.get(), lt.get())); @@ -407,18 +411,18 @@ TEST(ExpressionAlgoIsSubsetOf, Compare_LT_In) { } TEST(ExpressionAlgoIsSubsetOf, Compare_LTE_In) { - ParsedMatchExpressionForTest lte("{a: {$lte: 5}}"); + ParsedMatchExpression lte("{a: {$lte: 5}}"); - ParsedMatchExpressionForTest inLt("{a: {$in: [4.9]}}"); - ParsedMatchExpressionForTest inEq("{a: {$in: [5]}}"); - ParsedMatchExpressionForTest inGt("{a: {$in: [5.1]}}"); - ParsedMatchExpressionForTest inNull("{a: {$in: [null]}}"); + ParsedMatchExpression inLt("{a: {$in: [4.9]}}"); + ParsedMatchExpression inEq("{a: {$in: [5]}}"); + ParsedMatchExpression inGt("{a: {$in: [5.1]}}"); + ParsedMatchExpression inNull("{a: {$in: [null]}}"); - ParsedMatchExpressionForTest inAllEq("{a: {$in: [5, 5.0]}}"); - ParsedMatchExpressionForTest inAllLte("{a: {$in: [4.9, 5]}}"); - ParsedMatchExpressionForTest inAllLt("{a: {$in: [2, 3, 4]}}"); - ParsedMatchExpressionForTest inStraddle("{a: {$in: [4, 6]}}"); - ParsedMatchExpressionForTest inLtAndNull("{a: {$in: [1, null]}}"); + ParsedMatchExpression inAllEq("{a: {$in: [5, 5.0]}}"); + ParsedMatchExpression inAllLte("{a: {$in: [4.9, 5]}}"); + ParsedMatchExpression inAllLt("{a: {$in: [2, 3, 4]}}"); + ParsedMatchExpression inStraddle("{a: {$in: [4, 6]}}"); + ParsedMatchExpression inLtAndNull("{a: {$in: [1, null]}}"); ASSERT_TRUE(expression::isSubsetOf(inLt.get(), lte.get())); ASSERT_TRUE(expression::isSubsetOf(inEq.get(), lte.get())); @@ -435,16 +439,16 @@ TEST(ExpressionAlgoIsSubsetOf, Compare_LTE_In) { } TEST(ExpressionAlgoIsSubsetOf, Compare_EQ_In) { - ParsedMatchExpressionForTest eq("{a: 5}"); + ParsedMatchExpression eq("{a: 5}"); - ParsedMatchExpressionForTest inLt("{a: {$in: [4.9]}}"); - ParsedMatchExpressionForTest inEq("{a: {$in: [5]}}"); - ParsedMatchExpressionForTest inGt("{a: {$in: [5.1]}}"); - ParsedMatchExpressionForTest inNull("{a: {$in: [null]}}"); + ParsedMatchExpression inLt("{a: {$in: [4.9]}}"); + ParsedMatchExpression inEq("{a: {$in: [5]}}"); + ParsedMatchExpression inGt("{a: {$in: [5.1]}}"); + ParsedMatchExpression inNull("{a: {$in: [null]}}"); - ParsedMatchExpressionForTest inAllEq("{a: {$in: [5, 5.0]}}"); - ParsedMatchExpressionForTest inStraddle("{a: {$in: [4, 6]}}"); - ParsedMatchExpressionForTest inEqAndNull("{a: {$in: [5, null]}}"); + ParsedMatchExpression inAllEq("{a: {$in: [5, 5.0]}}"); + ParsedMatchExpression inStraddle("{a: {$in: [4, 6]}}"); + ParsedMatchExpression inEqAndNull("{a: {$in: [5, null]}}"); ASSERT_FALSE(expression::isSubsetOf(inLt.get(), eq.get())); ASSERT_TRUE(expression::isSubsetOf(inEq.get(), eq.get())); @@ -459,18 +463,18 @@ TEST(ExpressionAlgoIsSubsetOf, Compare_EQ_In) { } TEST(ExpressionAlgoIsSubsetOf, Compare_GT_In) { - ParsedMatchExpressionForTest gt("{a: {$gt: 5}}"); + ParsedMatchExpression gt("{a: {$gt: 5}}"); - ParsedMatchExpressionForTest inLt("{a: {$in: [4.9]}}"); - ParsedMatchExpressionForTest inEq("{a: {$in: [5]}}"); - ParsedMatchExpressionForTest inGt("{a: {$in: [5.1]}}"); - ParsedMatchExpressionForTest inNull("{a: {$in: [null]}}"); + ParsedMatchExpression inLt("{a: {$in: [4.9]}}"); + ParsedMatchExpression inEq("{a: {$in: [5]}}"); + ParsedMatchExpression inGt("{a: {$in: [5.1]}}"); + ParsedMatchExpression inNull("{a: {$in: [null]}}"); - ParsedMatchExpressionForTest inAllEq("{a: {$in: [5, 5.0]}}"); - ParsedMatchExpressionForTest inAllGte("{a: {$in: [5, 5.1]}}"); - ParsedMatchExpressionForTest inAllGt("{a: {$in: [6, 7, 8]}}"); - ParsedMatchExpressionForTest inStraddle("{a: {$in: [4, 6]}}"); - ParsedMatchExpressionForTest inGtAndNull("{a: {$in: [9, null]}}"); + ParsedMatchExpression inAllEq("{a: {$in: [5, 5.0]}}"); + ParsedMatchExpression inAllGte("{a: {$in: [5, 5.1]}}"); + ParsedMatchExpression inAllGt("{a: {$in: [6, 7, 8]}}"); + ParsedMatchExpression inStraddle("{a: {$in: [4, 6]}}"); + ParsedMatchExpression inGtAndNull("{a: {$in: [9, null]}}"); ASSERT_FALSE(expression::isSubsetOf(inLt.get(), gt.get())); ASSERT_FALSE(expression::isSubsetOf(inEq.get(), gt.get())); @@ -487,18 +491,18 @@ TEST(ExpressionAlgoIsSubsetOf, Compare_GT_In) { } TEST(ExpressionAlgoIsSubsetOf, Compare_GTE_In) { - ParsedMatchExpressionForTest gte("{a: {$gte: 5}}"); + ParsedMatchExpression gte("{a: {$gte: 5}}"); - ParsedMatchExpressionForTest inLt("{a: {$in: [4.9]}}"); - ParsedMatchExpressionForTest inEq("{a: {$in: [5]}}"); - ParsedMatchExpressionForTest inGt("{a: {$in: [5.1]}}"); - ParsedMatchExpressionForTest inNull("{a: {$in: [null]}}"); + ParsedMatchExpression inLt("{a: {$in: [4.9]}}"); + ParsedMatchExpression inEq("{a: {$in: [5]}}"); + ParsedMatchExpression inGt("{a: {$in: [5.1]}}"); + ParsedMatchExpression inNull("{a: {$in: [null]}}"); - ParsedMatchExpressionForTest inAllEq("{a: {$in: [5, 5.0]}}"); - ParsedMatchExpressionForTest inAllGte("{a: {$in: [5, 5.1]}}"); - ParsedMatchExpressionForTest inAllGt("{a: {$in: [6, 7, 8]}}"); - ParsedMatchExpressionForTest inStraddle("{a: {$in: [4, 6]}}"); - ParsedMatchExpressionForTest inGtAndNull("{a: {$in: [9, null]}}"); + ParsedMatchExpression inAllEq("{a: {$in: [5, 5.0]}}"); + ParsedMatchExpression inAllGte("{a: {$in: [5, 5.1]}}"); + ParsedMatchExpression inAllGt("{a: {$in: [6, 7, 8]}}"); + ParsedMatchExpression inStraddle("{a: {$in: [4, 6]}}"); + ParsedMatchExpression inGtAndNull("{a: {$in: [9, null]}}"); ASSERT_FALSE(expression::isSubsetOf(inLt.get(), gte.get())); ASSERT_TRUE(expression::isSubsetOf(inEq.get(), gte.get())); @@ -515,12 +519,12 @@ TEST(ExpressionAlgoIsSubsetOf, Compare_GTE_In) { } TEST(ExpressionAlgoIsSubsetOf, RegexAndIn) { - ParsedMatchExpressionForTest eq1("{x: 1}"); - ParsedMatchExpressionForTest eqA("{x: 'a'}"); - ParsedMatchExpressionForTest inRegexA("{x: {$in: [/a/]}}"); - ParsedMatchExpressionForTest inRegexAbc("{x: {$in: [/abc/]}}"); - ParsedMatchExpressionForTest inRegexAOrEq1("{x: {$in: [/a/, 1]}}"); - ParsedMatchExpressionForTest inRegexAOrNull("{x: {$in: [/a/, null]}}"); + ParsedMatchExpression eq1("{x: 1}"); + ParsedMatchExpression eqA("{x: 'a'}"); + ParsedMatchExpression inRegexA("{x: {$in: [/a/]}}"); + ParsedMatchExpression inRegexAbc("{x: {$in: [/abc/]}}"); + ParsedMatchExpression inRegexAOrEq1("{x: {$in: [/a/, 1]}}"); + ParsedMatchExpression inRegexAOrNull("{x: {$in: [/a/, null]}}"); ASSERT_FALSE(expression::isSubsetOf(inRegexAOrEq1.get(), eq1.get())); ASSERT_FALSE(expression::isSubsetOf(inRegexA.get(), eqA.get())); @@ -532,10 +536,10 @@ TEST(ExpressionAlgoIsSubsetOf, RegexAndIn) { } TEST(ExpressionAlgoIsSubsetOf, Exists) { - ParsedMatchExpressionForTest aExists("{a: {$exists: true}}"); - ParsedMatchExpressionForTest bExists("{b: {$exists: true}}"); - ParsedMatchExpressionForTest aExistsBExists("{a: {$exists: true}, b: {$exists: true}}"); - ParsedMatchExpressionForTest aExistsBExistsC5("{a: {$exists: true}, b: {$exists: true}, c: 5}"); + ParsedMatchExpression aExists("{a: {$exists: true}}"); + ParsedMatchExpression bExists("{b: {$exists: true}}"); + ParsedMatchExpression aExistsBExists("{a: {$exists: true}, b: {$exists: true}}"); + ParsedMatchExpression aExistsBExistsC5("{a: {$exists: true}, b: {$exists: true}, c: 5}"); ASSERT_TRUE(expression::isSubsetOf(aExists.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(aExists.get(), bExists.get())); @@ -550,10 +554,10 @@ TEST(ExpressionAlgoIsSubsetOf, Exists) { } TEST(ExpressionAlgoIsSubsetOf, Compare_Exists) { - ParsedMatchExpressionForTest exists("{a: {$exists: true}}"); - ParsedMatchExpressionForTest eq("{a: 1}"); - ParsedMatchExpressionForTest gt("{a: {$gt: 4}}"); - ParsedMatchExpressionForTest lte("{a: {$lte: 7}}"); + ParsedMatchExpression exists("{a: {$exists: true}}"); + ParsedMatchExpression eq("{a: 1}"); + ParsedMatchExpression gt("{a: {$gt: 4}}"); + ParsedMatchExpression lte("{a: {$lte: 7}}"); ASSERT_TRUE(expression::isSubsetOf(eq.get(), exists.get())); ASSERT_TRUE(expression::isSubsetOf(gt.get(), exists.get())); @@ -565,9 +569,9 @@ TEST(ExpressionAlgoIsSubsetOf, Compare_Exists) { } TEST(ExpressionAlgoIsSubsetOf, Type) { - ParsedMatchExpressionForTest aType1("{a: {$type: 1}}"); - ParsedMatchExpressionForTest aType2("{a: {$type: 2}}"); - ParsedMatchExpressionForTest bType2("{b: {$type: 2}}"); + ParsedMatchExpression aType1("{a: {$type: 1}}"); + ParsedMatchExpression aType2("{a: {$type: 2}}"); + ParsedMatchExpression bType2("{b: {$type: 2}}"); ASSERT_FALSE(expression::isSubsetOf(aType1.get(), aType2.get())); ASSERT_FALSE(expression::isSubsetOf(aType2.get(), aType1.get())); @@ -577,9 +581,9 @@ TEST(ExpressionAlgoIsSubsetOf, Type) { } TEST(ExpressionAlgoIsSubsetOf, TypeAndExists) { - ParsedMatchExpressionForTest aExists("{a: {$exists: true}}"); - ParsedMatchExpressionForTest aType2("{a: {$type: 2}}"); - ParsedMatchExpressionForTest bType2("{b: {$type: 2}}"); + ParsedMatchExpression aExists("{a: {$exists: true}}"); + ParsedMatchExpression aType2("{a: {$type: 2}}"); + ParsedMatchExpression bType2("{b: {$type: 2}}"); ASSERT_TRUE(expression::isSubsetOf(aType2.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(aExists.get(), aType2.get())); @@ -587,10 +591,10 @@ TEST(ExpressionAlgoIsSubsetOf, TypeAndExists) { } TEST(ExpressionAlgoIsSubsetOf, AllAndExists) { - ParsedMatchExpressionForTest aExists("{a: {$exists: true}}"); - ParsedMatchExpressionForTest aAll("{a: {$all: ['x', 'y', 'z']}}"); - ParsedMatchExpressionForTest bAll("{b: {$all: ['x', 'y', 'z']}}"); - ParsedMatchExpressionForTest aAllWithNull("{a: {$all: ['x', null, 'z']}}"); + ParsedMatchExpression aExists("{a: {$exists: true}}"); + ParsedMatchExpression aAll("{a: {$all: ['x', 'y', 'z']}}"); + ParsedMatchExpression bAll("{b: {$all: ['x', 'y', 'z']}}"); + ParsedMatchExpression aAllWithNull("{a: {$all: ['x', null, 'z']}}"); ASSERT_TRUE(expression::isSubsetOf(aAll.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(bAll.get(), aExists.get())); @@ -598,10 +602,10 @@ TEST(ExpressionAlgoIsSubsetOf, AllAndExists) { } TEST(ExpressionAlgoIsSubsetOf, ElemMatchAndExists_Value) { - ParsedMatchExpressionForTest aExists("{a: {$exists: true}}"); - ParsedMatchExpressionForTest aElemMatch("{a: {$elemMatch: {$gt: 5, $lte: 10}}}"); - ParsedMatchExpressionForTest bElemMatch("{b: {$elemMatch: {$gt: 5, $lte: 10}}}"); - ParsedMatchExpressionForTest aElemMatchNull("{a: {$elemMatch: {$eq: null}}}"); + ParsedMatchExpression aExists("{a: {$exists: true}}"); + ParsedMatchExpression aElemMatch("{a: {$elemMatch: {$gt: 5, $lte: 10}}}"); + ParsedMatchExpression bElemMatch("{b: {$elemMatch: {$gt: 5, $lte: 10}}}"); + ParsedMatchExpression aElemMatchNull("{a: {$elemMatch: {$eq: null}}}"); ASSERT_TRUE(expression::isSubsetOf(aElemMatch.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(aExists.get(), aElemMatch.get())); @@ -610,10 +614,10 @@ TEST(ExpressionAlgoIsSubsetOf, ElemMatchAndExists_Value) { } TEST(ExpressionAlgoIsSubsetOf, ElemMatchAndExists_Object) { - ParsedMatchExpressionForTest aExists("{a: {$exists: true}}"); - ParsedMatchExpressionForTest aElemMatch("{a: {$elemMatch: {x: {$gt: 5}, y: {$lte: 10}}}}"); - ParsedMatchExpressionForTest bElemMatch("{b: {$elemMatch: {x: {$gt: 5}, y: {$lte: 10}}}}"); - ParsedMatchExpressionForTest aElemMatchNull("{a: {$elemMatch: {x: null, y: null}}}"); + ParsedMatchExpression aExists("{a: {$exists: true}}"); + ParsedMatchExpression aElemMatch("{a: {$elemMatch: {x: {$gt: 5}, y: {$lte: 10}}}}"); + ParsedMatchExpression bElemMatch("{b: {$elemMatch: {x: {$gt: 5}, y: {$lte: 10}}}}"); + ParsedMatchExpression aElemMatchNull("{a: {$elemMatch: {x: null, y: null}}}"); ASSERT_TRUE(expression::isSubsetOf(aElemMatch.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(aExists.get(), aElemMatch.get())); @@ -622,11 +626,11 @@ TEST(ExpressionAlgoIsSubsetOf, ElemMatchAndExists_Object) { } TEST(ExpressionAlgoIsSubsetOf, SizeAndExists) { - ParsedMatchExpressionForTest aExists("{a: {$exists: true}}"); - ParsedMatchExpressionForTest aSize0("{a: {$size: 0}}"); - ParsedMatchExpressionForTest aSize1("{a: {$size: 1}}"); - ParsedMatchExpressionForTest aSize3("{a: {$size: 3}}"); - ParsedMatchExpressionForTest bSize3("{b: {$size: 3}}"); + ParsedMatchExpression aExists("{a: {$exists: true}}"); + ParsedMatchExpression aSize0("{a: {$size: 0}}"); + ParsedMatchExpression aSize1("{a: {$size: 1}}"); + ParsedMatchExpression aSize3("{a: {$size: 3}}"); + ParsedMatchExpression bSize3("{b: {$size: 3}}"); ASSERT_TRUE(expression::isSubsetOf(aSize0.get(), aExists.get())); ASSERT_TRUE(expression::isSubsetOf(aSize1.get(), aExists.get())); @@ -636,28 +640,28 @@ TEST(ExpressionAlgoIsSubsetOf, SizeAndExists) { } TEST(ExpressionAlgoIsSubsetOf, ModAndExists) { - ParsedMatchExpressionForTest aExists("{a: {$exists: true}}"); - ParsedMatchExpressionForTest aMod5("{a: {$mod: [5, 0]}}"); - ParsedMatchExpressionForTest bMod5("{b: {$mod: [5, 0]}}"); + ParsedMatchExpression aExists("{a: {$exists: true}}"); + ParsedMatchExpression aMod5("{a: {$mod: [5, 0]}}"); + ParsedMatchExpression bMod5("{b: {$mod: [5, 0]}}"); ASSERT_TRUE(expression::isSubsetOf(aMod5.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(bMod5.get(), aExists.get())); } TEST(ExpressionAlgoIsSubsetOf, RegexAndExists) { - ParsedMatchExpressionForTest aExists("{a: {$exists: true}}"); - ParsedMatchExpressionForTest aRegex("{a: {$regex: 'pattern'}}"); - ParsedMatchExpressionForTest bRegex("{b: {$regex: 'pattern'}}"); + ParsedMatchExpression aExists("{a: {$exists: true}}"); + ParsedMatchExpression aRegex("{a: {$regex: 'pattern'}}"); + ParsedMatchExpression bRegex("{b: {$regex: 'pattern'}}"); ASSERT_TRUE(expression::isSubsetOf(aRegex.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(bRegex.get(), aExists.get())); } TEST(ExpressionAlgoIsSubsetOf, InAndExists) { - ParsedMatchExpressionForTest aExists("{a: {$exists: true}}"); - ParsedMatchExpressionForTest aIn("{a: {$in: [1, 2, 3]}}"); - ParsedMatchExpressionForTest bIn("{b: {$in: [1, 2, 3]}}"); - ParsedMatchExpressionForTest aInWithNull("{a: {$in: [1, null, 3]}}"); + ParsedMatchExpression aExists("{a: {$exists: true}}"); + ParsedMatchExpression aIn("{a: {$in: [1, 2, 3]}}"); + ParsedMatchExpression bIn("{b: {$in: [1, 2, 3]}}"); + ParsedMatchExpression aInWithNull("{a: {$in: [1, null, 3]}}"); ASSERT_TRUE(expression::isSubsetOf(aIn.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(bIn.get(), aExists.get())); @@ -669,10 +673,10 @@ TEST(ExpressionAlgoIsSubsetOf, InAndExists) { } TEST(ExpressionAlgoIsSubsetOf, NinAndExists) { - ParsedMatchExpressionForTest aExists("{a: {$exists: true}}"); - ParsedMatchExpressionForTest aNin("{a: {$nin: [1, 2, 3]}}"); - ParsedMatchExpressionForTest bNin("{b: {$nin: [1, 2, 3]}}"); - ParsedMatchExpressionForTest aNinWithNull("{a: {$nin: [1, null, 3]}}"); + ParsedMatchExpression aExists("{a: {$exists: true}}"); + ParsedMatchExpression aNin("{a: {$nin: [1, 2, 3]}}"); + ParsedMatchExpression bNin("{b: {$nin: [1, 2, 3]}}"); + ParsedMatchExpression aNinWithNull("{a: {$nin: [1, null, 3]}}"); ASSERT_FALSE(expression::isSubsetOf(aNin.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(bNin.get(), aExists.get())); @@ -680,10 +684,10 @@ TEST(ExpressionAlgoIsSubsetOf, NinAndExists) { } TEST(ExpressionAlgoIsSubsetOf, Compare_Exists_NE) { - ParsedMatchExpressionForTest aExists("{a: {$exists: true}}"); - ParsedMatchExpressionForTest aNotEqual1("{a: {$ne: 1}}"); - ParsedMatchExpressionForTest bNotEqual1("{b: {$ne: 1}}"); - ParsedMatchExpressionForTest aNotEqualNull("{a: {$ne: null}}"); + ParsedMatchExpression aExists("{a: {$exists: true}}"); + ParsedMatchExpression aNotEqual1("{a: {$ne: 1}}"); + ParsedMatchExpression bNotEqual1("{b: {$ne: 1}}"); + ParsedMatchExpression aNotEqualNull("{a: {$ne: null}}"); ASSERT_FALSE(expression::isSubsetOf(aNotEqual1.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(bNotEqual1.get(), aExists.get())); @@ -692,13 +696,13 @@ TEST(ExpressionAlgoIsSubsetOf, Compare_Exists_NE) { TEST(ExpressionAlgoIsSubsetOf, CollationAwareStringComparison) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - ParsedMatchExpressionForTest lhs("{a: {$gt: 'abc'}}", &collator); - ParsedMatchExpressionForTest rhs("{a: {$gt: 'cba'}}", &collator); + ParsedMatchExpression lhs("{a: {$gt: 'abc'}}", &collator); + ParsedMatchExpression rhs("{a: {$gt: 'cba'}}", &collator); ASSERT_TRUE(expression::isSubsetOf(lhs.get(), rhs.get())); - ParsedMatchExpressionForTest lhsLT("{a: {$lt: 'abc'}}", &collator); - ParsedMatchExpressionForTest rhsLT("{a: {$lt: 'cba'}}", &collator); + ParsedMatchExpression lhsLT("{a: {$lt: 'abc'}}", &collator); + ParsedMatchExpression rhsLT("{a: {$lt: 'cba'}}", &collator); ASSERT_FALSE(expression::isSubsetOf(lhsLT.get(), rhsLT.get())); } @@ -706,27 +710,27 @@ TEST(ExpressionAlgoIsSubsetOf, CollationAwareStringComparison) { TEST(ExpressionAlgoIsSubsetOf, NonMatchingCollationsStringComparison) { CollatorInterfaceMock collatorAlwaysEqual(CollatorInterfaceMock::MockType::kAlwaysEqual); CollatorInterfaceMock collatorReverseString(CollatorInterfaceMock::MockType::kReverseString); - ParsedMatchExpressionForTest lhs("{a: {$gt: 'abc'}}", &collatorAlwaysEqual); - ParsedMatchExpressionForTest rhs("{a: {$gt: 'cba'}}", &collatorReverseString); + ParsedMatchExpression lhs("{a: {$gt: 'abc'}}", &collatorAlwaysEqual); + ParsedMatchExpression rhs("{a: {$gt: 'cba'}}", &collatorReverseString); ASSERT_FALSE(expression::isSubsetOf(lhs.get(), rhs.get())); - ParsedMatchExpressionForTest lhsLT("{a: {$lt: 'abc'}}", &collatorAlwaysEqual); - ParsedMatchExpressionForTest rhsLT("{a: {$lt: 'cba'}}", &collatorReverseString); + ParsedMatchExpression lhsLT("{a: {$lt: 'abc'}}", &collatorAlwaysEqual); + ParsedMatchExpression rhsLT("{a: {$lt: 'cba'}}", &collatorReverseString); ASSERT_FALSE(expression::isSubsetOf(lhsLT.get(), rhsLT.get())); } TEST(ExpressionAlgoIsSubsetOf, CollationAwareStringComparisonIn) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - ParsedMatchExpressionForTest lhsAllGTcba("{a: {$in: ['abc', 'cbc']}}", &collator); - ParsedMatchExpressionForTest lhsSomeGTcba("{a: {$in: ['abc', 'aba']}}", &collator); - ParsedMatchExpressionForTest rhs("{a: {$gt: 'cba'}}", &collator); + ParsedMatchExpression lhsAllGTcba("{a: {$in: ['abc', 'cbc']}}", &collator); + ParsedMatchExpression lhsSomeGTcba("{a: {$in: ['abc', 'aba']}}", &collator); + ParsedMatchExpression rhs("{a: {$gt: 'cba'}}", &collator); ASSERT_TRUE(expression::isSubsetOf(lhsAllGTcba.get(), rhs.get())); ASSERT_FALSE(expression::isSubsetOf(lhsSomeGTcba.get(), rhs.get())); - ParsedMatchExpressionForTest rhsLT("{a: {$lt: 'cba'}}", &collator); + ParsedMatchExpression rhsLT("{a: {$lt: 'cba'}}", &collator); ASSERT_FALSE(expression::isSubsetOf(lhsAllGTcba.get(), rhsLT.get())); ASSERT_FALSE(expression::isSubsetOf(lhsSomeGTcba.get(), rhsLT.get())); @@ -736,8 +740,8 @@ TEST(ExpressionAlgoIsSubsetOf, CollationAwareStringComparisonIn) { TEST(ExpressionAlgoIsSubsetOf, NonMatchingCollationsNoStringComparisonLHS) { CollatorInterfaceMock collatorAlwaysEqual(CollatorInterfaceMock::MockType::kAlwaysEqual); CollatorInterfaceMock collatorReverseString(CollatorInterfaceMock::MockType::kReverseString); - ParsedMatchExpressionForTest lhs("{a: {b: 1}}", &collatorAlwaysEqual); - ParsedMatchExpressionForTest rhs("{a: {$lt: {b: 'abc'}}}", &collatorReverseString); + ParsedMatchExpression lhs("{a: {b: 1}}", &collatorAlwaysEqual); + ParsedMatchExpression rhs("{a: {$lt: {b: 'abc'}}}", &collatorReverseString); ASSERT_FALSE(expression::isSubsetOf(lhs.get(), rhs.get())); } @@ -745,75 +749,72 @@ TEST(ExpressionAlgoIsSubsetOf, NonMatchingCollationsNoStringComparisonLHS) { TEST(ExpressionAlgoIsSubsetOf, NonMatchingCollationsNoStringComparison) { CollatorInterfaceMock collatorAlwaysEqual(CollatorInterfaceMock::MockType::kAlwaysEqual); CollatorInterfaceMock collatorReverseString(CollatorInterfaceMock::MockType::kReverseString); - ParsedMatchExpressionForTest lhs("{a: 1}", &collatorAlwaysEqual); - ParsedMatchExpressionForTest rhs("{a: {$gt: 0}}", &collatorReverseString); + ParsedMatchExpression lhs("{a: 1}", &collatorAlwaysEqual); + ParsedMatchExpression rhs("{a: {$gt: 0}}", &collatorReverseString); ASSERT_TRUE(expression::isSubsetOf(lhs.get(), rhs.get())); } TEST(ExpressionAlgoIsSubsetOf, InternalExprEqIsSubsetOfNothing) { - ParsedMatchExpressionForTest exprEq("{a: {$_internalExprEq: 0}}"); - ParsedMatchExpressionForTest regularEq("{a: {$eq: 0}}"); + ParsedMatchExpression exprEq("{a: {$_internalExprEq: 0}}"); + ParsedMatchExpression regularEq("{a: {$eq: 0}}"); { - ParsedMatchExpressionForTest rhs("{a: {$gte: 0}}"); + ParsedMatchExpression rhs("{a: {$gte: 0}}"); ASSERT_FALSE(expression::isSubsetOf(exprEq.get(), rhs.get())); ASSERT_TRUE(expression::isSubsetOf(regularEq.get(), rhs.get())); } { - ParsedMatchExpressionForTest rhs("{a: {$lte: 0}}"); + ParsedMatchExpression rhs("{a: {$lte: 0}}"); ASSERT_FALSE(expression::isSubsetOf(exprEq.get(), rhs.get())); ASSERT_TRUE(expression::isSubsetOf(regularEq.get(), rhs.get())); } } TEST(ExpressionAlgoIsSubsetOf, IsSubsetOfRHSAndWithinOr) { - ParsedMatchExpressionForTest rhs("{$or: [{a: 3}, {$and: [{a: 5}, {b: 5}]}]}"); + ParsedMatchExpression rhs("{$or: [{a: 3}, {$and: [{a: 5}, {b: 5}]}]}"); { - ParsedMatchExpressionForTest lhs("{a:5, b:5}"); + ParsedMatchExpression lhs("{a:5, b:5}"); ASSERT_TRUE(expression::isSubsetOf(lhs.get(), rhs.get())); } } TEST(ExpressionAlgoIsSubsetOf, IsSubsetOfComplexRHSExpression) { - ParsedMatchExpressionForTest complex( - "{$or: [{z: 1}, {$and: [{x: 1}, {$or: [{y: 1}, {y: 2}]}]}]}"); + ParsedMatchExpression complex("{$or: [{z: 1}, {$and: [{x: 1}, {$or: [{y: 1}, {y: 2}]}]}]}"); { - ParsedMatchExpressionForTest lhs("{z: 1}"); + ParsedMatchExpression lhs("{z: 1}"); ASSERT_TRUE(expression::isSubsetOf(lhs.get(), complex.get())); } { - ParsedMatchExpressionForTest lhs("{z: 1, x: 1, y:2}"); + ParsedMatchExpression lhs("{z: 1, x: 1, y:2}"); ASSERT_TRUE(expression::isSubsetOf(lhs.get(), complex.get())); } { - ParsedMatchExpressionForTest lhs( - "{$or: [{z: 1}, {$and: [{x: 1}, {$or: [{y: 1}, {y: 2}]}]}]}"); + ParsedMatchExpression lhs("{$or: [{z: 1}, {$and: [{x: 1}, {$or: [{y: 1}, {y: 2}]}]}]}"); ASSERT_TRUE(expression::isSubsetOf(lhs.get(), complex.get())); } { - ParsedMatchExpressionForTest lhs( - "{$or: [{z: 2}, {$and: [{x: 2}, {$or: [{y: 3}, {y: 4}]}]}]}"); + ParsedMatchExpression lhs("{$or: [{z: 2}, {$and: [{x: 2}, {$or: [{y: 3}, {y: 4}]}]}]}"); ASSERT_FALSE(expression::isSubsetOf(lhs.get(), complex.get())); } { - ParsedMatchExpressionForTest lhs("{z: 1, y:2}"); + ParsedMatchExpression lhs("{z: 1, y:2}"); ASSERT_TRUE(expression::isSubsetOf(lhs.get(), complex.get())); } { - ParsedMatchExpressionForTest lhs("{z: 2, y: 1}"); + ParsedMatchExpression lhs("{z: 2, y: 1}"); ASSERT_FALSE(expression::isSubsetOf(lhs.get(), complex.get())); } { - ParsedMatchExpressionForTest lhs("{x: 1, y: 3}"); + ParsedMatchExpression lhs("{x: 1, y: 3}"); ASSERT_FALSE(expression::isSubsetOf(lhs.get(), complex.get())); } } @@ -930,15 +931,6 @@ TEST(IsIndependent, NonRenameableExpressionIsNotIndependent) { } } -TEST(IsIndependent, EmptyDependencySetsPassIsOnlyDependentOn) { - BSONObj matchPredicate = fromjson("{}"); - boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); - auto swMatchExpression = MatchExpressionParser::parse(matchPredicate, std::move(expCtx)); - ASSERT_OK(swMatchExpression.getStatus()); - auto matchExpression = std::move(swMatchExpression.getValue()); - ASSERT_TRUE(expression::isOnlyDependentOn(*matchExpression.get(), {})); -} - TEST(SplitMatchExpression, AndWithSplittableChildrenIsSplittable) { BSONObj matchPredicate = fromjson("{$and: [{a: 1}, {b: 1}]}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); @@ -950,11 +942,15 @@ TEST(SplitMatchExpression, AndWithSplittableChildrenIsSplittable) { expression::splitMatchExpressionBy(std::move(status.getValue()), {"b"}, {}); ASSERT_TRUE(splitExpr.first.get()); + BSONObjBuilder firstBob; + splitExpr.first->serialize(&firstBob, true); ASSERT_TRUE(splitExpr.second.get()); + BSONObjBuilder secondBob; + splitExpr.second->serialize(&secondBob, true); - ASSERT_BSONOBJ_EQ(splitExpr.first->serialize(), fromjson("{a: {$eq: 1}}")); - ASSERT_BSONOBJ_EQ(splitExpr.second->serialize(), fromjson("{b: {$eq: 1}}")); + ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{a: {$eq: 1}}")); + ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{b: {$eq: 1}}")); } TEST(SplitMatchExpression, NorWithIndependentChildrenIsSplittable) { @@ -968,11 +964,15 @@ TEST(SplitMatchExpression, NorWithIndependentChildrenIsSplittable) { expression::splitMatchExpressionBy(std::move(status.getValue()), {"b"}, {}); ASSERT_TRUE(splitExpr.first.get()); + BSONObjBuilder firstBob; + splitExpr.first->serialize(&firstBob, true); ASSERT_TRUE(splitExpr.second.get()); + BSONObjBuilder secondBob; + splitExpr.second->serialize(&secondBob, true); - ASSERT_BSONOBJ_EQ(splitExpr.first->serialize(), fromjson("{$nor: [{a: {$eq: 1}}]}")); - ASSERT_BSONOBJ_EQ(splitExpr.second->serialize(), fromjson("{$nor: [{b: {$eq: 1}}]}")); + ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{$nor: [{a: {$eq: 1}}]}")); + ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{$nor: [{b: {$eq: 1}}]}")); } TEST(SplitMatchExpression, NotWithIndependentChildIsSplittable) { @@ -986,8 +986,10 @@ TEST(SplitMatchExpression, NotWithIndependentChildIsSplittable) { expression::splitMatchExpressionBy(std::move(status.getValue()), {"y"}, {}); ASSERT_TRUE(splitExpr.first.get()); + BSONObjBuilder firstBob; + splitExpr.first->serialize(&firstBob, true); - ASSERT_BSONOBJ_EQ(splitExpr.first->serialize(), fromjson("{x: {$not: {$gt: 4}}}")); + ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{x: {$not: {$gt: 4}}}")); ASSERT_FALSE(splitExpr.second); } @@ -1002,10 +1004,11 @@ TEST(SplitMatchExpression, OrWithOnlyIndependentChildrenIsNotSplittable) { expression::splitMatchExpressionBy(std::move(status.getValue()), {"b"}, {}); ASSERT_TRUE(splitExpr.second.get()); + BSONObjBuilder bob; + splitExpr.second->serialize(&bob, true); ASSERT_FALSE(splitExpr.first); - ASSERT_BSONOBJ_EQ(splitExpr.second->serialize(), - fromjson("{$or: [{a: {$eq: 1}}, {b: {$eq: 1}}]}")); + ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{$or: [{a: {$eq: 1}}, {b: {$eq: 1}}]}")); } TEST(SplitMatchExpression, ComplexMatchExpressionSplitsCorrectly) { @@ -1022,12 +1025,15 @@ TEST(SplitMatchExpression, ComplexMatchExpressionSplitsCorrectly) { expression::splitMatchExpressionBy(std::move(status.getValue()), {"x"}, {}); ASSERT_TRUE(splitExpr.first.get()); + BSONObjBuilder firstBob; + splitExpr.first->serialize(&firstBob, true); ASSERT_TRUE(splitExpr.second.get()); + BSONObjBuilder secondBob; + splitExpr.second->serialize(&secondBob, true); - ASSERT_BSONOBJ_EQ(splitExpr.first->serialize(), - fromjson("{$or: [{'a.b': {$eq: 3}}, {'a.b.c': {$eq: 4}}]}")); - ASSERT_BSONOBJ_EQ(splitExpr.second->serialize(), + ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{$or: [{'a.b': {$eq: 3}}, {'a.b.c': {$eq: 4}}]}")); + ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{$and: [{x: {$not: {$size: 2}}}, {$nor: [{x: {$gt: 4}}, {$and: " "[{x: {$not: {$eq: 1}}}, {y: {$eq: 3}}]}]}]}")); } @@ -1044,12 +1050,15 @@ TEST(SplitMatchExpression, ShouldNotExtractPrefixOfDottedPathAsIndependent) { expression::splitMatchExpressionBy(std::move(status.getValue()), {"a.b"}, {}); ASSERT_TRUE(splitExpr.first.get()); + BSONObjBuilder firstBob; + splitExpr.first->serialize(&firstBob, true); ASSERT_TRUE(splitExpr.second.get()); + BSONObjBuilder secondBob; + splitExpr.second->serialize(&secondBob, true); - ASSERT_BSONOBJ_EQ(splitExpr.first->serialize(), fromjson("{'a.c': {$eq: 1}}")); - ASSERT_BSONOBJ_EQ(splitExpr.second->serialize(), - fromjson("{$and: [{a: {$eq: 1}}, {'a.b': {$eq: 1}}]}")); + ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{'a.c': {$eq: 1}}")); + ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{$and: [{a: {$eq: 1}}, {'a.b': {$eq: 1}}]}")); } TEST(SplitMatchExpression, ShouldMoveIndependentLeafPredicateAcrossRename) { @@ -1063,7 +1072,9 @@ TEST(SplitMatchExpression, ShouldMoveIndependentLeafPredicateAcrossRename) { expression::splitMatchExpressionBy(std::move(matcher.getValue()), {}, renames); ASSERT_TRUE(splitExpr.first.get()); - ASSERT_BSONOBJ_EQ(splitExpr.first->serialize(), fromjson("{b: {$eq: 1}}")); + BSONObjBuilder firstBob; + splitExpr.first->serialize(&firstBob, true); + ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{b: {$eq: 1}}")); ASSERT_FALSE(splitExpr.second.get()); } @@ -1079,8 +1090,9 @@ TEST(SplitMatchExpression, ShouldMoveIndependentAndPredicateAcrossRename) { expression::splitMatchExpressionBy(std::move(matcher.getValue()), {}, renames); ASSERT_TRUE(splitExpr.first.get()); - ASSERT_BSONOBJ_EQ(splitExpr.first->serialize(), - fromjson("{$and: [{c: {$eq: 1}}, {b: {$eq: 2}}]}")); + BSONObjBuilder firstBob; + splitExpr.first->serialize(&firstBob, true); + ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{$and: [{c: {$eq: 1}}, {b: {$eq: 2}}]}")); ASSERT_FALSE(splitExpr.second.get()); } @@ -1096,10 +1108,14 @@ TEST(SplitMatchExpression, ShouldSplitPartiallyDependentAndPredicateAcrossRename expression::splitMatchExpressionBy(std::move(matcher.getValue()), {"b"}, renames); ASSERT_TRUE(splitExpr.first.get()); - ASSERT_BSONOBJ_EQ(splitExpr.first->serialize(), fromjson("{c: {$eq: 1}}")); + BSONObjBuilder firstBob; + splitExpr.first->serialize(&firstBob, true); + ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{c: {$eq: 1}}")); ASSERT_TRUE(splitExpr.second.get()); - ASSERT_BSONOBJ_EQ(splitExpr.second->serialize(), fromjson("{b: {$eq: 2}}")); + BSONObjBuilder secondBob; + splitExpr.second->serialize(&secondBob, true); + ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{b: {$eq: 2}}")); } TEST(SplitMatchExpression, ShouldSplitPartiallyDependentComplexPredicateMultipleRenames) { @@ -1113,11 +1129,14 @@ TEST(SplitMatchExpression, ShouldSplitPartiallyDependentComplexPredicateMultiple expression::splitMatchExpressionBy(std::move(matcher.getValue()), {"a"}, renames); ASSERT_TRUE(splitExpr.first.get()); - ASSERT_BSONOBJ_EQ(splitExpr.first->serialize(), - fromjson("{$or: [{d: {$eq: 2}}, {e: {$eq: 3}}]}")); + BSONObjBuilder firstBob; + splitExpr.first->serialize(&firstBob, true); + ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{$or: [{d: {$eq: 2}}, {e: {$eq: 3}}]}")); ASSERT_TRUE(splitExpr.second.get()); - ASSERT_BSONOBJ_EQ(splitExpr.second->serialize(), fromjson("{a: {$eq: 1}}")); + BSONObjBuilder secondBob; + splitExpr.second->serialize(&secondBob, true); + ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{a: {$eq: 1}}")); } TEST(SplitMatchExpression, @@ -1132,11 +1151,14 @@ TEST(SplitMatchExpression, expression::splitMatchExpressionBy(std::move(matcher.getValue()), {"a"}, renames); ASSERT_TRUE(splitExpr.first.get()); - ASSERT_BSONOBJ_EQ(splitExpr.first->serialize(), - fromjson("{$or: [{x: {$eq: 2}}, {y: {$eq: 3}}]}")); + BSONObjBuilder firstBob; + splitExpr.first->serialize(&firstBob, true); + ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{$or: [{x: {$eq: 2}}, {y: {$eq: 3}}]}")); ASSERT_TRUE(splitExpr.second.get()); - ASSERT_BSONOBJ_EQ(splitExpr.second->serialize(), fromjson("{a: {$eq: 1}}")); + BSONObjBuilder secondBob; + splitExpr.second->serialize(&secondBob, true); + ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{a: {$eq: 1}}")); } TEST(SplitMatchExpression, ShouldNotMoveElemMatchObjectAcrossRename) { @@ -1152,7 +1174,9 @@ TEST(SplitMatchExpression, ShouldNotMoveElemMatchObjectAcrossRename) { ASSERT_FALSE(splitExpr.first.get()); ASSERT_TRUE(splitExpr.second.get()); - ASSERT_BSONOBJ_EQ(splitExpr.second->serialize(), fromjson("{a: {$elemMatch: {b: {$eq: 3}}}}")); + BSONObjBuilder secondBob; + splitExpr.second->serialize(&secondBob, true); + ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{a: {$elemMatch: {b: {$eq: 3}}}}")); } TEST(SplitMatchExpression, ShouldNotMoveElemMatchValueAcrossRename) { @@ -1168,7 +1192,9 @@ TEST(SplitMatchExpression, ShouldNotMoveElemMatchValueAcrossRename) { ASSERT_FALSE(splitExpr.first.get()); ASSERT_TRUE(splitExpr.second.get()); - ASSERT_BSONOBJ_EQ(splitExpr.second->serialize(), fromjson("{a: {$elemMatch: {$eq: 3}}}")); + BSONObjBuilder secondBob; + splitExpr.second->serialize(&secondBob, true); + ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{a: {$elemMatch: {$eq: 3}}}")); } TEST(SplitMatchExpression, ShouldMoveTypeAcrossRename) { @@ -1181,7 +1207,10 @@ TEST(SplitMatchExpression, ShouldMoveTypeAcrossRename) { std::pair<unique_ptr<MatchExpression>, unique_ptr<MatchExpression>> splitExpr = expression::splitMatchExpressionBy(std::move(matcher.getValue()), {}, renames); - ASSERT_BSONOBJ_EQ(splitExpr.first->serialize(), fromjson("{c: {$type: [16]}}")); + ASSERT_TRUE(splitExpr.first.get()); + BSONObjBuilder firstBob; + splitExpr.first->serialize(&firstBob, true); + ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{c: {$type: [16]}}")); ASSERT_FALSE(splitExpr.second.get()); } @@ -1199,7 +1228,9 @@ TEST(SplitMatchExpression, ShouldNotMoveSizeAcrossRename) { ASSERT_FALSE(splitExpr.first.get()); ASSERT_TRUE(splitExpr.second.get()); - ASSERT_BSONOBJ_EQ(splitExpr.second->serialize(), fromjson("{a: {$size: 3}}")); + BSONObjBuilder secondBob; + splitExpr.second->serialize(&secondBob, true); + ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{a: {$size: 3}}")); } TEST(SplitMatchExpression, ShouldNotMoveMinItemsAcrossRename) { @@ -1215,8 +1246,9 @@ TEST(SplitMatchExpression, ShouldNotMoveMinItemsAcrossRename) { ASSERT_FALSE(splitExpr.first.get()); ASSERT_TRUE(splitExpr.second.get()); - ASSERT_BSONOBJ_EQ(splitExpr.second->serialize(), - fromjson("{a: {$_internalSchemaMinItems: 3}}")); + BSONObjBuilder secondBob; + splitExpr.second->serialize(&secondBob, true); + ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{a: {$_internalSchemaMinItems: 3}}")); } TEST(SplitMatchExpression, ShouldNotMoveMaxItemsAcrossRename) { @@ -1232,8 +1264,9 @@ TEST(SplitMatchExpression, ShouldNotMoveMaxItemsAcrossRename) { ASSERT_FALSE(splitExpr.first.get()); ASSERT_TRUE(splitExpr.second.get()); - ASSERT_BSONOBJ_EQ(splitExpr.second->serialize(), - fromjson("{a: {$_internalSchemaMaxItems: 3}}")); + BSONObjBuilder secondBob; + splitExpr.second->serialize(&secondBob, true); + ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{a: {$_internalSchemaMaxItems: 3}}")); } TEST(SplitMatchExpression, ShouldNotMoveMaxItemsInLogicalExpressionAcrossRename) { @@ -1251,7 +1284,9 @@ TEST(SplitMatchExpression, ShouldNotMoveMaxItemsInLogicalExpressionAcrossRename) ASSERT_FALSE(splitExpr.first.get()); ASSERT_TRUE(splitExpr.second.get()); - ASSERT_BSONOBJ_EQ(splitExpr.second->serialize(), + BSONObjBuilder secondBob; + splitExpr.second->serialize(&secondBob, true); + ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{$or: [{a: {$_internalSchemaMaxItems: 3}}," " {a: {$_internalSchemaMaxItems: 4}}]}")); } @@ -1271,7 +1306,9 @@ TEST(SplitMatchExpression, ShouldNotMoveInternalSchemaObjectMatchInLogicalExpres ASSERT_FALSE(splitExpr.first.get()); ASSERT_TRUE(splitExpr.second.get()); - ASSERT_BSONOBJ_EQ(splitExpr.second->serialize(), + BSONObjBuilder secondBob; + splitExpr.second->serialize(&secondBob, true); + ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{$or: [{a: {$_internalSchemaObjectMatch: {b: {$eq: 1}}}}," " {a: {$_internalSchemaObjectMatch: {b: {$eq: 1}}}}]}")); } @@ -1287,8 +1324,9 @@ TEST(SplitMatchExpression, ShouldMoveMinLengthAcrossRename) { expression::splitMatchExpressionBy(std::move(matcher.getValue()), {}, renames); ASSERT_TRUE(splitExpr.first.get()); - ASSERT_BSONOBJ_EQ(splitExpr.first->serialize(), - fromjson("{c: {$_internalSchemaMinLength: 3}}")); + BSONObjBuilder firstBob; + splitExpr.first->serialize(&firstBob, true); + ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{c: {$_internalSchemaMinLength: 3}}")); ASSERT_FALSE(splitExpr.second.get()); } @@ -1304,8 +1342,9 @@ TEST(SplitMatchExpression, ShouldMoveMaxLengthAcrossRename) { expression::splitMatchExpressionBy(std::move(matcher.getValue()), {}, renames); ASSERT_TRUE(splitExpr.first.get()); - ASSERT_BSONOBJ_EQ(splitExpr.first->serialize(), - fromjson("{c: {$_internalSchemaMaxLength: 3}}")); + BSONObjBuilder firstBob; + splitExpr.first->serialize(&firstBob, true); + ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{c: {$_internalSchemaMaxLength: 3}}")); ASSERT_FALSE(splitExpr.second.get()); } @@ -1322,34 +1361,13 @@ TEST(SplitMatchExpression, ShouldMoveIndependentPredicateWhenThereAreMultipleRen expression::splitMatchExpressionBy(std::move(matcher.getValue()), {}, renames); ASSERT_TRUE(splitExpr.first.get()); - ASSERT_BSONOBJ_EQ(splitExpr.first->serialize(), fromjson("{x: {$eq: 3}}")); + BSONObjBuilder firstBob; + splitExpr.first->serialize(&firstBob, true); + ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{x: {$eq: 3}}")); ASSERT_FALSE(splitExpr.second.get()); } -TEST(SplitMatchExpression, ShouldNotSplitWhenRand) { - const auto randExpr = "{$expr: {$lt: [{$rand: {}}, {$const: 0.25}]}}"; - const auto assertMatchDoesNotSplit = [&](const std::string& exprString) { - BSONObj matchPredicate = fromjson(exprString); - boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); - auto matcher = MatchExpressionParser::parse(matchPredicate, std::move(expCtx)); - ASSERT_OK(matcher.getStatus()); - - auto&& [split, residual] = - expression::splitMatchExpressionBy(std::move(matcher.getValue()), {}, {}); - ASSERT_FALSE(split.get()); - ASSERT_TRUE(residual.get()); - - ASSERT_BSONOBJ_EQ(residual->serialize(), fromjson(randExpr)); - }; - - // We should not push down a $match with a $rand expression. - assertMatchDoesNotSplit(randExpr); - - // This is equivalent to 'randExpr'. - assertMatchDoesNotSplit("{$sampleRate: 0.25}"); -} - TEST(ApplyRenamesToExpression, ShouldApplyBasicRenamesForAMatchWithExpr) { BSONObj matchPredicate = fromjson("{$expr: {$eq: ['$a.b', '$c']}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); @@ -1545,7 +1563,7 @@ TEST(HasExistencePredicateOnPath, ReturnsFalseWhenExistsOnSubpath) { } TEST(SplitMatchExpressionForColumns, PreservesEmptyPredicates) { - ParsedMatchExpressionForTest empty("{}"); + ParsedMatchExpression empty("{}"); auto splitUp = expression::splitMatchExpressionForColumns(empty.get()); ASSERT(splitUp); ASSERT(splitUp->size() == 0); @@ -1554,21 +1572,21 @@ TEST(SplitMatchExpressionForColumns, PreservesEmptyPredicates) { TEST(SplitMatchExpressionForColumns, RejectsUnsupportedPredicates) { { // Future work. - ParsedMatchExpressionForTest orClause("{$or: [{a: 1}, {b: 2}]}"); + ParsedMatchExpression orClause("{$or: [{a: 1}, {b: 2}]}"); auto splitUp = expression::splitMatchExpressionForColumns(orClause.get()); ASSERT(!splitUp); } { // Would match missing values, not safe for a columnar index. - ParsedMatchExpressionForTest alwaysTrue("{$alwaysTrue: 1}"); + ParsedMatchExpression alwaysTrue("{$alwaysTrue: 1}"); auto splitUp = expression::splitMatchExpressionForColumns(alwaysTrue.get()); ASSERT(!splitUp); } { // Future work. - ParsedMatchExpressionForTest exprClause("{$expr: {$eq: ['$x', 0]}}"); + ParsedMatchExpression exprClause("{$expr: {$eq: ['$x', 0]}}"); auto splitUp = expression::splitMatchExpressionForColumns(exprClause.get()); ASSERT(!splitUp); } @@ -1578,7 +1596,7 @@ TEST(SplitMatchExpressionForColumns, RejectsUnsupportedPredicates) { TEST(SplitMatchExpressionForColumns, SplitsSafeEqualities) { { - ParsedMatchExpressionForTest singleEqualsNumber("{albatross: 1}"); + ParsedMatchExpression singleEqualsNumber("{albatross: 1}"); auto splitUp = expression::splitMatchExpressionForColumns(singleEqualsNumber.get()); ASSERT(splitUp); ASSERT_EQ(splitUp->size(), 1) << splitUp->size(); @@ -1588,7 +1606,7 @@ TEST(SplitMatchExpressionForColumns, SplitsSafeEqualities) { } { - ParsedMatchExpressionForTest singleEqualsString("{albatross: 'flying'}"); + ParsedMatchExpression singleEqualsString("{albatross: 'flying'}"); auto splitUp = expression::splitMatchExpressionForColumns(singleEqualsString.get()); ASSERT(splitUp); ASSERT_EQ(splitUp->size(), 1) << splitUp->size(); @@ -1598,7 +1616,7 @@ TEST(SplitMatchExpressionForColumns, SplitsSafeEqualities) { } { - ParsedMatchExpressionForTest doubleEqualsNumber("{albatross: 1, blackbird: 2}"); + ParsedMatchExpression doubleEqualsNumber("{albatross: 1, blackbird: 2}"); auto splitUp = expression::splitMatchExpressionForColumns(doubleEqualsNumber.get()); ASSERT(splitUp); ASSERT_EQ(splitUp->size(), 2) << splitUp->size(); @@ -1611,7 +1629,7 @@ TEST(SplitMatchExpressionForColumns, SplitsSafeEqualities) { } { - ParsedMatchExpressionForTest mixedEquals( + ParsedMatchExpression mixedEquals( "{albatross: 1," " blackbird: 'flying'," " cowbird: {$eq: /oreo/}," @@ -1639,7 +1657,7 @@ TEST(SplitMatchExpressionForColumns, SplitsSafeEqualities) { TEST(SplitMatchExpressionForColumns, SupportsEqualityToEmptyObjects) { { - ParsedMatchExpressionForTest equalsEmptyObj("{albatross: {}}"); + ParsedMatchExpression equalsEmptyObj("{albatross: {}}"); auto splitUp = expression::splitMatchExpressionForColumns(equalsEmptyObj.get()); ASSERT(splitUp); ASSERT_EQ(splitUp->size(), 1) << splitUp->size(); @@ -1651,7 +1669,7 @@ TEST(SplitMatchExpressionForColumns, SupportsEqualityToEmptyObjects) { TEST(SplitMatchExpressionForColumns, SupportsEqualityToEmptyArray) { { - ParsedMatchExpressionForTest equalsEmptyArray("{albatross: []}"); + ParsedMatchExpression equalsEmptyArray("{albatross: []}"); auto splitUp = expression::splitMatchExpressionForColumns(equalsEmptyArray.get()); ASSERT(splitUp); ASSERT_EQ(splitUp->size(), 1) << splitUp->size(); @@ -1663,7 +1681,7 @@ TEST(SplitMatchExpressionForColumns, SupportsEqualityToEmptyArray) { TEST(SplitMatchExpressionForColumns, DoesNotSupportEqualsNull) { { - ParsedMatchExpressionForTest equalsNull("{a: null}"); + ParsedMatchExpression equalsNull("{a: null}"); auto splitUp = expression::splitMatchExpressionForColumns(equalsNull.get()); ASSERT(!splitUp); } @@ -1671,7 +1689,7 @@ TEST(SplitMatchExpressionForColumns, DoesNotSupportEqualsNull) { TEST(SplitMatchExpressionForColumns, DoesSupportNotEqualsNull) { { - ParsedMatchExpressionForTest neNull("{a: {$ne: null}}"); + ParsedMatchExpression neNull("{a: {$ne: null}}"); auto splitUp = expression::splitMatchExpressionForColumns(neNull.get()); ASSERT(splitUp); ASSERT_EQ(splitUp->size(), 1) << splitUp->size(); @@ -1680,7 +1698,7 @@ TEST(SplitMatchExpressionForColumns, DoesSupportNotEqualsNull) { << splitUp->at("a")->toString(); } { - ParsedMatchExpressionForTest notEqualsNull("{a: {$not: {$eq: null}}}"); + ParsedMatchExpression notEqualsNull("{a: {$not: {$eq: null}}}"); auto splitUp = expression::splitMatchExpressionForColumns(notEqualsNull.get()); ASSERT(splitUp) << notEqualsNull.get()->toString(); ASSERT_EQ(splitUp->size(), 1) << splitUp->size(); @@ -1692,28 +1710,28 @@ TEST(SplitMatchExpressionForColumns, DoesSupportNotEqualsNull) { TEST(SplitMatchExpressionForColumns, DoesNotSupportCompoundEquals) { { - ParsedMatchExpressionForTest implicitEqualsArray("{a: [1, 2]}"); + ParsedMatchExpression implicitEqualsArray("{a: [1, 2]}"); auto splitUp = expression::splitMatchExpressionForColumns(implicitEqualsArray.get()); ASSERT(!splitUp); } { - ParsedMatchExpressionForTest explicitEqualsArray("{a: {$eq: [1, 2]}}"); + ParsedMatchExpression explicitEqualsArray("{a: {$eq: [1, 2]}}"); auto splitUp = expression::splitMatchExpressionForColumns(explicitEqualsArray.get()); ASSERT(!splitUp); } { - ParsedMatchExpressionForTest implicitEqualsObject("{a: {boats: 1, planes: 2}}"); + ParsedMatchExpression implicitEqualsObject("{a: {boats: 1, planes: 2}}"); auto splitUp = expression::splitMatchExpressionForColumns(implicitEqualsObject.get()); ASSERT(!splitUp); } { - ParsedMatchExpressionForTest explicitEqualsObject("{a: {$eq: {boats: 1, planes: 2}}}"); + ParsedMatchExpression explicitEqualsObject("{a: {$eq: {boats: 1, planes: 2}}}"); auto splitUp = expression::splitMatchExpressionForColumns(explicitEqualsObject.get()); ASSERT(!splitUp); } // We should be able to do dotted path version though, as a potential workaround. { - ParsedMatchExpressionForTest equalsDotted("{'a.boats': 1, 'a.planes': 2}"); + ParsedMatchExpression equalsDotted("{'a.boats': 1, 'a.planes': 2}"); auto splitUp = expression::splitMatchExpressionForColumns(equalsDotted.get()); ASSERT(splitUp); ASSERT(splitUp->size() == 2); @@ -1730,7 +1748,7 @@ TEST(SplitMatchExpressionForColumns, DoesNotSupportCompoundEquals) { TEST(SplitMatchExpressionForColumns, SupportsComparisonsLikeEqualities) { { - ParsedMatchExpressionForTest singleLtNumber("{albatross: {$lt: 1}}"); + ParsedMatchExpression singleLtNumber("{albatross: {$lt: 1}}"); auto splitUp = expression::splitMatchExpressionForColumns(singleLtNumber.get()); ASSERT(splitUp); ASSERT_EQ(splitUp->size(), 1) << splitUp->size(); @@ -1739,7 +1757,7 @@ TEST(SplitMatchExpressionForColumns, SupportsComparisonsLikeEqualities) { << splitUp->at("albatross")->toString(); } { - ParsedMatchExpressionForTest singleLteNumber("{albatross: {$lte: 1}}"); + ParsedMatchExpression singleLteNumber("{albatross: {$lte: 1}}"); auto splitUp = expression::splitMatchExpressionForColumns(singleLteNumber.get()); ASSERT(splitUp); ASSERT_EQ(splitUp->size(), 1) << splitUp->size(); @@ -1748,7 +1766,7 @@ TEST(SplitMatchExpressionForColumns, SupportsComparisonsLikeEqualities) { << splitUp->at("albatross")->toString(); } { - ParsedMatchExpressionForTest singleGtNumber("{albatross: {$gt: 1}}"); + ParsedMatchExpression singleGtNumber("{albatross: {$gt: 1}}"); auto splitUp = expression::splitMatchExpressionForColumns(singleGtNumber.get()); ASSERT(splitUp); ASSERT_EQ(splitUp->size(), 1) << splitUp->size(); @@ -1757,7 +1775,7 @@ TEST(SplitMatchExpressionForColumns, SupportsComparisonsLikeEqualities) { << splitUp->at("albatross")->toString(); } { - ParsedMatchExpressionForTest singleGteNumber("{albatross: {$gte: 1}}"); + ParsedMatchExpression singleGteNumber("{albatross: {$gte: 1}}"); auto splitUp = expression::splitMatchExpressionForColumns(singleGteNumber.get()); ASSERT(splitUp); ASSERT_EQ(splitUp->size(), 1) << splitUp->size(); @@ -1766,7 +1784,7 @@ TEST(SplitMatchExpressionForColumns, SupportsComparisonsLikeEqualities) { << splitUp->at("albatross")->toString(); } { - ParsedMatchExpressionForTest combinationPredicate( + ParsedMatchExpression combinationPredicate( "{" " albatross: {$lt: 100}," " blackbird: {$gt: 0}," @@ -1790,42 +1808,42 @@ TEST(SplitMatchExpressionForColumns, SupportsComparisonsLikeEqualities) { // While equality to [] or {} is OK, inequality is not so obvious. Left as future work. TEST(SplitMatchExpressionForColumns, DoesNotSupportInequalitiesToObjectsOrArrays) { { - ParsedMatchExpressionForTest ltArray("{albatross: {$lt: []}}"); + ParsedMatchExpression ltArray("{albatross: {$lt: []}}"); auto splitUp = expression::splitMatchExpressionForColumns(ltArray.get()); ASSERT(!splitUp); } { - ParsedMatchExpressionForTest ltObject("{albatross: {$lt: {}}}"); + ParsedMatchExpression ltObject("{albatross: {$lt: {}}}"); auto splitUp = expression::splitMatchExpressionForColumns(ltObject.get()); ASSERT(!splitUp); } { - ParsedMatchExpressionForTest lteArray("{albatross: {$lte: []}}"); + ParsedMatchExpression lteArray("{albatross: {$lte: []}}"); auto splitUp = expression::splitMatchExpressionForColumns(lteArray.get()); ASSERT(!splitUp); } { - ParsedMatchExpressionForTest lteObject("{albatross: {$lte: {}}}"); + ParsedMatchExpression lteObject("{albatross: {$lte: {}}}"); auto splitUp = expression::splitMatchExpressionForColumns(lteObject.get()); ASSERT(!splitUp); } { - ParsedMatchExpressionForTest gtArray("{albatross: {$gt: []}}"); + ParsedMatchExpression gtArray("{albatross: {$gt: []}}"); auto splitUp = expression::splitMatchExpressionForColumns(gtArray.get()); ASSERT(!splitUp); } { - ParsedMatchExpressionForTest gtObject("{albatross: {$gt: {}}}"); + ParsedMatchExpression gtObject("{albatross: {$gt: {}}}"); auto splitUp = expression::splitMatchExpressionForColumns(gtObject.get()); ASSERT(!splitUp); } { - ParsedMatchExpressionForTest gteArray("{albatross: {$gte: []}}"); + ParsedMatchExpression gteArray("{albatross: {$gte: []}}"); auto splitUp = expression::splitMatchExpressionForColumns(gteArray.get()); ASSERT(!splitUp); } { - ParsedMatchExpressionForTest gteObject("{albatross: {$gte: {}}}"); + ParsedMatchExpression gteObject("{albatross: {$gte: {}}}"); auto splitUp = expression::splitMatchExpressionForColumns(gteObject.get()); ASSERT(!splitUp); } @@ -1833,7 +1851,7 @@ TEST(SplitMatchExpressionForColumns, DoesNotSupportInequalitiesToObjectsOrArrays // Tests that comparisons which only match values of a certain type are allowed. TEST(SplitMatchExpressionForColumns, SupportsTypeSpecificPredicates) { - ParsedMatchExpressionForTest combinationPredicate( + ParsedMatchExpression combinationPredicate( "{" " albatross: /oreo/," " blackbird: {$mod: [2, 0]}," @@ -1868,7 +1886,7 @@ TEST(SplitMatchExpressionForColumns, SupportsTypeSpecificPredicates) { TEST(SplitMatchExpressionForColumns, SupportsInWithRegexes) { { // First confirm a $in clause is supported without regexes. - ParsedMatchExpressionForTest stringInClause("{albatross: {$in: ['big', 'ol', 'bird']}}"); + ParsedMatchExpression stringInClause("{albatross: {$in: ['big', 'ol', 'bird']}}"); auto splitUp = expression::splitMatchExpressionForColumns(stringInClause.get()); ASSERT(splitUp); ASSERT(splitUp->contains("albatross")); @@ -1878,7 +1896,7 @@ TEST(SplitMatchExpressionForColumns, SupportsInWithRegexes) { } { // Test that $in with regexes is supported also work. - ParsedMatchExpressionForTest regexInClause("{albatross: {$in: [/big/, /bird/]}}"); + ParsedMatchExpression regexInClause("{albatross: {$in: [/big/, /bird/]}}"); auto splitUp = expression::splitMatchExpressionForColumns(regexInClause.get()); ASSERT(splitUp); ASSERT(splitUp->contains("albatross")); @@ -1888,7 +1906,7 @@ TEST(SplitMatchExpressionForColumns, SupportsInWithRegexes) { } { // Test that a mix of both is supported - ParsedMatchExpressionForTest regexInClause("{albatross: {$in: [/big/, 'bird']}}"); + ParsedMatchExpression regexInClause("{albatross: {$in: [/big/, 'bird']}}"); auto splitUp = expression::splitMatchExpressionForColumns(regexInClause.get()); ASSERT(splitUp); ASSERT(splitUp->contains("albatross")); @@ -1898,14 +1916,14 @@ TEST(SplitMatchExpressionForColumns, SupportsInWithRegexes) { } { // Test that it is still disallowed if there's a disqualifying equality such as a null. - ParsedMatchExpressionForTest regexInClause("{albatross: {$in: [/big/, null, 'bird']}}"); + ParsedMatchExpression regexInClause("{albatross: {$in: [/big/, null, 'bird']}}"); auto splitUp = expression::splitMatchExpressionForColumns(regexInClause.get()); ASSERT(!splitUp); } } TEST(SplitMatchExpressionForColumns, SupportsExistsTrue) { - ParsedMatchExpressionForTest existsPredicate("{albatross: {$exists: true}}"); + ParsedMatchExpression existsPredicate("{albatross: {$exists: true}}"); auto splitUp = expression::splitMatchExpressionForColumns(existsPredicate.get()); ASSERT(splitUp); ASSERT(splitUp->contains("albatross")); @@ -1915,7 +1933,7 @@ TEST(SplitMatchExpressionForColumns, SupportsExistsTrue) { } TEST(SplitMatchExpressionForColumns, DoesNotSupportExistsFalse) { - ParsedMatchExpressionForTest existsPredicate("{albatross: {$exists: false}}"); + ParsedMatchExpression existsPredicate("{albatross: {$exists: false}}"); auto splitUp = expression::splitMatchExpressionForColumns(existsPredicate.get()); ASSERT(!splitUp); } @@ -1924,7 +1942,7 @@ TEST(SplitMatchExpressionForColumns, DoesNotSupportExistsFalse) { // next test. TEST(SplitMatchExpressionForColumns, SupportsInPredicates) { { - ParsedMatchExpressionForTest emptyIn("{albatross: {$in: []}}"); + ParsedMatchExpression emptyIn("{albatross: {$in: []}}"); auto splitUp = expression::splitMatchExpressionForColumns(emptyIn.get()); ASSERT(splitUp); ASSERT(splitUp->contains("albatross")); @@ -1933,7 +1951,7 @@ TEST(SplitMatchExpressionForColumns, SupportsInPredicates) { ASSERT_EQ(splitUp->size(), 1) << splitUp->size(); } { - ParsedMatchExpressionForTest singleElementIn("{albatross: {$in: [4]}}"); + ParsedMatchExpression singleElementIn("{albatross: {$in: [4]}}"); auto splitUp = expression::splitMatchExpressionForColumns(singleElementIn.get()); ASSERT(splitUp); ASSERT(splitUp->contains("albatross")); @@ -1942,7 +1960,7 @@ TEST(SplitMatchExpressionForColumns, SupportsInPredicates) { ASSERT_EQ(splitUp->size(), 1) << splitUp->size(); } { - ParsedMatchExpressionForTest inWithEmptyArray("{albatross: {$in: [[]]}}"); + ParsedMatchExpression inWithEmptyArray("{albatross: {$in: [[]]}}"); auto splitUp = expression::splitMatchExpressionForColumns(inWithEmptyArray.get()); ASSERT(splitUp); ASSERT(splitUp->contains("albatross")); @@ -1951,7 +1969,7 @@ TEST(SplitMatchExpressionForColumns, SupportsInPredicates) { ASSERT_EQ(splitUp->size(), 1) << splitUp->size(); } { - ParsedMatchExpressionForTest inWithEmptyObject("{albatross: {$in: [{}]}}"); + ParsedMatchExpression inWithEmptyObject("{albatross: {$in: [{}]}}"); auto splitUp = expression::splitMatchExpressionForColumns(inWithEmptyObject.get()); ASSERT(splitUp); ASSERT(splitUp->contains("albatross")); @@ -1960,8 +1978,7 @@ TEST(SplitMatchExpressionForColumns, SupportsInPredicates) { ASSERT_EQ(splitUp->size(), 1) << splitUp->size(); } { - ParsedMatchExpressionForTest mixedTypeIn( - "{albatross: {$in: [4, {}, [], 'string', /regex/]}}"); + ParsedMatchExpression mixedTypeIn("{albatross: {$in: [4, {}, [], 'string', /regex/]}}"); auto splitUp = expression::splitMatchExpressionForColumns(mixedTypeIn.get()); ASSERT(splitUp); ASSERT(splitUp->contains("albatross")); @@ -1974,22 +1991,22 @@ TEST(SplitMatchExpressionForColumns, SupportsInPredicates) { // We can't support compound types, just like for equality. TEST(SplitMatchExpressionForColumns, DoesNotSupportCertainInEdgeCases) { { - ParsedMatchExpressionForTest inWithArray("{albatross: {$in: [[2,3]]}}"); + ParsedMatchExpression inWithArray("{albatross: {$in: [[2,3]]}}"); auto splitUp = expression::splitMatchExpressionForColumns(inWithArray.get()); ASSERT(!splitUp); } { - ParsedMatchExpressionForTest inWithObject("{albatross: {$in: [{wings: 2}]}}"); + ParsedMatchExpression inWithObject("{albatross: {$in: [{wings: 2}]}}"); auto splitUp = expression::splitMatchExpressionForColumns(inWithObject.get()); ASSERT(!splitUp); } { - ParsedMatchExpressionForTest inWithNull("{albatross: {$in: [null]}}"); + ParsedMatchExpression inWithNull("{albatross: {$in: [null]}}"); auto splitUp = expression::splitMatchExpressionForColumns(inWithNull.get()); ASSERT(!splitUp); } { - ParsedMatchExpressionForTest unsupporedMixedInWithSupported( + ParsedMatchExpression unsupporedMixedInWithSupported( "{albatross: {$in: ['strings', 1, null, {x: 4}, [0, 0], 4]}}"); auto splitUp = expression::splitMatchExpressionForColumns(unsupporedMixedInWithSupported.get()); @@ -1999,7 +2016,7 @@ TEST(SplitMatchExpressionForColumns, DoesNotSupportCertainInEdgeCases) { TEST(SplitMatchExpressionForColumns, SupportsTypePredicates) { { - ParsedMatchExpressionForTest intFilter("{albatross: {$type: 'int'}}"); + ParsedMatchExpression intFilter("{albatross: {$type: 'int'}}"); auto splitUp = expression::splitMatchExpressionForColumns(intFilter.get()); ASSERT(splitUp); ASSERT(splitUp->contains("albatross")); @@ -2008,7 +2025,7 @@ TEST(SplitMatchExpressionForColumns, SupportsTypePredicates) { ASSERT_EQ(splitUp->size(), 1) << splitUp->size(); } { - ParsedMatchExpressionForTest numberFilter("{albatross: {$type: 'number'}}"); + ParsedMatchExpression numberFilter("{albatross: {$type: 'number'}}"); auto splitUp = expression::splitMatchExpressionForColumns(numberFilter.get()); ASSERT(splitUp); ASSERT(splitUp->contains("albatross")); @@ -2017,7 +2034,7 @@ TEST(SplitMatchExpressionForColumns, SupportsTypePredicates) { ASSERT_EQ(splitUp->size(), 1) << splitUp->size(); } { - ParsedMatchExpressionForTest stringFilter("{albatross: {$type: 'string'}}"); + ParsedMatchExpression stringFilter("{albatross: {$type: 'string'}}"); auto splitUp = expression::splitMatchExpressionForColumns(stringFilter.get()); ASSERT(splitUp); ASSERT(splitUp->contains("albatross")); @@ -2026,7 +2043,7 @@ TEST(SplitMatchExpressionForColumns, SupportsTypePredicates) { ASSERT_EQ(splitUp->size(), 1) << splitUp->size(); } { - ParsedMatchExpressionForTest nullFilter("{albatross: {$type: 'null'}}"); + ParsedMatchExpression nullFilter("{albatross: {$type: 'null'}}"); auto splitUp = expression::splitMatchExpressionForColumns(nullFilter.get()); ASSERT(splitUp); ASSERT(splitUp->contains("albatross")); @@ -2037,20 +2054,20 @@ TEST(SplitMatchExpressionForColumns, SupportsTypePredicates) { } TEST(SplitMatchExpressionForColumns, DoesNotSupportQueriesForTypeObject) { - ParsedMatchExpressionForTest objectFilter("{albatross: {$type: 'object'}}"); + ParsedMatchExpression objectFilter("{albatross: {$type: 'object'}}"); auto splitUp = expression::splitMatchExpressionForColumns(objectFilter.get()); ASSERT(!splitUp); } // This may be workable. But until we can prove it we'll disallow {$type: "array"}. TEST(SplitMatchExpressionForColumns, DoesNotSupportQueriesForTypeArray) { - ParsedMatchExpressionForTest objectFilter("{albatross: {$type: 'array'}}"); + ParsedMatchExpression objectFilter("{albatross: {$type: 'array'}}"); auto splitUp = expression::splitMatchExpressionForColumns(objectFilter.get()); ASSERT(!splitUp); } TEST(SplitMatchExpressionForColumns, CanCombinePredicates) { - ParsedMatchExpressionForTest compoundFilter( + ParsedMatchExpression compoundFilter( "{" " albatross: {$gte: 100}," " albatross: {$mod: [2, 0]}" @@ -2075,7 +2092,7 @@ TEST(SplitMatchExpressionForColumns, CanCombinePredicates) { } TEST(SplitMatchExpressionForColumns, SupportsDottedPaths) { - ParsedMatchExpressionForTest compoundFilter( + ParsedMatchExpression compoundFilter( "{" " albatross: /oreo/," " \"blackbird.feet\": {$mod: [2, 0]}," @@ -2118,7 +2135,7 @@ TEST(SplitMatchExpressionForColumns, SupportsDottedPaths) { } TEST(SplitMatchExpressionForColumns, LeavesOriginalMatchExpressionFunctional) { - ParsedMatchExpressionForTest combinationPredicate( + ParsedMatchExpression combinationPredicate( "{" " albatross: {$lt: 100}," " blackbird: {$gt: 0}," diff --git a/src/mongo/db/matcher/expression_always_boolean.h b/src/mongo/db/matcher/expression_always_boolean.h index 2ad7e25d85c..6e0e2cdfbc8 100644 --- a/src/mongo/db/matcher/expression_always_boolean.h +++ b/src/mongo/db/matcher/expression_always_boolean.h @@ -62,10 +62,8 @@ public: debug << name() << ": 1\n"; } - void serialize(BSONObjBuilder* out, - const SerializationOptions& opts = {}, - bool includePath = true) const final { - opts.appendLiteral(out, name(), 1); + void serialize(BSONObjBuilder* out, bool includePath) const final { + out->append(name(), 1); } bool equivalent(const MatchExpression* other) const final { diff --git a/src/mongo/db/matcher/expression_arity.h b/src/mongo/db/matcher/expression_arity.h index 840d4be61f6..90286200e21 100644 --- a/src/mongo/db/matcher/expression_arity.h +++ b/src/mongo/db/matcher/expression_arity.h @@ -58,7 +58,7 @@ public: _debugAddSpace(debug, indentationLevel); BSONObjBuilder builder; - serialize(&builder, {}); + serialize(&builder, true); debug << builder.obj().toString(); } @@ -107,13 +107,11 @@ public: /** * Serializes each subexpression sequentially in a BSONArray. */ - void serialize(BSONObjBuilder* builder, - const SerializationOptions& opts = {}, - bool includePath = true) const final { + void serialize(BSONObjBuilder* builder, bool includePath) const final { BSONArrayBuilder exprArray(builder->subarrayStart(name())); for (const auto& expr : _expressions) { BSONObjBuilder exprBuilder(exprArray.subobjStart()); - expr->serialize(&exprBuilder, opts, includePath); + expr->serialize(&exprBuilder, includePath); exprBuilder.doneFast(); } exprArray.doneFast(); diff --git a/src/mongo/db/matcher/expression_array.cpp b/src/mongo/db/matcher/expression_array.cpp index 42d30aaf0f1..cbff0adf1ca 100644 --- a/src/mongo/db/matcher/expression_array.cpp +++ b/src/mongo/db/matcher/expression_array.cpp @@ -101,13 +101,10 @@ void ElemMatchObjectMatchExpression::debugString(StringBuilder& debug, int inden _sub->debugString(debug, indentationLevel + 1); } -void ElemMatchObjectMatchExpression::appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts, - bool includePath) const { - BSONObjBuilder elemMatchBob = bob->subobjStart("$elemMatch"); - SerializationOptions options = opts; - _sub->serialize(&elemMatchBob, options, true); - elemMatchBob.doneFast(); +BSONObj ElemMatchObjectMatchExpression::getSerializedRightHandSide() const { + BSONObjBuilder subBob; + _sub->serialize(&subBob, true); + return BSON("$elemMatch" << subBob.obj()); } MatchExpression::ExpressionOptimizerFunc ElemMatchObjectMatchExpression::getOptimizer() const { @@ -173,15 +170,14 @@ void ElemMatchValueMatchExpression::debugString(StringBuilder& debug, int indent } } -void ElemMatchValueMatchExpression::appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts, - bool includePath) const { - BSONObjBuilder emBob = bob->subobjStart("$elemMatch"); - SerializationOptions options = opts; +BSONObj ElemMatchValueMatchExpression::getSerializedRightHandSide() const { + BSONObjBuilder emBob; + for (auto&& child : _subs) { - child->serialize(&emBob, options, false); + child->serialize(&emBob, false); } - emBob.doneFast(); + + return BSON("$elemMatch" << emBob.obj()); } MatchExpression::ExpressionOptimizerFunc ElemMatchValueMatchExpression::getOptimizer() const { @@ -219,10 +215,8 @@ void SizeMatchExpression::debugString(StringBuilder& debug, int indentationLevel } } -void SizeMatchExpression::appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts, - bool includePath) const { - opts.appendLiteral(bob, "$size", _size); +BSONObj SizeMatchExpression::getSerializedRightHandSide() const { + return BSON("$size" << _size); } bool SizeMatchExpression::equivalent(const MatchExpression* other) const { diff --git a/src/mongo/db/matcher/expression_array.h b/src/mongo/db/matcher/expression_array.h index 10f30629c92..1b6cb5dfcc9 100644 --- a/src/mongo/db/matcher/expression_array.h +++ b/src/mongo/db/matcher/expression_array.h @@ -90,9 +90,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel) const; - void appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts = {}, - bool includePath = true) const final; + BSONObj getSerializedRightHandSide() const final; std::vector<std::unique_ptr<MatchExpression>>* getChildVector() final { return nullptr; @@ -159,9 +157,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel) const; - void appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts = {}, - bool includePath = true) const final; + BSONObj getSerializedRightHandSide() const final; std::vector<std::unique_ptr<MatchExpression>>* getChildVector() final { return &_subs; @@ -234,9 +230,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel) const; - void appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts = {}, - bool includePath = true) const final; + BSONObj getSerializedRightHandSide() const final; virtual bool equivalent(const MatchExpression* other) const; diff --git a/src/mongo/db/matcher/expression_expr.cpp b/src/mongo/db/matcher/expression_expr.cpp index 1ce4f0f6954..567039995f6 100644 --- a/src/mongo/db/matcher/expression_expr.cpp +++ b/src/mongo/db/matcher/expression_expr.cpp @@ -77,10 +77,8 @@ Value ExprMatchExpression::evaluateExpression(const MatchableDocument* doc) cons return _expression->evaluate(document, &variables); } -void ExprMatchExpression::serialize(BSONObjBuilder* out, - const SerializationOptions& opts, - bool includePath) const { - *out << "$expr" << _expression->serialize(opts); +void ExprMatchExpression::serialize(BSONObjBuilder* out, bool includePath) const { + *out << "$expr" << _expression->serialize(false); } bool ExprMatchExpression::equivalent(const MatchExpression* other) const { @@ -96,8 +94,8 @@ bool ExprMatchExpression::equivalent(const MatchExpression* other) const { } // TODO SERVER-30982: Add mechanism to allow for checking Expression equivalency. - return ValueComparator().evaluate(_expression->serialize() == - realOther->_expression->serialize()); + return ValueComparator().evaluate(_expression->serialize(false) == + realOther->_expression->serialize(false)); } void ExprMatchExpression::_doSetCollator(const CollatorInterface* collator) { @@ -116,7 +114,7 @@ void ExprMatchExpression::_doSetCollator(const CollatorInterface* collator) { std::unique_ptr<MatchExpression> ExprMatchExpression::shallowClone() const { // TODO SERVER-31003: Replace Expression clone via serialization with Expression::clone(). BSONObjBuilder bob; - bob << "" << _expression->serialize(); + bob << "" << _expression->serialize(false); boost::intrusive_ptr<Expression> clonedExpr = Expression::parseOperand( _expCtx.get(), bob.obj().firstElement(), _expCtx->variablesParseState); @@ -132,9 +130,8 @@ MatchExpression::ExpressionOptimizerFunc ExprMatchExpression::getOptimizer() con return [](std::unique_ptr<MatchExpression> expression) { auto& exprMatchExpr = static_cast<ExprMatchExpression&>(*expression); - // $expr expressions can't take advantage of indexes. We attempt to rewrite the expressions - // as a conjunction of internal match expressions, so the query planner can use the - // internal match expressions to potentially generate an index scan. + // If '_expression' can be rewritten to a MatchExpression, we will return a $and node with + // both the original ExprMatchExpression and the MatchExpression rewrite as children. // Exiting early prevents additional calls to optimize from performing additional rewrites // and adding duplicate MatchExpression sub-trees to the tree. if (exprMatchExpr._rewriteResult) { @@ -146,10 +143,6 @@ MatchExpression::ExpressionOptimizerFunc ExprMatchExpression::getOptimizer() con RewriteExpr::rewrite(exprMatchExpr._expression, exprMatchExpr._expCtx->getCollator()); if (exprMatchExpr._rewriteResult->matchExpression()) { - // If '_expression' can be rewritten to a MatchExpression, we will return a $and node - // with both the original ExprMatchExpression and the MatchExpression rewrite as - // children. The rewritten expression might not be equivalent to the original one so we - // still have to keep the latter for correctness. auto andMatch = std::make_unique<AndMatchExpression>(); andMatch->add(exprMatchExpr._rewriteResult->releaseMatchExpression()); andMatch->add(std::move(expression)); diff --git a/src/mongo/db/matcher/expression_expr.h b/src/mongo/db/matcher/expression_expr.h index b3f670e4597..281fdd3afd6 100644 --- a/src/mongo/db/matcher/expression_expr.h +++ b/src/mongo/db/matcher/expression_expr.h @@ -72,12 +72,10 @@ public: void debugString(StringBuilder& debug, int indentationLevel = 0) const final { _debugAddSpace(debug, indentationLevel); - debug << "$expr " << _expression->serialize().toString(); + debug << "$expr " << _expression->serialize(false).toString(); } - void serialize(BSONObjBuilder* out, - const SerializationOptions& opts = {}, - bool includePath = true) const final; + void serialize(BSONObjBuilder* out, bool includePath) const final; bool equivalent(const MatchExpression* other) const final; diff --git a/src/mongo/db/matcher/expression_expr_test.cpp b/src/mongo/db/matcher/expression_expr_test.cpp index 6f91f12bc5d..5ff0cbf88e9 100644 --- a/src/mongo/db/matcher/expression_expr_test.cpp +++ b/src/mongo/db/matcher/expression_expr_test.cpp @@ -37,8 +37,6 @@ #include "mongo/db/matcher/matcher.h" #include "mongo/db/pipeline/expression_context_for_test.h" #include "mongo/db/query/collation/collator_interface_mock.h" -#include "mongo/db/query/query_shape/serialization_options.h" -#include "mongo/unittest/death_test.h" #include "mongo/unittest/unittest.h" namespace mongo { @@ -83,10 +81,6 @@ public: return checked_cast<ExprMatchExpression*>(_matchExpression.get()); } - BSONObj serialize(const SerializationOptions& opts) { - return _matchExpression->serialize(opts); - } - private: const boost::intrusive_ptr<ExpressionContextForTest> _expCtx; std::unique_ptr<MatchExpression> _matchExpression; @@ -720,10 +714,17 @@ TEST(ExprMatchTest, OptimizingExprAbsorbsAndOfAnd) { // The optimized match expression should not have and AND children of AND nodes. This should be // collapsed during optimization. + BSONObj serialized; + { + BSONObjBuilder builder; + optimized->serialize(&builder, true); + serialized = builder.obj(); + } + BSONObj expectedSerialization = fromjson( "{$and: [{$expr: {$and: [{$eq: ['$a', {$const: 1}]}, {$eq: ['$b', {$const: 2}]}]}}," "{a: {$_internalExprEq: 1}}, {b: {$_internalExprEq: 2}}]}"); - ASSERT_BSONOBJ_EQ(optimized->serialize(), expectedSerialization); + ASSERT_BSONOBJ_EQ(serialized, expectedSerialization); } TEST_F(ExprMatchTest, ExpressionEvaluationReturnsResultsCorrectly) { @@ -734,198 +735,5 @@ TEST_F(ExprMatchTest, ExpressionEvaluationReturnsResultsCorrectly) { ASSERT_EQUALS(-2, expressionResult.coerceToInt()); } -TEST_F(ExprMatchTest, ExprRedactsCorrectly) { - boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); - createMatcher(fromjson("{$expr: {$sum: [\"$a\", \"$b\"]}}")); - - SerializationOptions opts = SerializationOptions::kDebugShapeAndMarkIdentifiers_FOR_TEST; - - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({"$expr":{"$sum":["$HASH<a>","$HASH<b>"]}})", - serialize(opts)); - - createMatcher(fromjson("{$expr: {$sum: [\"$a\", \"b\"]}}")); - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({"$expr":{"$sum":["$HASH<a>","?string"]}})", - serialize(opts)); - - createMatcher(fromjson("{$expr: {$sum: [\"$a.b\", \"$b\"]}}")); - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({"$expr":{"$sum":["$HASH<a>.HASH<b>","$HASH<b>"]}})", - serialize(opts)); - - createMatcher(fromjson("{$expr: {$eq: [\"$a\", \"$$NOW\"]}}")); - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({ - "$and": [ - { - "HASH<a>": { - "$_internalExprEq": "?date" - } - }, - { - "$expr": { - "$eq": [ - "$HASH<a>", - "?date" - ] - } - } - ] - })", - serialize(opts)); - - createMatcher(fromjson("{$expr: {$eq: [\"$a\", \"$$NOW\"]}}")); - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({ - "$and": [ - { - "HASH<a>": { - "$_internalExprEq": "?date" - } - }, - { - "$expr": { - "$eq": [ - "$HASH<a>", - "?date" - ] - } - } - ] - })", - serialize(opts)); - - createMatcher(fromjson("{$expr: {$getField: {field: \"b\", input: {a: 1, b: 2}}}}")); - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({"$expr":{"$getField":{"field":"HASH<b>","input":"?object"}}})", - serialize(opts)); - - createMatcher(fromjson("{$expr: {$getField: {field: \"b\", input: \"$a\"}}}")); - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({"$expr":{"$getField":{"field":"HASH<b>","input":"$HASH<a>"}}})", - serialize(opts)); - - createMatcher(fromjson("{$expr: {$getField: {field: \"b\", input: {a: 1, b: \"$c\"}}}}")); - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({ - "$expr": { - "$getField": { - "field": "HASH<b>", - "input": { - "HASH<a>": "?number", - "HASH<b>": "$HASH<c>" - } - } - } - })", - serialize(opts)); - - createMatcher(fromjson("{$expr: {$getField: {field: \"b.c\", input: {a: 1, b: \"$c\"}}}}")); - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({ - "$expr": { - "$getField": { - "field": "HASH<b>.HASH<c>", - "input": { - "HASH<a>": "?number", - "HASH<b>": "$HASH<c>" - } - } - } - })", - serialize(opts)); - - createMatcher( - fromjson("{$expr: {$setField: {field: \"b\", input: {a: 1, b: \"$c\"}, value: 5}}}")); - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({ - "$expr": { - "$setField": { - "field": "HASH<b>", - "input": { - "HASH<a>": "?number", - "HASH<b>": "$HASH<c>" - }, - "value": "?number" - } - } - })", - serialize(opts)); - - createMatcher(fromjson( - "{$expr: {$setField: {field: \"b.c\", input: {a: 1, b: \"$c\"}, value: \"$d\"}}}")); - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({ - "$expr": { - "$setField": { - "field": "HASH<b>.HASH<c>", - "input": { - "HASH<a>": "?number", - "HASH<b>": "$HASH<c>" - }, - "value": "$HASH<d>" - } - } - })", - serialize(opts)); - - createMatcher(fromjson( - "{$expr: {$setField: {field: \"b.c\", input: {a: 1, b: \"$c\"}, value: \"$d.e\"}}}")); - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({ - "$expr": { - "$setField": { - "field": "HASH<b>.HASH<c>", - "input": { - "HASH<a>": "?number", - "HASH<b>": "$HASH<c>" - }, - "value": "$HASH<d>.HASH<e>" - } - } - })", - serialize(opts)); - - createMatcher( - fromjson("{$expr: {$setField: {field: \"b\", input: {a: 1, b: \"$c\"}, value: {a: 1, b: 2, " - "c: 3}}}}")); - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({ - "$expr": { - "$setField": { - "field": "HASH<b>", - "input": { - "HASH<a>": "?number", - "HASH<b>": "$HASH<c>" - }, - "value": "?object" - } - } - })", - serialize(opts)); - - createMatcher( - fromjson("{$expr: {$setField: {field: \"b\", input: {a: 1, b: \"$c\"}, value: {a: 1, b: 2, " - "c: \"$d\"}}}}")); - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({ - "$expr": { - "$setField": { - "field": "HASH<b>", - "input": { - "HASH<a>": "?number", - "HASH<b>": "$HASH<c>" - }, - "value": { - "HASH<a>": "?number", - "HASH<b>": "?number", - "HASH<c>": "$HASH<d>" - } - } - } - })", - serialize(opts)); -} } // namespace } // namespace mongo diff --git a/src/mongo/db/matcher/expression_geo.cpp b/src/mongo/db/matcher/expression_geo.cpp index 9050de83741..fe592df742e 100644 --- a/src/mongo/db/matcher/expression_geo.cpp +++ b/src/mongo/db/matcher/expression_geo.cpp @@ -33,7 +33,6 @@ #include "mongo/bson/simple_bsonobj_comparator.h" #include "mongo/db/geo/geoparser.h" -#include "mongo/db/matcher/expression_geo_serializer.h" #include "mongo/db/matcher/expression_parser.h" #include "mongo/logv2/log.h" #include "mongo/platform/basic.h" @@ -245,11 +244,6 @@ Status GeoNearExpression::parseNewQuery(const BSONObj& obj) { if (fieldName == "$geometry") { if (e.isABSONObj()) { BSONObj embeddedObj = e.embeddedObject(); - // TODO SERVER-84598: $geoNear queries don't do a type check on the type of the - // geometry provided into the query. This is different than the behaviour we see for - // other GeoExpressions that use a GeometryContainer. GeoNear is a special case that - // only ever has type 'Point', however it may be a good idea to consider removing - // this silent acceptance of invalid "type" in the long term. Status status = GeoParser::parseQueryPoint(e, centroid.get()); if (!status.isOK()) { return Status(ErrorCodes::BadValue, @@ -432,7 +426,7 @@ void GeoMatchExpression::debugString(StringBuilder& debug, int indentationLevel) _debugAddSpace(debug, indentationLevel); BSONObjBuilder builder; - serialize(&builder, {}); + serialize(&builder, true); debug << "GEO raw = " << builder.obj().toString(); MatchExpression::TagData* td = getTag(); @@ -443,14 +437,10 @@ void GeoMatchExpression::debugString(StringBuilder& debug, int indentationLevel) debug << "\n"; } -void GeoMatchExpression::appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts, - bool includePath) const { - if (opts.literalPolicy != LiteralSerializationPolicy::kUnchanged) { - geoExpressionCustomSerialization(*bob, _rawObj, opts, includePath); - return; - } - bob->appendElements(_rawObj); +BSONObj GeoMatchExpression::getSerializedRightHandSide() const { + BSONObjBuilder subobj; + subobj.appendElements(_rawObj); + return subobj.obj(); } bool GeoMatchExpression::equivalent(const MatchExpression* other) const { @@ -505,14 +495,10 @@ void GeoNearMatchExpression::debugString(StringBuilder& debug, int indentationLe debug << "\n"; } -void GeoNearMatchExpression::appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts, - bool includePath) const { - if (opts.literalPolicy != LiteralSerializationPolicy::kUnchanged) { - geoNearExpressionCustomSerialization(*bob, _rawObj, opts, includePath); - return; - } - bob->appendElements(_rawObj); +BSONObj GeoNearMatchExpression::getSerializedRightHandSide() const { + BSONObjBuilder objBuilder; + objBuilder.appendElements(_rawObj); + return objBuilder.obj(); } bool GeoNearMatchExpression::equivalent(const MatchExpression* other) const { diff --git a/src/mongo/db/matcher/expression_geo.h b/src/mongo/db/matcher/expression_geo.h index b58904a7eff..bd3752399c8 100644 --- a/src/mongo/db/matcher/expression_geo.h +++ b/src/mongo/db/matcher/expression_geo.h @@ -73,8 +73,7 @@ private: // e.g. // { "$intersect" : { "$geometry" : { "type" : "Point", "coordinates": [ 40, 5 ] } } } Status parseQuery(const BSONObj& obj); - BSONObj redactGeoExpression(const BSONObj& obj, - boost::optional<StringData> literalArgsReplacement); + // Name of the field in the query. std::string field; std::shared_ptr<GeometryContainer> geoContainer; @@ -107,9 +106,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel = 0) const; - void appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts = {}, - bool includePath = true) const final; + BSONObj getSerializedRightHandSide() const final; virtual bool equivalent(const MatchExpression* other) const; @@ -209,9 +206,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel = 0) const; - void appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts = {}, - bool includePath = true) const final; + BSONObj getSerializedRightHandSide() const final; virtual bool equivalent(const MatchExpression* other) const; @@ -251,10 +246,8 @@ public: TwoDPtInAnnulusExpression(const R2Annulus& annulus, StringData twoDPath) : LeafMatchExpression(INTERNAL_2D_POINT_IN_ANNULUS, twoDPath), _annulus(annulus) {} - void serialize(BSONObjBuilder* out, - const SerializationOptions& opts = {}, - bool includePath = true) const final { - out->append("$TwoDPtInAnnulusExpression", true); + void serialize(BSONObjBuilder* out, bool includePath) const final { + out->append("TwoDPtInAnnulusExpression", true); } bool matchesSingleElement(const BSONElement& e, MatchDetails* details = nullptr) const final { @@ -272,9 +265,7 @@ public: // These won't be called. // - void appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts = {}, - bool includePath = true) const final { + BSONObj getSerializedRightHandSide() const final { MONGO_UNREACHABLE; } diff --git a/src/mongo/db/matcher/expression_geo_serializer.cpp b/src/mongo/db/matcher/expression_geo_serializer.cpp deleted file mode 100644 index 04643133335..00000000000 --- a/src/mongo/db/matcher/expression_geo_serializer.cpp +++ /dev/null @@ -1,391 +0,0 @@ -/** - * Copyright (C) 2023-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * 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 - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * 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 Server Side 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/db/matcher/expression_geo_serializer.h" - -#include "mongo/db/geo/geoparser.h" - -namespace mongo { -namespace { -void appendGeoNearLegacyArray(BSONObjBuilder& bob, - const BSONElement& e, - const SerializationOptions& opts) { - if (opts.literalPolicy != LiteralSerializationPolicy::kToRepresentativeParseableValue) { - opts.appendLiteral(&bob, e); - } else { - // Legacy $geoNear, $nearSphere, and $near require at minimum 2 coordinates to be - // re-parseable, so the representative value is [1, 1]. - StringData fieldName = e.fieldNameStringData(); - bob.appendArray(fieldName, BSON_ARRAY(1 << 1)); - } -} - -void appendShapeOperator(BSONObjBuilder& bob, - const BSONElement& e, - const SerializationOptions& opts) { - if (opts.literalPolicy != LiteralSerializationPolicy::kToRepresentativeParseableValue) { - opts.appendLiteral(&bob, e); - return; - } - - StringData fieldName = e.fieldNameStringData(); - if (fieldName == "$center"_sd || fieldName == "$centerSphere"_sd) { - // $center and $centerSphere requires a pair of coordinates and a radius to be - // re-parseable, so the representative value is [[1, 1],1]. - bob.appendArray(fieldName, BSON_ARRAY(BSON_ARRAY(1 << 1) << 1)); - } else if (fieldName == "$box"_sd) { - // $box requires two pairs of coordinates to be re-parseable, so the - // representative value is [[1, 1],[1,1]]. - bob.appendArray(fieldName, BSON_ARRAY(BSON_ARRAY(1 << 1) << BSON_ARRAY(1 << 1))); - } else if (fieldName == "$polygon"_sd) { - // $polygon requires three pairs of coordinates to be re-parseable, so the representative - // value is [[0,0],[0,1],[1,1]]. - bob.appendArray(fieldName, - BSON_ARRAY(BSON_ARRAY(0 << 0) << BSON_ARRAY(0 << 1) << BSON_ARRAY(1 << 1))); - } else { - opts.appendLiteral(&bob, e); - } -} - -void appendGeoJSONCoordinatesLiteral(BSONObjBuilder& bob, - const BSONElement& coordinatesElem, - const BSONElement& typeElem, - const SerializationOptions& opts) { - if (opts.literalPolicy != LiteralSerializationPolicy::kToRepresentativeParseableValue) { - opts.appendLiteral(&bob, coordinatesElem); - return; - } - - StringData fieldName = coordinatesElem.fieldNameStringData(); - - // When a $geoNear expression is parsed (see GeoNearExpression::parseNewQuery()), a $geometry - // object defaults to being parsed as a point, without checking the type of the geometry object. - // This means we can query for a $geoNear expression that specifies an invalid type, or no type - // at all. In order to accomodate this case, we default to type: 'Point' to ensure our - // representative shape is re-parseable. - auto geoType = GeoParser::geoJSONTypeStringToEnum(typeElem.valueStringData()); - if (geoType == GeoParser::GEOJSON_UNKNOWN) { - geoType = GeoParser::GEOJSON_POINT; - } - switch (geoType) { - case GeoParser::GEOJSON_POLYGON: { - // Polygon requires four pairs of coordinates in a closed loop wrapped in an array to be - // re-parseable, so the representative value is [[[0,0],[0,1],[1,1],[0,0]]]. - bob.appendArray( - fieldName, - BSON_ARRAY(BSON_ARRAY(BSON_ARRAY(0 << 0) << BSON_ARRAY(0 << 1) << BSON_ARRAY(1 << 1) - << BSON_ARRAY(0 << 0)))); - return; - } - case GeoParser::GEOJSON_MULTI_POLYGON: { - // MultiPolygon requires four pairs of coordinates in a closed loop wrapped in 2 arrays - // to be re-parseable, so the representative value is [[[[0,0],[0,1],[1,1],[0,0]]]]. - bob.appendArray(fieldName, - BSON_ARRAY(BSON_ARRAY(BSON_ARRAY( - BSON_ARRAY(0 << 0) << BSON_ARRAY(0 << 1) << BSON_ARRAY(1 << 1) - << BSON_ARRAY(0 << 0))))); - return; - } - case GeoParser::GEOJSON_POINT: { - // Point requires a pair of coordinates to be re-parseable, so the representative - // value is [1,1]. - bob.appendArray(fieldName, BSON_ARRAY(1 << 1)); - return; - } - case GeoParser::GEOJSON_MULTI_POINT: { - // MultiPoint requires a pair of coordinates wrapped in an array to be re-parseable, so - // the representative value is [[1,1]]. - bob.appendArray(fieldName, BSON_ARRAY(BSON_ARRAY(1 << 1))); - return; - } - case GeoParser::GEOJSON_LINESTRING: { - // LineString requires two pairs of coordinates to be re-parseable, so the - // representative value is [[0,0],[1,1]]. - bob.appendArray(fieldName, BSON_ARRAY(BSON_ARRAY(0 << 0) << BSON_ARRAY(1 << 1))); - return; - } - case GeoParser::GEOJSON_MULTI_LINESTRING: { - // MultiLineString requires two LineStrings wrapped in an array to be re-parseable, so - // the representative value is [[[0,0],[1,1]],[[0,0],[1,1]]]. - bob.appendArray(fieldName, - BSON_ARRAY(BSON_ARRAY(BSON_ARRAY(0 << 0) << BSON_ARRAY(1 << 1)) - << BSON_ARRAY(BSON_ARRAY(0 << 0) << BSON_ARRAY(1 << 1)))); - return; - } - case GeoParser::GEOJSON_GEOMETRY_COLLECTION: - opts.appendLiteral(&bob, coordinatesElem); - return; - case GeoParser::GEOJSON_UNKNOWN: - break; - } - - tasserted(8456600, - str::stream() << "unexpected geo type found in coordinates serialization: " - << geoType); -} - -void appendCRSObject(BSONObjBuilder& bob, - const BSONElement& crsObj, - const SerializationOptions& opts) { - // 'crs' is always an object. - tassert(7559700, "Expected 'crs' to be an object", crsObj.type() == BSONType::Object); - // 'crs' is required to have a 'type' field with the value 'name'. - // Additionally, it is required to have an object properties field - // with a single 'name' field. - tassert(7559701, - str::stream() << "Expected 'crs' to contain a string 'type' field, got " << crsObj, - crsObj["type"] && crsObj["type"].type() == BSONType::String); - tassert(7559702, - str::stream() << "Expected 'crs' to contain a 'properties' object, got , " << crsObj, - crsObj["properties"] && crsObj["properties"].type() == BSONType::Object); - tassert(7559703, - str::stream() << "Expected 'crs.properties' to contain a 'name' " - "string field, got " - << crsObj["properties"], - crsObj["properties"].Obj()["name"] && - crsObj["properties"].Obj()["name"].type() == BSONType::String); - - // The CRS "type" and "properties.name" fields must be preserved for - // kToRepresentativeParseableValue serialization policy so the query - // shape can be re-parsed (and will be preserved for kUnchanged policy - // as well). - BSONObjBuilder crsObjBuilder(bob.subobjStart("crs")); - if (opts.literalPolicy == LiteralSerializationPolicy::kToDebugTypeString) { - opts.appendLiteral(&crsObjBuilder, crsObj["type"]); - } else { - crsObjBuilder.append(crsObj["type"]); - } - BSONObjBuilder crsPropBuilder(crsObjBuilder.subobjStart("properties")); - if (opts.literalPolicy == LiteralSerializationPolicy::kToDebugTypeString) { - opts.appendLiteral(&crsPropBuilder, crsObj["properties"].Obj()["name"]); - } else { - crsPropBuilder.append(crsObj["properties"].Obj()["name"]); - } - crsPropBuilder.doneFast(); - crsObjBuilder.doneFast(); -} - -// This function maps closely to GeoParser::parseFromGeoJSON. We serialize a GeoJSON object that -// should have a type, coordinates, etc. This format may have been used as the RHS for a $geometry -// obj, or implicitly as the RHS of a $geoNear. -void appendGeoJSONObj(BSONObjBuilder& bob, - const BSONObj& geometryObj, - const SerializationOptions& opts) { - auto typeElem = geometryObj["type"]; - if (typeElem) { - bob.append(typeElem); - } - if (auto coordinatesElem = geometryObj["coordinates"]) { - appendGeoJSONCoordinatesLiteral(bob, coordinatesElem, typeElem, opts); - } else if (auto geometriesElem = geometryObj[GEOJSON_GEOMETRIES]) { - // We have a collection of geometries rather than a single one. Recursively - // serialize them and add to the output object. - BSONArrayBuilder geometriesArrBuilder; - for (const auto& geometry : geometriesElem.Array()) { - BSONObjBuilder geometryBuilder; - appendGeoJSONObj(geometryBuilder, geometry.Obj(), opts); - geometriesArrBuilder.append(geometryBuilder.obj()); - } - bob.append(GEOJSON_GEOMETRIES, geometriesArrBuilder.arr()); - } - - // 'crs' can be present if users want to use STRICT_SPHERE coordinate - // system. - if (auto crsElt = geometryObj["crs"]) { - appendCRSObject(bob, crsElt, opts); - } -} - -/* appendGeometryOperator() implements the serialization of a $geometry, which could be either an - * Array or Object. This code is closely tied to GeometryContainer parsing. During GeometryContainer - * parsing, if the $geometry element is an array type, or if the first element of the object is a - * number, we parse the element as a Point. Parsing a legacy point does not enforce specific field - * names, the only constraint is that the coordinates be numeric. So you can essentially have a - * $geometry: {"foo":1, "banana": 2} and it will interpret is as a point with x:1, y: 2. - */ -void appendGeometryOperator(BSONObjBuilder& bob, - const BSONElement& geometryElem, - const SerializationOptions& opts) { - if (geometryElem.type() == BSONType::Array) { - // This would be like {$geometry: [0, 0]} which must be a point. - auto asArray = geometryElem.Array(); - tassert(7539807, - "Expected the point to have exactly 2 elements: an x and y.", - asArray.size() == 2UL); - bob.appendArray( - "$geometry"_sd, - BSON_ARRAY(opts.serializeLiteral(asArray[0]) << opts.serializeLiteral(asArray[1]))); - } else { - const auto& geometryObj = geometryElem.Obj(); - BSONObjBuilder nestedSubObj = bob.subobjStart("$geometry"_sd); - if (geometryObj.firstElement().isNumber()) { - BSONElement x, y; - auto status = GeoParser::parseFlatPointCoordinates(geometryElem, x, y); - tassert(8548500, - "Expected the point to have exactly 2 elements: an x and y.", - status.isOK()); - opts.appendLiteral(&nestedSubObj, x); - opts.appendLiteral(&nestedSubObj, y); - } else { - appendGeoJSONObj(nestedSubObj, geometryObj, opts); - } - nestedSubObj.doneFast(); - } -} - -/** - * Typically, geoNear expressions have a single embedded object under the top-level - * geoNear operator, but there is an exception for syntax that allows geoJSON - * coordinates without specifying $geometry (e.g., {$nearSphere: {type: 'Point', - * coordinates: [1,2]}}). We're iterating outer_it and appending extra literals to - * handle $minDistance and $maxDistance fields that could be included outside the - * primary geo object in those edge cases (e.g., {$nearSphere: {type: 'Point', - * coordinates: [1,2]}, $minDistance: 10}). - */ -void appendGeoNearOperator(BSONObjBuilder& bob, - StringData fieldName, - const BSONElement& geoNearElem, - const SerializationOptions& opts) { - if (geoNearElem.type() == mongo::Array) { - appendGeoNearLegacyArray(bob, geoNearElem, opts); - } else { - BSONObjBuilder subObj = BSONObjBuilder(bob.subobjStart(fieldName)); - auto geoNearObj = geoNearElem.Obj(); - BSONObjIterator embedded_it(geoNearObj); - tassert(8548501, "Expected non-empty geometry object.", embedded_it.more()); - - // If the first element of the embedded object is numeric, we could be dealing - // with a legacy style embedded coordinate pair. - if (geoNearObj.firstElement().isNumber()) { - BSONElement x, y; - auto status = GeoParser::parseFlatPointCoordinates(geoNearObj.firstElement(), x, y); - // If we successfully parsed a legacy flat point as an embedded object, we - // can return as we are done parsing the geometry. - if (status.isOK()) { - opts.appendLiteral(&subObj, x); - opts.appendLiteral(&subObj, y); - subObj.doneFast(); - return; - } - } - // We couldn't parse a legacy coordinate pair expressed as an embedded object, so we - // enumerate the embedded geometry obj and parse the internals. Typically we expect to find - // $geometry, a GeoJSONPoint or $minDistance/$maxDistance/$uniqueDocs. - while (embedded_it.more()) { - auto embeddedElem = embedded_it.next(); - auto embeddedFieldName = embeddedElem.fieldNameStringData(); - if (embeddedFieldName == "$geometry"_sd) { - appendGeometryOperator(subObj, embeddedElem, opts); - } else if (embeddedFieldName == "crs" || embeddedFieldName == "type"_sd || - embeddedFieldName == "coordinates"_sd) { - appendGeoJSONObj(subObj, geoNearObj, opts); - } else { - // $minDistance/$maxDistance/$uniqueDocs. - opts.appendLiteral(&subObj, embeddedElem); - } - } - subObj.doneFast(); - } -} -} // namespace - -/** - * geoNearExpressionCustomSerialization implements the serialization of geoNear queries (i.e $near, - * $geoNear, $nearSphere). GeoNear queries can be broken down into two styles of queries: - * 1. Legacy Style (see GeoNearExpression::parseLegacyQuery()): - * t.find({ loc : { $nearSphere: [0,0], $minDistance: 1, $maxDistance: 3 }}) - * t.find({ loc : { $nearSphere: [0,0] }}) - * t.find({ loc : { $near : [0, 0, 1] } }); - * t.find({ loc : { $near: { someGeoJSONPoint}}) - * t.find({ loc : { $geoNear: { someGeoJSONPoint}}) - * 2. New style queries: - * t.find({loc: {$geoNear : { $geometry: {someGeoJSONPoint}, $maxDistance:3 }}}) - */ -void geoNearExpressionCustomSerialization(BSONObjBuilder& bob, - const BSONObj& obj, - const SerializationOptions& opts, - bool includePath) { - BSONObjIterator outer_it(obj); - while (outer_it.more()) { - auto elem = outer_it.next(); - if (elem.isABSONObj()) { - StringData fieldName = elem.fieldNameStringData(); - if (fieldName == "$near"_sd || fieldName == "$geoNear"_sd || - fieldName == "$nearSphere"_sd) { - appendGeoNearOperator(bob, fieldName, elem, opts); - } else if (fieldName == "$geometry"_sd) { - appendGeometryOperator(bob, elem, opts); - } else { - opts.appendLiteral(&bob, elem); - } - } else { - // Here we only expect to see $minDistance/$maxDistance/$uniqueDocs - opts.appendLiteral(&bob, elem); - } - } -} - -/** - * geoExpressionCustomSerialization() implements the serialization of geoExpressions ($within, - * $geoWithin, $geoIntersects). Examples of such expressions are: - * { $geoWithin : { $geometry : <GeoJSON> } } - * { $geoIntersects : { $geometry : <GeoJSON> } } - * { $geoWithin : { $box : [[x1, y1], [x2, y2]] } } - * { $geoWithin : { $polygon : [[x1, y1], [x1, y2], [x2, y2], [x2, y1]] } } - * { $geoWithin : { $center : [[x1, y1], r], } } - * { $geoWithin : { $centerSphere : [[x, y], radius] } } - * { $geoIntersects : { $geometry : [1, 2] } } - */ -void geoExpressionCustomSerialization(BSONObjBuilder& bob, - const BSONObj& obj, - const SerializationOptions& opts, - bool includePath) { - BSONObjIterator outerIt(obj); - BSONElement geoExprElem = outerIt.next(); - tassert(8548502, "Invalid extra fields in geo expression.", !outerIt.more()); - tassert(8548503, "Geo expression must be an object.", geoExprElem.type() == mongo::Object); - auto fieldName = geoExprElem.fieldNameStringData(); - - BSONObjBuilder subObj = BSONObjBuilder(bob.subobjStart(fieldName)); - auto geoObj = geoExprElem.Obj(); - BSONObjIterator embedded_it(geoObj); - while (embedded_it.more()) { - auto elem = embedded_it.next(); - fieldName = elem.fieldNameStringData(); - // $geoWithin/$geoIntersects can only have a $geometry or shape operators as operands - // (i.e $polygon, $box, etc.) - if (fieldName == "$geometry"_sd) { - appendGeometryOperator(subObj, elem, opts); - break; - } else { - appendShapeOperator(subObj, elem, opts); - } - } -} -} // namespace mongo diff --git a/src/mongo/db/matcher/expression_geo_serializer.h b/src/mongo/db/matcher/expression_geo_serializer.h deleted file mode 100644 index aac11318466..00000000000 --- a/src/mongo/db/matcher/expression_geo_serializer.h +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright (C) 2023-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * 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 - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * 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 Server Side 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. - */ - -#pragma once - -#include "mongo/bson/bsonelement.h" -#include "mongo/bson/bsonobj.h" -#include "mongo/db/query/query_shape/serialization_options.h" - -namespace mongo { - -/** - * We rely on these custom serializers for geo expressions to handle serialization with - * kToRepresentativeParseableValue and kToDebugTypeString policies since the original raw query - * needs to be re-parsed in order to properly serialize. - * - * Ideally each sub operator ($minDistance, $maxDistance, $geometry, $box) would serialize itself, - * rather than GeoExpression reparse the query during serialization. However, GeoExpression and - * GeoNearExpression don't capture the nesting of the various sub-operators. Re-parsing is therefore - * required to serialize GeoMatchExpression and GeoNearMatchExpression into BSON representative of - * the correct original query. - * - * To further complicate the serialization, serializing with policy - * kToRepresentativeParseableValue requires output that can again be - * re-parsed, and the geoparser performs validation checking to make sure input coordinates apply to - * the correct geo type. For example, a GeoJSON Polygon must have minimum four pairs of coordinates - * in a closed loop. The default representative parseable array value used in const - * SerializationOptions (an empty array) is not useful here since it won't pass geo validation - * checks. As a workaround, this custom serializer determines a parseable value for each shape or - * point type. - */ -void geoNearExpressionCustomSerialization(BSONObjBuilder& bob, - const BSONObj& obj, - const SerializationOptions& opts = {}, - bool includePath = true); - -void geoExpressionCustomSerialization(BSONObjBuilder& bob, - const BSONObj& obj, - const SerializationOptions& opts = {}, - bool includePath = true); -} // namespace mongo diff --git a/src/mongo/db/matcher/expression_geo_test.cpp b/src/mongo/db/matcher/expression_geo_test.cpp index 4c2d81cbfa8..4115285de18 100644 --- a/src/mongo/db/matcher/expression_geo_test.cpp +++ b/src/mongo/db/matcher/expression_geo_test.cpp @@ -33,13 +33,11 @@ #include <memory> -#include "mongo/db/exec/document_value/document_value_test_util.h" #include "mongo/db/jsobj.h" #include "mongo/db/json.h" #include "mongo/db/matcher/expression.h" #include "mongo/db/matcher/expression_geo.h" - namespace mongo { TEST(ExpressionGeoTest, Geo1) { @@ -155,143 +153,6 @@ TEST(ExpressionGeoTest, GeoNearEquivalent) { } } - -TEST(ExpressionGeoTest, SerializeGeoExpressions) { - SerializationOptions opts = {}; - opts.transformIdentifiers = true; - opts.literalPolicy = LiteralSerializationPolicy::kToDebugTypeString; - { - BSONObj query = fromjson("{$within: {$box: [{x: 4, y: 4}, [6, 6]]}}"); - std::unique_ptr<GeoMatchExpression> ge(makeGeoMatchExpression(query)); - - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({"$within":{"$box":"?array<>"}})", - ge->getSerializedRightHandSide(opts)); - } - { - BSONObj query = fromjson( - "{$geoWithin: {$geometry: {type: \"MultiPolygon\", coordinates: [[[[20.0, 70.0],[30.0, " - "70.0],[30.0, 50.0],[20.0, 50.0],[20.0, 70.0]]]]}}}"); - std::unique_ptr<GeoMatchExpression> ge(makeGeoMatchExpression(query)); - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({ - "$geoWithin": { - "$geometry": { - "type": "MultiPolygon", - "coordinates": "?array<?array>" - } - } - })", - ge->getSerializedRightHandSide(opts)); - } - { - BSONObj query = fromjson( - R"({ - "$geoIntersects": { - "$geometry": { - "type": "MultiPolygon", - "coordinates": [[[ - [-20.0, -70.0], - [-30.0, -70.0], - [-30.0, -50.0], - [-20.0, -50.0], - [-20.0, -70.0] - ]]] - } - } - })"); - std::unique_ptr<GeoMatchExpression> ge(makeGeoMatchExpression(query)); - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({ - "$geoIntersects": { - "$geometry": { - "type": "MultiPolygon", - "coordinates": "?array<?array>" - } - } - })", - ge->getSerializedRightHandSide(opts)); - } - { - BSONObj query1 = fromjson( - R"({$within: { - $geometry: { - type: 'Polygon', - coordinates: [[[0, 0], [3, 6], [6, 1], [0, 0]]] - } - }})"); - std::unique_ptr<GeoMatchExpression> ge(makeGeoMatchExpression(query1)); - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({"$within":{"$geometry":{"type":"Polygon","coordinates":"?array<?array>"}}})", - ge->getSerializedRightHandSide(opts)); - } - { - BSONObj query = fromjson( - "{$near: {$maxDistance: 100, " - "$geometry: {type: 'Point', coordinates: [0, 0]}}}"); - std::unique_ptr<GeoNearMatchExpression> gne(makeGeoNearMatchExpression(query)); - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({ - "$near": { - "$maxDistance": "?number", - "$geometry": { - "type": "Point", - "coordinates": "?array<?number>" - } - } - })", - gne->getSerializedRightHandSide(opts)); - } - { - BSONObj query = fromjson("{ $nearSphere: [0,0], $minDistance: 1, $maxDistance: 3 }"); - std::unique_ptr<GeoNearMatchExpression> gne(makeGeoNearMatchExpression(query)); - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({ - "$nearSphere": "?array<?number>", - "$minDistance": "?number", - "$maxDistance": "?number" - })", - gne->getSerializedRightHandSide(opts)); - } - - { - BSONObj query = fromjson("{$near : [0, 0, 1] }"); - std::unique_ptr<GeoNearMatchExpression> gne(makeGeoNearMatchExpression(query)); - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({"$near":"?array<?number>"})", - gne->getSerializedRightHandSide(opts)); - } - { - BSONObj query = fromjson("{$geoNear: [0, 0, 100]}"); - std::unique_ptr<GeoNearMatchExpression> gne(makeGeoNearMatchExpression(query)); - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({"$geoNear":"?array<?number>"})", - gne->getSerializedRightHandSide(opts)); - } - { - BSONObj query = fromjson("{$geoNear: [0, 10], $maxDistance: 80 }"); - std::unique_ptr<GeoNearMatchExpression> gne(makeGeoNearMatchExpression(query)); - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({"$geoNear":"?array<?number>","$maxDistance":"?number"})", - gne->getSerializedRightHandSide(opts)); - } - { - BSONObj query = fromjson("{$geoIntersects: {$geometry: [0, 0]}}"); - std::unique_ptr<GeoMatchExpression> ge(makeGeoMatchExpression(query)); - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({"$geoIntersects":{"$geometry":["?number","?number"]}})", - ge->getSerializedRightHandSide(opts)); - } - { - // Make sure we reject arrays with <2 or >2 elements. - BSONObj query = fromjson("{$geoIntersects: {$geometry: [0, 0, 1]}}"); - std::unique_ptr<GeoExpression> gq(new GeoExpression); - ASSERT_NOT_OK(gq->parseFrom(query)); - query = fromjson("{$geoIntersects: {$geometry: [0]}}"); - ASSERT_NOT_OK(gq->parseFrom(query)); - } -} - /** * A geo expression being not equivalent to another expression. */ @@ -321,280 +182,4 @@ TEST(ExpressionGeoTest, GeoNearNotEquivalent) { gne2(makeGeoNearMatchExpression(query2)); ASSERT(!gne1->equivalent(gne2.get())); } - -TEST(ExpressionGeoTest, SerializeWithCRSIFSpecifiedWithChangedOptions) { - BSONObj query1 = fromjson( - "{$within: {$geometry: {type: 'Polygon'," - "coordinates: [[[0, 0], [3, 6], [6, 1], [0, 0]]]," - "crs: {" - "type: 'name'," - "properties: { name: 'urn:x-mongodb:crs:strictwinding:EPSG:4326' }" - "}}}}"); - std::unique_ptr<GeoMatchExpression> ge1(makeGeoMatchExpression(query1)); - auto opts = SerializationOptions{LiteralSerializationPolicy::kToRepresentativeParseableValue}; - auto serialized = ge1->getSerializedRightHandSide(opts); - ASSERT_BSONOBJ_EQ_AUTO( - R"({ - "$within": { - "$geometry": { - "type": "Polygon", - "coordinates": [ - [ - [ - 0, - 0 - ], - [ - 0, - 1 - ], - [ - 1, - 1 - ], - [ - 0, - 0 - ] - ] - ], - "crs": { - "type": "name", - "properties": { - "name": "urn:x-mongodb:crs:strictwinding:EPSG:4326" - } - } - } - } - })", - serialized); - serialized = ge1->getSerializedRightHandSide(opts); - ASSERT_BSONOBJ_EQ_AUTO( - R"({ - "$within": { - "$geometry": { - "type": "Polygon", - "coordinates": [ - [ - [ - 0, - 0 - ], - [ - 0, - 1 - ], - [ - 1, - 1 - ], - [ - 0, - 0 - ] - ] - ], - "crs": { - "type": "name", - "properties": { - "name": "urn:x-mongodb:crs:strictwinding:EPSG:4326" - } - } - } - } - })", - serialized); -} - -template <typename CreateFn> -void assertRepresentativeShapeIsStable(BSONObj inputExpr, - BSONObj expectedRepresentativeExpr, - CreateFn createFn) { - auto opts = SerializationOptions{LiteralSerializationPolicy::kToRepresentativeParseableValue}; - auto ge(createFn(inputExpr)); - - auto serializedExpr = ge->getSerializedRightHandSide(opts); - ASSERT_BSONOBJ_EQ(serializedExpr, expectedRepresentativeExpr); - - auto roundTripped = createFn(serializedExpr); - ASSERT_BSONOBJ_EQ(roundTripped->getSerializedRightHandSide(opts), serializedExpr); -} - -void assertRepresentativeGeoShapeIsStable(BSONObj inputExpr, BSONObj expectedRepresentativeExpr) { - assertRepresentativeShapeIsStable( - inputExpr, expectedRepresentativeExpr, [](const BSONObj& input) { - return makeGeoMatchExpression(input); - }); -} - -void assertRepresentativeGeoNearShapeIsStable(BSONObj inputExpr, - BSONObj expectedRepresentativeExpr) { - assertRepresentativeShapeIsStable( - inputExpr, expectedRepresentativeExpr, [](const BSONObj& input) { - return makeGeoNearMatchExpression(input); - }); -} - -TEST(ExpressionGeoTest, RoundTripSerializeGeoExpressions) { - assertRepresentativeGeoShapeIsStable(fromjson("{$within: {$box: [{x: 4, y: 4}, [6, 6]]}}"), - fromjson("{$within: {$box: [[1, 1],[1, 1]]}}")); - - assertRepresentativeGeoShapeIsStable( - fromjson( - R"({$geoWithin: {$geometry: {type: "MultiPolygon", coordinates: [[[[20.0, 70.0],[30.0, 70.0],[30.0, 50.0],[20.0, 50.0],[20.0, 70.0]]]]}}})"), - fromjson( - R"({$geoWithin: {$geometry: {type: "MultiPolygon", coordinates: [[[[0, 0],[0, 1],[1, 1],[0, 0]]]]}}})")); - - assertRepresentativeGeoShapeIsStable(fromjson(R"({ - "$geoIntersects": { - "$geometry": { - "type": "MultiPolygon", - "coordinates": [[[ - [-20.0, -70.0], - [-30.0, -70.0], - [-30.0, -50.0], - [-20.0, -50.0], - [-20.0, -70.0] - ]]] - } - } - })"), - fromjson(R"({ - "$geoIntersects": { - "$geometry": { - "type": "MultiPolygon", - "coordinates": [[[[0, 0],[0, 1],[1, 1],[0, 0]]]] - } - } - })")); - - assertRepresentativeGeoShapeIsStable(fromjson(R"({$within: { - $geometry: { - type: 'Polygon', - coordinates: [[[0, 0], [3, 6], [6, 1], [0, 0]]] - } - }})"), - fromjson(R"({$within: { - $geometry: { - type: 'Polygon', - coordinates: [[[0, 0],[0, 1],[1, 1],[0, 0]]] - } - }})")); - - assertRepresentativeGeoNearShapeIsStable( - fromjson("{$near: {$maxDistance: 100, $geometry: {type: 'Point', coordinates: [0, 0]}}}"), - fromjson("{$near: {$maxDistance: 1, $geometry: {type: 'Point', coordinates: [1, 1]}}}")); - - assertRepresentativeGeoNearShapeIsStable( - fromjson("{$nearSphere: [0,0], $minDistance: 2, $maxDistance: 4 }"), - fromjson("{$nearSphere: [1,1], $minDistance: 1, $maxDistance: 1 }")); - - assertRepresentativeGeoNearShapeIsStable( - fromjson("{$minDistance: 2, $maxDistance: 4, $nearSphere: [0,0]}"), - fromjson("{$minDistance: 1, $maxDistance: 1, $nearSphere: [1,1]}")); - - assertRepresentativeGeoNearShapeIsStable(fromjson("{$near: [0, 0, 1]}"), - fromjson("{$near: [1, 1]}")); - - assertRepresentativeGeoNearShapeIsStable(fromjson("{$geoNear: [0, 0, 100]}"), - fromjson("{$geoNear: [1, 1]}")); - - assertRepresentativeGeoNearShapeIsStable(fromjson("{$geoNear: [0, 10], $maxDistance: 80 }"), - fromjson("{$geoNear: [1, 1], $maxDistance: 1}")); - - assertRepresentativeGeoShapeIsStable(fromjson("{$geoIntersects: {$geometry: [0, 0]}}"), - fromjson("{$geoIntersects: {$geometry: [1, 1]}}")); - // Test scenario with new $geometry query not specifying the geometry type. - assertRepresentativeGeoNearShapeIsStable( - fromjson("{$geoNear: { $geometry: {coordinates: [0, 10]}}}"), - fromjson("{$geoNear: { $geometry: {coordinates: [1, 1]}}}")); - - // Test scenario with new $geometry query specifying invalid type. - assertRepresentativeGeoNearShapeIsStable( - fromjson("{$geoNear: { $geometry: { type: 'b.c', coordinates: [0, 10]}}}"), - fromjson("{$geoNear: { $geometry: {type: 'b.c', coordinates: [1, 1]}}}")); - - // Test scenario with $nearSphere without $geometry and no type specified - assertRepresentativeGeoNearShapeIsStable(fromjson(R"({"$nearSphere":{"coordinates":[0,0]}})"), - fromjson(R"({"$nearSphere":{"coordinates":[1,1]}})")); - - // Test case with first field of $geometry as numeric field, arbitrary coordinate naming. - assertRepresentativeGeoShapeIsStable( - fromjson(R"({"$geoIntersects":{"$geometry":{"shardOptions":40,"y":5}}})"), - fromjson(R"({"$geoIntersects":{"$geometry":{"shardOptions":1,"y":1}}})")); - - assertRepresentativeGeoShapeIsStable(fromjson(R"({ - "$geoIntersects": { - "$geometry": { - "type": "MultiLineString", - "coordinates": [[ - [2, 0], - [2, 2] - ], [ - [0, 4], - [1, 4] - ]] - } - } - })"), - fromjson(R"({ - "$geoIntersects": { - "$geometry": { - "type": "MultiLineString", - "coordinates": [[[0, 0], [1, 1]],[[0, 0], [1, 1]]] - } - } - })")); - - assertRepresentativeGeoShapeIsStable(fromjson(R"({ - "$geoIntersects": { - "$geometry": { - "type": "LineString", - "coordinates": [ - [2, 0], - [2, 2] - ] - } - } - })"), - fromjson(R"({ - "$geoIntersects": { - "$geometry": { - "type": "LineString", - "coordinates": [[0, 0], [1, 1]] - } - } - })")); - - assertRepresentativeGeoShapeIsStable(fromjson(R"({ - "$geoIntersects": { - "$geometry": { - "type": "GeometryCollection", - "geometries": [{ - "type": "LineString", - "coordinates": [ - [2, 0], - [2, 2] - ] - }, { - type: 'Point', coordinates: [2, 2] - }] - } - } - })"), - fromjson(R"({ - "$geoIntersects": { - "$geometry": { - "type": "GeometryCollection", - "geometries": [{ - "type": "LineString", - "coordinates": [[0, 0], [1, 1]] - }, { - type: 'Point', coordinates: [1, 1] - }] - } - } - })")); -} - } // namespace mongo diff --git a/src/mongo/db/matcher/expression_internal_bucket_geo_within.cpp b/src/mongo/db/matcher/expression_internal_bucket_geo_within.cpp index e09f142a770..5bf75f8218f 100644 --- a/src/mongo/db/matcher/expression_internal_bucket_geo_within.cpp +++ b/src/mongo/db/matcher/expression_internal_bucket_geo_within.cpp @@ -51,7 +51,7 @@ void InternalBucketGeoWithinMatchExpression::debugString(StringBuilder& debug, _debugAddSpace(debug, indentationLevel); BSONObjBuilder builder; - serialize(&builder, {}); + serialize(&builder, true); debug << builder.obj().toString() << "\n"; const auto* tag = getTag(); @@ -194,17 +194,13 @@ bool InternalBucketGeoWithinMatchExpression::_matchesBSONObj(const BSONObj& obj) } void InternalBucketGeoWithinMatchExpression::serialize(BSONObjBuilder* builder, - const SerializationOptions& opts, bool includePath) const { BSONObjBuilder bob(builder->subobjStart(InternalBucketGeoWithinMatchExpression::kName)); - // Serialize the geometry shape. BSONObjBuilder withinRegionBob( bob.subobjStart(InternalBucketGeoWithinMatchExpression::kWithinRegion)); - opts.appendLiteral(&withinRegionBob, _geoContainer->getGeoElement()); + withinRegionBob.append(_geoContainer->getGeoElement()); withinRegionBob.doneFast(); - // Serialize the field which is being searched over. - bob.append(InternalBucketGeoWithinMatchExpression::kField, - opts.serializeFieldPathFromString(_field)); + bob.append(InternalBucketGeoWithinMatchExpression::kField, _field); bob.doneFast(); } diff --git a/src/mongo/db/matcher/expression_internal_bucket_geo_within.h b/src/mongo/db/matcher/expression_internal_bucket_geo_within.h index 0749e33adb4..b46b6c5076c 100644 --- a/src/mongo/db/matcher/expression_internal_bucket_geo_within.h +++ b/src/mongo/db/matcher/expression_internal_bucket_geo_within.h @@ -70,7 +70,6 @@ public: : MatchExpression(MatchExpression::INTERNAL_BUCKET_GEO_WITHIN, std::move(annotation)), _geoContainer(container), _indexField("data." + field), - _fieldRef(_indexField), _field(std::move(field)) {} void debugString(StringBuilder& debug, int indentationLevel) const final; @@ -95,9 +94,7 @@ public: return false; } - void serialize(BSONObjBuilder* builder, - const SerializationOptions& opts = {}, - bool includePath = true) const final; + void serialize(BSONObjBuilder* builder, bool includePath) const final; std::unique_ptr<MatchExpression> shallowClone() const final; @@ -130,7 +127,8 @@ public: } const FieldRef* fieldRef() const final { - return &_fieldRef; + MONGO_UNREACHABLE_TASSERT(5837104); + return nullptr; } void acceptVisitor(MatchExpressionMutableVisitor* visitor) final { @@ -157,7 +155,6 @@ private: std::shared_ptr<GeometryContainer> _geoContainer; std::string _indexField; - FieldRef _fieldRef; std::string _field; }; diff --git a/src/mongo/db/matcher/expression_internal_expr_eq_test.cpp b/src/mongo/db/matcher/expression_internal_expr_eq_test.cpp index facc1f9f7a4..0de1fbec00b 100644 --- a/src/mongo/db/matcher/expression_internal_expr_eq_test.cpp +++ b/src/mongo/db/matcher/expression_internal_expr_eq_test.cpp @@ -264,7 +264,7 @@ TEST(InternalExprEqMatchExpression, SerializesCorrectly) { operand.firstElement()); BSONObjBuilder bob; - eq.serialize(&bob, {}); + eq.serialize(&bob, true); ASSERT_BSONOBJ_EQ(BSON("x" << BSON("$_internalExprEq" << 5)), bob.obj()); } diff --git a/src/mongo/db/matcher/expression_leaf.cpp b/src/mongo/db/matcher/expression_leaf.cpp index 721b93c8da4..31157666e92 100644 --- a/src/mongo/db/matcher/expression_leaf.cpp +++ b/src/mongo/db/matcher/expression_leaf.cpp @@ -39,7 +39,6 @@ #include "mongo/bson/bsonmisc.h" #include "mongo/bson/bsonobj.h" #include "mongo/config.h" -#include "mongo/db/exec/document_value/value.h" #include "mongo/db/field_ref.h" #include "mongo/db/jsobj.h" #include "mongo/db/matcher/expression_parser.h" @@ -94,10 +93,8 @@ void ComparisonMatchExpressionBase::debugString(StringBuilder& debug, int indent debug << "\n"; } -void ComparisonMatchExpressionBase::appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts, - bool includePath) const { - opts.appendLiteral(bob, name(), _rhs); +BSONObj ComparisonMatchExpressionBase::getSerializedRightHandSide() const { + return BSON(name() << _rhs); } ComparisonMatchExpression::ComparisonMatchExpression(MatchType type, @@ -293,19 +290,15 @@ void RegexMatchExpression::debugString(StringBuilder& debug, int indentationLeve debug << "\n"; } -void RegexMatchExpression::appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts, - bool includePath) const { - // We need to be careful to generate a valid regex representative value, and the default string - // "?" is not valid. - opts.appendLiteral(bob, "$regex", _regex, Value("\\?"_sd)); +BSONObj RegexMatchExpression::getSerializedRightHandSide() const { + BSONObjBuilder regexBuilder; + regexBuilder.append("$regex", _regex); if (!_flags.empty()) { - // We need to make sure the $options value can be re-parsed as legal regex options, so - // we'll set the representative value in this case to be the string "i" rather than - // "?", which is the standard representative for string values. - opts.appendLiteral(bob, "$options", _flags, Value("i"_sd)); + regexBuilder.append("$options", _flags); } + + return regexBuilder.obj(); } void RegexMatchExpression::serializeToBSONTypeRegex(BSONObjBuilder* out) const { @@ -378,11 +371,8 @@ void ModMatchExpression::debugString(StringBuilder& debug, int indentationLevel) debug << "\n"; } -void ModMatchExpression::appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts, - bool includePath) const { - bob->append("$mod", - BSON_ARRAY(opts.serializeLiteral(_divisor) << opts.serializeLiteral(_remainder))); +BSONObj ModMatchExpression::getSerializedRightHandSide() const { + return BSON("$mod" << BSON_ARRAY(_divisor << _remainder)); } bool ModMatchExpression::equivalent(const MatchExpression* other) const { @@ -417,10 +407,8 @@ void ExistsMatchExpression::debugString(StringBuilder& debug, int indentationLev debug << "\n"; } -void ExistsMatchExpression::appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts, - bool includePath) const { - opts.appendLiteral(bob, "$exists", true); +BSONObj ExistsMatchExpression::getSerializedRightHandSide() const { + return BSON("$exists" << true); } bool ExistsMatchExpression::equivalent(const MatchExpression* other) const { @@ -446,7 +434,6 @@ std::unique_ptr<MatchExpression> InMatchExpression::shallowClone() const { } next->_hasNull = _hasNull; next->_hasEmptyArray = _hasEmptyArray; - next->_hasNonEmptyArrayOrObject = _hasNonEmptyArrayOrObject; next->_equalitySet = _equalitySet; next->_originalEqualityVector = _originalEqualityVector; next->_equalityStorage = _equalityStorage; @@ -502,45 +489,9 @@ void InMatchExpression::debugString(StringBuilder& debug, int indentationLevel) debug << "\n"; } -namespace { -/** - * Reduces the potentially large vector of elements to just the first of each "canonical" type. - * Different types of numbers are not considered distinct. - * - * For example, collapses [2, 4, NumberInt(3), "string", "another", 3, 5] into just [2, "string"]. - */ -std::vector<Value> justFirstOfEachType(std::vector<BSONElement> elems) { - stdx::unordered_set<int> seenTypes; - std::vector<Value> result; - for (auto&& elem : elems) { - bool inserted = seenTypes.insert(canonicalizeBSONType(elem.type())).second; - if (inserted) { - // A new type. - result.emplace_back(elem); - } - } - return result; -} -} // namespace - -void InMatchExpression::serializeToShape(BSONObjBuilder* bob, - const SerializationOptions& opts) const { - std::vector<Value> firstOfEachType = justFirstOfEachType(_equalitySet); - if (hasRegex()) { - firstOfEachType.emplace_back(BSONRegEx()); - } - opts.appendLiteral(bob, "$in", std::move(firstOfEachType)); -} - -void InMatchExpression::appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts, - bool includePath) const { - if (opts.literalPolicy != LiteralSerializationPolicy::kUnchanged) { - serializeToShape(bob, opts); - return; - } - - BSONArrayBuilder arrBob(bob->subarrayStart("$in")); +BSONObj InMatchExpression::getSerializedRightHandSide() const { + BSONObjBuilder inBob; + BSONArrayBuilder arrBob(inBob.subarrayStart("$in")); for (auto&& _equality : _equalitySet) { arrBob.append(_equality); } @@ -550,6 +501,7 @@ void InMatchExpression::appendSerializedRightHandSide(BSONObjBuilder* bob, arrBob.append(regexBob.obj().firstElement()); } arrBob.doneFast(); + return inBob.obj(); } bool InMatchExpression::equivalent(const MatchExpression* other) const { @@ -625,8 +577,6 @@ Status InMatchExpression::setEqualities(std::vector<BSONElement> equalities) { _hasNull = true; } else if (equality.type() == BSONType::Array && equality.Obj().isEmpty()) { _hasEmptyArray = true; - } else if (equality.type() == BSONType::Array || equality.type() == BSONType::Object) { - _hasNonEmptyArrayOrObject = true; } } @@ -845,25 +795,6 @@ bool BitTestMatchExpression::matchesSingleElement(const BSONElement& e, if (eDouble != static_cast<double>(static_cast<long long>(eDouble))) { return false; } - } else if (e.type() == BSONType::NumberDecimal) { - Decimal128 eDecimal = e.numberDecimal(); - - // NaN NumberDecimals are rejected. - if (eDecimal.isNaN()) { - return false; - } - - // NumberDecimals that are too large or small to be represented as a 64-bit signed - // integer are treated as 0. - if (eDecimal > Decimal128(std::numeric_limits<long long>::max()) || - eDecimal < Decimal128(std::numeric_limits<long long>::min())) { - return false; - } - - // This checks if e is an integral NumberDecimal. - if (eDecimal != eDecimal.round(Decimal128::kRoundTowardZero)) { - return false; - } } long long eValue = e.numberLong(); @@ -908,9 +839,7 @@ void BitTestMatchExpression::debugString(StringBuilder& debug, int indentationLe } } -void BitTestMatchExpression::appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts, - bool includePath) const { +BSONObj BitTestMatchExpression::getSerializedRightHandSide() const { std::string opString = ""; switch (matchType()) { @@ -935,10 +864,8 @@ void BitTestMatchExpression::appendSerializedRightHandSide(BSONObjBuilder* bob, arrBob.append(static_cast<int32_t>(bitPosition)); } arrBob.doneFast(); - // Unfortunately this cannot be done without copying the array into the BSONObjBuilder, since - // `opts.appendLiteral` may choose to append this actual array, a representative empty array, or - // a debug string. - opts.appendLiteral(bob, opString, arrBob.arr()); + + return BSON(opString << arrBob.arr()); } bool BitTestMatchExpression::equivalent(const MatchExpression* other) const { diff --git a/src/mongo/db/matcher/expression_leaf.h b/src/mongo/db/matcher/expression_leaf.h index 5f4f2666a94..46a80aa5e91 100644 --- a/src/mongo/db/matcher/expression_leaf.h +++ b/src/mongo/db/matcher/expression_leaf.h @@ -163,9 +163,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel = 0) const; - virtual void appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts = {}, - bool includePath = true) const; + BSONObj getSerializedRightHandSide() const final; virtual bool equivalent(const MatchExpression* other) const; @@ -508,9 +506,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel) const; - void appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts = {}, - bool includePath = true) const final; + BSONObj getSerializedRightHandSide() const final; void serializeToBSONTypeRegex(BSONObjBuilder* out) const; @@ -590,9 +586,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel) const; - void appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts = {}, - bool includePath = true) const final; + BSONObj getSerializedRightHandSide() const final; virtual bool equivalent(const MatchExpression* other) const; @@ -657,9 +651,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel) const; - void appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts = {}, - bool includePath = true) const final; + BSONObj getSerializedRightHandSide() const final; virtual bool equivalent(const MatchExpression* other) const; @@ -690,9 +682,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel) const; - void appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts = {}, - bool includePath = true) const final; + BSONObj getSerializedRightHandSide() const final; virtual bool equivalent(const MatchExpression* other) const; @@ -733,14 +723,6 @@ public: return _hasEmptyArray; } - bool hasNonEmptyArrayOrObject() const { - return _hasNonEmptyArrayOrObject; - } - - bool hasNonScalarOrNonEmptyValues() const { - return hasNonEmptyArrayOrObject() || hasNull() || hasRegex(); - } - void acceptVisitor(MatchExpressionMutableVisitor* visitor) final { visitor->visit(this); } @@ -760,21 +742,12 @@ public: private: ExpressionOptimizerFunc getOptimizer() const final; - /** - * A helper to serialize to something like {$in: "?array<?number>"} or similar, depending on - * 'opts' and whether we have a mixed-type $in or not. - */ - void serializeToShape(BSONObjBuilder* bob, const SerializationOptions& opts) const; - // Whether or not '_equalities' has a jstNULL element in it. bool _hasNull = false; // Whether or not '_equalities' has an empty array element in it. bool _hasEmptyArray = false; - // Whether or not '_equalities' has a non-empty array or object element in it. - bool _hasNonEmptyArrayOrObject = false; - // Collator used to construct '_eltCmp'; const CollatorInterface* _collator = nullptr; @@ -834,9 +807,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel) const; - void appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts = {}, - bool includePath = true) const final; + BSONObj getSerializedRightHandSide() const final; virtual bool equivalent(const MatchExpression* other) const; diff --git a/src/mongo/db/matcher/expression_leaf_test.cpp b/src/mongo/db/matcher/expression_leaf_test.cpp index cefae1506b9..99bedfa067f 100644 --- a/src/mongo/db/matcher/expression_leaf_test.cpp +++ b/src/mongo/db/matcher/expression_leaf_test.cpp @@ -1463,15 +1463,6 @@ TEST(BitTestMatchExpression, DoesNotMatchOther) { BSONObj notMatch8 = fromjson("{a: 1e100}"); // Too-Large Double BSONObj notMatch9 = fromjson("{a: ObjectId('000000000000000000000000')}"); // OID BSONObj notMatch10 = fromjson("{a: Date(54)}"); // Date - BSONObj notMatch11 = fromjson("{a: NumberDecimal(\"Infinity\")}"); // Infinity NumberDecimal - BSONObj notMatch12 = - fromjson("{a: NumberDecimal(\"-Infinity\")}"); // Negative infinity NumberDecimal - BSONObj notMatch13 = fromjson("{a: NumberDecimal(\"NaN\")}"); // NaN NumberDecimal - BSONObj notMatch14 = fromjson("{a: NumberDecimal(\"1e100\")}"); // Too-Large NumberDecimal - BSONObj notMatch15 = fromjson("{a: NumberDecimal(\"-1e100\")}"); // Too-Small NumberDecimal - BSONObj notMatch16 = fromjson("{a: NumberDecimal(\"5.5\")}"); // Non-integral NumberDecimal - BSONObj notMatch17 = - fromjson("{a: NumberDecimal(\"-5.5\")}"); // Negative-integral NumberDecimal BitsAllSetMatchExpression balls("a", bitPositions); BitsAllClearMatchExpression ballc("a", bitPositions); @@ -1492,13 +1483,6 @@ TEST(BitTestMatchExpression, DoesNotMatchOther) { ASSERT(!balls.matchesSingleElement(notMatch8["a"])); ASSERT(!balls.matchesSingleElement(notMatch9["a"])); ASSERT(!balls.matchesSingleElement(notMatch10["a"])); - ASSERT(!balls.matchesSingleElement(notMatch11["a"])); - ASSERT(!balls.matchesSingleElement(notMatch12["a"])); - ASSERT(!balls.matchesSingleElement(notMatch13["a"])); - ASSERT(!balls.matchesSingleElement(notMatch14["a"])); - ASSERT(!balls.matchesSingleElement(notMatch15["a"])); - ASSERT(!balls.matchesSingleElement(notMatch16["a"])); - ASSERT(!balls.matchesSingleElement(notMatch17["a"])); ASSERT(!ballc.matchesSingleElement(notMatch1["a"])); ASSERT(!ballc.matchesSingleElement(notMatch2["a"])); ASSERT(!ballc.matchesSingleElement(notMatch3["a"])); @@ -1509,13 +1493,6 @@ TEST(BitTestMatchExpression, DoesNotMatchOther) { ASSERT(!ballc.matchesSingleElement(notMatch8["a"])); ASSERT(!ballc.matchesSingleElement(notMatch9["a"])); ASSERT(!ballc.matchesSingleElement(notMatch10["a"])); - ASSERT(!ballc.matchesSingleElement(notMatch11["a"])); - ASSERT(!ballc.matchesSingleElement(notMatch12["a"])); - ASSERT(!ballc.matchesSingleElement(notMatch13["a"])); - ASSERT(!ballc.matchesSingleElement(notMatch14["a"])); - ASSERT(!ballc.matchesSingleElement(notMatch15["a"])); - ASSERT(!ballc.matchesSingleElement(notMatch16["a"])); - ASSERT(!ballc.matchesSingleElement(notMatch17["a"])); ASSERT(!banys.matchesSingleElement(notMatch1["a"])); ASSERT(!banys.matchesSingleElement(notMatch2["a"])); ASSERT(!banys.matchesSingleElement(notMatch3["a"])); @@ -1526,13 +1503,6 @@ TEST(BitTestMatchExpression, DoesNotMatchOther) { ASSERT(!banys.matchesSingleElement(notMatch8["a"])); ASSERT(!banys.matchesSingleElement(notMatch9["a"])); ASSERT(!banys.matchesSingleElement(notMatch10["a"])); - ASSERT(!banys.matchesSingleElement(notMatch11["a"])); - ASSERT(!banys.matchesSingleElement(notMatch12["a"])); - ASSERT(!banys.matchesSingleElement(notMatch13["a"])); - ASSERT(!banys.matchesSingleElement(notMatch14["a"])); - ASSERT(!banys.matchesSingleElement(notMatch15["a"])); - ASSERT(!banys.matchesSingleElement(notMatch16["a"])); - ASSERT(!banys.matchesSingleElement(notMatch17["a"])); ASSERT(!banyc.matchesSingleElement(notMatch1["a"])); ASSERT(!banyc.matchesSingleElement(notMatch2["a"])); ASSERT(!banyc.matchesSingleElement(notMatch3["a"])); @@ -1543,13 +1513,6 @@ TEST(BitTestMatchExpression, DoesNotMatchOther) { ASSERT(!banyc.matchesSingleElement(notMatch8["a"])); ASSERT(!banyc.matchesSingleElement(notMatch9["a"])); ASSERT(!banyc.matchesSingleElement(notMatch10["a"])); - ASSERT(!banyc.matchesSingleElement(notMatch11["a"])); - ASSERT(!banyc.matchesSingleElement(notMatch12["a"])); - ASSERT(!banyc.matchesSingleElement(notMatch13["a"])); - ASSERT(!banyc.matchesSingleElement(notMatch14["a"])); - ASSERT(!banyc.matchesSingleElement(notMatch15["a"])); - ASSERT(!banyc.matchesSingleElement(notMatch16["a"])); - ASSERT(!banyc.matchesSingleElement(notMatch17["a"])); } TEST(BitTestMatchExpression, MatchBinaryWithLongBitMask) { diff --git a/src/mongo/db/matcher/expression_optimize_test.cpp b/src/mongo/db/matcher/expression_optimize_test.cpp index 763d80fa2cb..eaa606e06df 100644 --- a/src/mongo/db/matcher/expression_optimize_test.cpp +++ b/src/mongo/db/matcher/expression_optimize_test.cpp @@ -47,9 +47,9 @@ using unittest::assertGet; /** * Helper function to parse the given BSON object as a MatchExpression, checks the status, - * and return the MatchExpression pointer. + * and return the MatchExpression*. */ -std::unique_ptr<MatchExpression> parseMatchExpression(const BSONObj& obj) { +MatchExpression* parseMatchExpression(const BSONObj& obj) { boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); StatusWithMatchExpression status = MatchExpressionParser::parse(obj, @@ -63,7 +63,7 @@ std::unique_ptr<MatchExpression> parseMatchExpression(const BSONObj& obj) { FAIL(ss); } - return std::move(status.getValue()); + return status.getValue().release(); } /** @@ -75,8 +75,7 @@ Status isValid(const std::string& queryStr, const FindCommandRequest& findComman BSONObj queryObj = fromjson(queryStr); std::unique_ptr<MatchExpression> me(parseMatchExpression(queryObj)); me = MatchExpression::optimize(std::move(me)); - if (auto status = parsed_find_command::isValid(me.get(), findCommand).getStatus(); - !status.isOK()) { + if (auto status = CanonicalQuery::isValid(me.get(), findCommand).getStatus(); !status.isOK()) { return status; } return CanonicalQuery::isValidNormalized(me.get()); @@ -351,14 +350,18 @@ TEST(ExpressionOptimizeTest, AndWithAlwaysFalseChildOptimizesToAlwaysFalse) { BSONObj obj = fromjson("{$and: [{a: 1}, {$alwaysFalse: 1}]}"); std::unique_ptr<MatchExpression> matchExpression(parseMatchExpression(obj)); auto optimizedMatchExpression = MatchExpression::optimize(std::move(matchExpression)); - ASSERT_BSONOBJ_EQ(optimizedMatchExpression->serialize(), fromjson("{$alwaysFalse: 1}")); + BSONObjBuilder bob; + optimizedMatchExpression->serialize(&bob, true); + ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{$alwaysFalse: 1}")); } TEST(ExpressionOptimizeTest, AndRemovesAlwaysTrueChildren) { BSONObj obj = fromjson("{$and: [{a: 1}, {$alwaysTrue: 1}]}"); std::unique_ptr<MatchExpression> matchExpression(parseMatchExpression(obj)); auto optimizedMatchExpression = MatchExpression::optimize(std::move(matchExpression)); - ASSERT_BSONOBJ_EQ(optimizedMatchExpression->serialize(), fromjson("{a: {$eq: 1}}")); + BSONObjBuilder bob; + optimizedMatchExpression->serialize(&bob, true); + ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{a: {$eq: 1}}")); } TEST(ExpressionOptimizeTest, AndWithSingleChildAlwaysTrueOptimizesToEmptyAnd) { @@ -367,7 +370,9 @@ TEST(ExpressionOptimizeTest, AndWithSingleChildAlwaysTrueOptimizesToEmptyAnd) { auto optimizedMatchExpression = MatchExpression::optimize(std::move(matchExpression)); // TODO SERVER-34759 We want this to optimize to an AlwaysTrueMatchExpression. ASSERT_TRUE(dynamic_cast<AndMatchExpression*>(optimizedMatchExpression.get())); - ASSERT_BSONOBJ_EQ(optimizedMatchExpression->serialize(), fromjson("{}")); + BSONObjBuilder bob; + optimizedMatchExpression->serialize(&bob, true); + ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{}")); } TEST(ExpressionOptimizeTest, AndWithEachChildAlwaysTrueOptimizesToEmptyAnd) { @@ -376,28 +381,36 @@ TEST(ExpressionOptimizeTest, AndWithEachChildAlwaysTrueOptimizesToEmptyAnd) { auto optimizedMatchExpression = MatchExpression::optimize(std::move(matchExpression)); // TODO SERVER-34759 We want this to optimize to an AlwaysTrueMatchExpression. ASSERT_TRUE(dynamic_cast<AndMatchExpression*>(optimizedMatchExpression.get())); - ASSERT_BSONOBJ_EQ(optimizedMatchExpression->serialize(), fromjson("{}")); + BSONObjBuilder bob; + optimizedMatchExpression->serialize(&bob, true); + ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{}")); } TEST(ExpressionOptimizeTest, NestedAndWithAlwaysFalseOptimizesToAlwaysFalse) { BSONObj obj = fromjson("{$and: [{$and: [{$alwaysFalse: 1}, {a: 1}]}, {b: 1}]}"); std::unique_ptr<MatchExpression> matchExpression(parseMatchExpression(obj)); auto optimizedMatchExpression = MatchExpression::optimize(std::move(matchExpression)); - ASSERT_BSONOBJ_EQ(optimizedMatchExpression->serialize(), fromjson("{$alwaysFalse: 1}")); + BSONObjBuilder bob; + optimizedMatchExpression->serialize(&bob, true); + ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{$alwaysFalse: 1}")); } TEST(ExpressionOptimizeTest, OrWithAlwaysTrueOptimizesToAlwaysTrue) { BSONObj obj = fromjson("{$or: [{a: 1}, {$alwaysTrue: 1}]}"); std::unique_ptr<MatchExpression> matchExpression(parseMatchExpression(obj)); auto optimizedMatchExpression = MatchExpression::optimize(std::move(matchExpression)); - ASSERT_BSONOBJ_EQ(optimizedMatchExpression->serialize(), fromjson("{$alwaysTrue: 1}")); + BSONObjBuilder bob; + optimizedMatchExpression->serialize(&bob, true); + ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{$alwaysTrue: 1}")); } TEST(ExpressionOptimizeTest, OrRemovesAlwaysFalseChildren) { BSONObj obj = fromjson("{$or: [{a: 1}, {$alwaysFalse: 1}]}"); std::unique_ptr<MatchExpression> matchExpression(parseMatchExpression(obj)); auto optimizedMatchExpression = MatchExpression::optimize(std::move(matchExpression)); - ASSERT_BSONOBJ_EQ(optimizedMatchExpression->serialize(), fromjson("{a: {$eq: 1}}")); + BSONObjBuilder bob; + optimizedMatchExpression->serialize(&bob, true); + ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{a: {$eq: 1}}")); } TEST(ExpressionOptimizeTest, OrPromotesSingleAlwaysFalseAfterOptimize) { @@ -406,7 +419,9 @@ TEST(ExpressionOptimizeTest, OrPromotesSingleAlwaysFalseAfterOptimize) { std::unique_ptr<MatchExpression> matchExpression(parseMatchExpression(obj)); auto optimizedMatchExpression = MatchExpression::optimize(std::move(matchExpression)); ASSERT_TRUE(dynamic_cast<AlwaysFalseMatchExpression*>(optimizedMatchExpression.get())); - ASSERT_BSONOBJ_EQ(optimizedMatchExpression->serialize(), fromjson("{$alwaysFalse: 1}")); + BSONObjBuilder bob; + optimizedMatchExpression->serialize(&bob, true); + ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{$alwaysFalse: 1}")); } TEST(ExpressionOptimizeTest, OrPromotesSingleAlwaysFalse) { @@ -414,7 +429,9 @@ TEST(ExpressionOptimizeTest, OrPromotesSingleAlwaysFalse) { std::unique_ptr<MatchExpression> matchExpression(parseMatchExpression(obj)); auto optimizedMatchExpression = MatchExpression::optimize(std::move(matchExpression)); ASSERT_TRUE(dynamic_cast<AlwaysFalseMatchExpression*>(optimizedMatchExpression.get())); - ASSERT_BSONOBJ_EQ(optimizedMatchExpression->serialize(), fromjson("{$alwaysFalse: 1}")); + BSONObjBuilder bob; + optimizedMatchExpression->serialize(&bob, true); + ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{$alwaysFalse: 1}")); } TEST(ExpressionOptimizeTest, OrPromotesMultipleAlwaysFalse) { @@ -422,14 +439,18 @@ TEST(ExpressionOptimizeTest, OrPromotesMultipleAlwaysFalse) { std::unique_ptr<MatchExpression> matchExpression(parseMatchExpression(obj)); auto optimizedMatchExpression = MatchExpression::optimize(std::move(matchExpression)); ASSERT_TRUE(dynamic_cast<AlwaysFalseMatchExpression*>(optimizedMatchExpression.get())); - ASSERT_BSONOBJ_EQ(optimizedMatchExpression->serialize(), fromjson("{$alwaysFalse: 1}")); + BSONObjBuilder bob; + optimizedMatchExpression->serialize(&bob, true); + ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{$alwaysFalse: 1}")); } TEST(ExpressionOptimizeTest, NestedOrWithAlwaysTrueOptimizesToAlwaysTrue) { BSONObj obj = fromjson("{$or: [{$or: [{$alwaysTrue: 1}, {a: 1}]}, {b: 1}]}"); std::unique_ptr<MatchExpression> matchExpression(parseMatchExpression(obj)); auto optimizedMatchExpression = MatchExpression::optimize(std::move(matchExpression)); - ASSERT_BSONOBJ_EQ(optimizedMatchExpression->serialize(), fromjson("{$alwaysTrue: 1}")); + BSONObjBuilder bob; + optimizedMatchExpression->serialize(&bob, true); + ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{$alwaysTrue: 1}")); } TEST(ExpressionOptimizeTest, OrRewrittenToIn) { @@ -456,7 +477,9 @@ TEST(ExpressionOptimizeTest, OrRewrittenToIn) { auto obj = fromjson(exprStr); std::unique_ptr<MatchExpression> matchExpression(parseMatchExpression(obj)); auto optimizedMatchExpression = MatchExpression::optimize(std::move(matchExpression)); - return optimizedMatchExpression->serialize(); + BSONObjBuilder bob; + optimizedMatchExpression->serialize(&bob, true); + return bob.obj(); }; ASSERT_BSONOBJ_EQ(optimizeExpr(queries[0].first), fromjson(queries[0].second)); @@ -472,33 +495,5 @@ TEST(ExpressionOptimizeTest, OrRewrittenToIn) { ASSERT_BSONOBJ_EQ(optimizeExpr(queries[10].first), fromjson(queries[10].second)); } -TEST(ExpressionOptimizeTest, PartialOrToInRewriteDoesNotGenerateDirectlyNestedOr) { - BSONObj obj = fromjson("{$or: [{x: {$eq: 3}}, {x: {$eq: 4}}, {y: 5}, {z: 6}]}"); - auto optimizedMatchExpression = MatchExpression::optimize(parseMatchExpression(obj)); - ASSERT_BSONOBJ_EQ(optimizedMatchExpression->serialize(), - fromjson("{$or: [{x: {$in: [3, 4]}}, {y: {$eq: 5}}, {z: {$eq: 6}}]}")); -} - -TEST(ExpressionOptimizeTest, NorRemovesAlwaysFalseChildren) { - BSONObj obj = fromjson("{$nor: [{a: 1}, {$alwaysFalse: 1}]}"); - std::unique_ptr<MatchExpression> matchExpression(parseMatchExpression(obj)); - auto optimizedMatchExpression = MatchExpression::optimize(std::move(matchExpression)); - ASSERT_BSONOBJ_EQ(optimizedMatchExpression->serialize(), fromjson("{a: {$not: {$eq: 1}}}")); -} - -TEST(ExpressionOptimizeTest, NorWithoutChildrenOptimizesToEmptyAnd) { - BSONObj obj = fromjson("{$nor: [{$alwaysFalse: 1}, {$alwaysFalse: 1}]}"); - std::unique_ptr<MatchExpression> matchExpression(parseMatchExpression(obj)); - auto optimizedMatchExpression = MatchExpression::optimize(std::move(matchExpression)); - ASSERT_TRUE(dynamic_cast<AndMatchExpression*>(optimizedMatchExpression.get())); - ASSERT_BSONOBJ_EQ(optimizedMatchExpression->serialize(), fromjson("{}")); -} - -TEST(ExpressionOptimizeTest, NorWithAlwaysTrueChildOptimizesToAlwaysFalse) { - BSONObj obj = fromjson("{$nor: [{a: 1}, {$alwaysTrue: 1}]}"); - std::unique_ptr<MatchExpression> matchExpression(parseMatchExpression(obj)); - auto optimizedMatchExpression = MatchExpression::optimize(std::move(matchExpression)); - ASSERT_BSONOBJ_EQ(optimizedMatchExpression->serialize(), fromjson("{$alwaysFalse: 1}")); -} } // namespace } // namespace mongo diff --git a/src/mongo/db/matcher/expression_parser.cpp b/src/mongo/db/matcher/expression_parser.cpp index e6529908910..0e86cd4e06b 100644 --- a/src/mongo/db/matcher/expression_parser.cpp +++ b/src/mongo/db/matcher/expression_parser.cpp @@ -2205,15 +2205,15 @@ MONGO_INITIALIZER_WITH_PREREQUISITES(MatchExpressionCounters, if (name[0] == '_' || exceptionsSet.count(name) > 0) { continue; } - operatorCountersMatchExpressions.addCounter("$" + name); + operatorCountersMatchExpressions.addMatchExprCounter("$" + name); } for (auto&& [name, fn] : *pathlessOperatorMap) { if (name[0] == '_' || exceptionsSet.count(name) > 0) { continue; } - operatorCountersMatchExpressions.addCounter("$" + name); + operatorCountersMatchExpressions.addMatchExprCounter("$" + name); } - operatorCountersMatchExpressions.addCounter("$not"); + operatorCountersMatchExpressions.addMatchExprCounter("$not"); } diff --git a/src/mongo/db/matcher/expression_path.h b/src/mongo/db/matcher/expression_path.h index f167177bdb4..16182705a62 100644 --- a/src/mongo/db/matcher/expression_path.h +++ b/src/mongo/db/matcher/expression_path.h @@ -122,39 +122,21 @@ public: } } - void serialize(BSONObjBuilder* out, - const SerializationOptions& opts = {}, - bool includePath = true) const override { + void serialize(BSONObjBuilder* out, bool includePath) const override { if (includePath) { - BSONObjBuilder subObj(out->subobjStart(opts.serializeFieldPathFromString(path()))); - appendSerializedRightHandSide(&subObj, opts, includePath); - subObj.doneFast(); + out->append(path(), getSerializedRightHandSide()); } else { - appendSerializedRightHandSide(out, opts, includePath); + out->appendElements(getSerializedRightHandSide()); } } /** - * Constructs a BSONObj that represents the right-hand-side of a PathMatchExpression. Used for + * Returns a BSONObj that represents the right-hand-side of a PathMatchExpression. Used for * serialization of PathMatchExpression in cases where we do not want to serialize the path in * line with the expression. For example {x: {$not: {$eq: 1}}}, where $eq is the * PathMatchExpression. - * - * Serialization options should be respected for any descendent expressions. Eg, if the - * 'literalPolicy' option is 'kToDebugTypeString', then any literal argument (like the number 1 - * in the example above), should be "shapified" (e.g. "?number"). 'literal' here is in contrast - * to another expression, if that is possible syntactically. */ - virtual void appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts = {}, - bool includePath = true) const = 0; - - BSONObj getSerializedRightHandSide(const SerializationOptions& opts = {}, - bool includePath = true) const { - BSONObjBuilder bob; - appendSerializedRightHandSide(&bob, opts, includePath); - return bob.obj(); - } + virtual BSONObj getSerializedRightHandSide() const = 0; protected: void _doAddDependencies(DepsTracker* deps) const final { diff --git a/src/mongo/db/matcher/expression_serialization_test.cpp b/src/mongo/db/matcher/expression_serialization_test.cpp index 3b25631cd0f..78517a52634 100644 --- a/src/mongo/db/matcher/expression_serialization_test.cpp +++ b/src/mongo/db/matcher/expression_serialization_test.cpp @@ -37,16 +37,7 @@ #include "mongo/db/matcher/expression_parser.h" #include "mongo/db/matcher/extensions_callback_noop.h" #include "mongo/db/matcher/matcher.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_eq.h" -#include "mongo/db/matcher/schema/expression_internal_schema_fmod.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/pipeline/expression_context_for_test.h" #include "mongo/unittest/unittest.h" @@ -58,7 +49,9 @@ using std::string; using std::unique_ptr; BSONObj serialize(MatchExpression* match) { - return match->serialize(); + BSONObjBuilder bob; + match->serialize(&bob, true); + return bob.obj(); } TEST(SerializeBasic, AndExpressionWithOneChildSerializesCorrectly) { @@ -362,6 +355,7 @@ TEST(SerializeBasic, ExpressionElemMatchValueWithTripleNotSerializesCorrectly) { ASSERT_EQ(original.matches(obj), reserialized.matches(obj)); } + TEST(SerializeBasic, ExpressionSizeSerializesCorrectly) { boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); Matcher original(fromjson("{x: {$size: 2}}"), @@ -636,32 +630,6 @@ TEST(SerializeBasic, ExpressionRegexWithValueAndOptionsSerializesCorrectly) { ASSERT_EQ(original.matches(obj), reserialized.matches(obj)); } -TEST(SerializeBasic, ExpressionRegexWithoutOptionsSerializesShapeCorrectly) { - auto query = fromjson(R"({x: {$regex: ".*"}})"); - boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); - auto objMatch = MatchExpressionParser::parse(query, expCtx); - ASSERT_OK(objMatch.getStatus()); - SerializationOptions opts; - opts.literalPolicy = LiteralSerializationPolicy::kToRepresentativeParseableValue; - ASSERT_BSONOBJ_EQ(BSON("x" << BSON("$regex" - << "\\?")), - objMatch.getValue()->serialize(opts)); -} - -TEST(SerializeBasic, ExpressionRegexWithOptionsSerializesShapeCorrectly) { - auto query = fromjson(R"({x: {$regex: ".*", $options: "m"}})"); - boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); - auto objMatch = MatchExpressionParser::parse(query, expCtx); - ASSERT_OK(objMatch.getStatus()); - SerializationOptions opts; - opts.literalPolicy = LiteralSerializationPolicy::kToRepresentativeParseableValue; - ASSERT_BSONOBJ_EQ(BSON("x" << BSON("$regex" - << "\\?" - << "$options" - << "i")), - objMatch.getValue()->serialize(opts)); -} - TEST(SerializeBasic, ExpressionRegexWithValueSerializesCorrectly) { boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); Matcher original(fromjson("{x: /a.b/}"), @@ -1125,7 +1093,7 @@ TEST(SerializeBasic, ExpressionNotWithDirectPathExpSerializesCorrectly) { // direct path expression child, instead creating a NOT -> AND -> path expression. This test // manually constructs such an expression in case it ever turns up, since that should still be // able to serialize. - auto originalBSON = fromjson("{a: {$not: {$eq: 2}}}"); + auto originalBSON = fromjson("{a: {$not: {$eq: 2}}}}"); auto equalityRHSElem = originalBSON["a"]["$not"]["$eq"]; auto equalityExpression = std::make_unique<EqualityMatchExpression>("a"_sd, equalityRHSElem); @@ -1711,7 +1679,7 @@ TEST(SerializeInternalSchema, ExpressionInternalSchemaMaxLengthSerializesCorrect TEST(SerializeInternalSchema, ExpressionInternalSchemaCondSerializesCorrectly) { boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); - Matcher original(fromjson("{$_internalSchemaCond: [{a: 1}, {b: 2}, {c: 3}]}"), + Matcher original(fromjson("{$_internalSchemaCond: [{a: 1}, {b: 2}, {c: 3}]}}"), expCtx, ExtensionsCallbackNoop(), MatchExpressionParser::kAllowAllSpecialFeatures); @@ -1722,7 +1690,7 @@ TEST(SerializeInternalSchema, ExpressionInternalSchemaCondSerializesCorrectly) { BSONObjBuilder builder; ASSERT_BSONOBJ_EQ( *reserialized.getQuery(), - fromjson("{$_internalSchemaCond: [{a: {$eq: 1}}, {b: {$eq: 2}}, {c: {$eq: 3}}]}")); + fromjson("{$_internalSchemaCond: [{a: {$eq: 1}}, {b: {$eq: 2}}, {c: {$eq: 3}}]}}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); } @@ -1880,311 +1848,5 @@ TEST(SerializeInternalBinDataSubType, ExpressionBinDataSubTypeSerializesCorrectl ASSERT_TRUE(original.matches(obj)); } -TEST(SerializeInternalSchema, AllowedPropertiesRedactsCorrectly) { - - auto query = fromjson( - "{$_internalSchemaAllowedProperties: {properties: ['a', 'b']," - "namePlaceholder: 'i', patternProperties: [], otherwise: {i: 0}}}"); - boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); - auto objMatch = MatchExpressionParser::parse(query, expCtx); - ASSERT_OK(objMatch.getStatus()); - - SerializationOptions opts = SerializationOptions::kDebugShapeAndMarkIdentifiers_FOR_TEST; - - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({ - "$_internalSchemaAllowedProperties": { - "properties": "?array<?string>", - "namePlaceholder": "i", - "patternProperties": [], - "otherwise": { - "HASH<i>": { - "$eq": "?number" - } - } - } - })", - objMatch.getValue()->serialize(opts)); -} - -/** - * Helper function for parsing and creating MatchExpressions. - */ -std::unique_ptr<InternalSchemaCondMatchExpression> createCondMatchExpression(BSONObj condition, - BSONObj thenBranch, - BSONObj elseBranch) { - boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); - auto conditionExpr = MatchExpressionParser::parse(condition, expCtx); - ASSERT_OK(conditionExpr.getStatus()); - auto thenBranchExpr = MatchExpressionParser::parse(thenBranch, expCtx); - ASSERT_OK(thenBranchExpr.getStatus()); - auto elseBranchExpr = MatchExpressionParser::parse(elseBranch, expCtx); - - std::array<std::unique_ptr<MatchExpression>, 3> expressions = { - {std::move(conditionExpr.getValue()), - std::move(thenBranchExpr.getValue()), - std::move(elseBranchExpr.getValue())}}; - - auto cond = std::make_unique<InternalSchemaCondMatchExpression>(std::move(expressions)); - - return cond; -} - -TEST(SerializeInternalSchema, CondMatchRedactsCorrectly) { - auto opts = SerializationOptions::kDebugShapeAndMarkIdentifiers_FOR_TEST; - auto conditionQuery = BSON("age" << BSON("$lt" << 18)); - auto thenQuery = BSON("job" - << "student"); - auto elseQuery = BSON("job" - << "engineer"); - auto cond = createCondMatchExpression(conditionQuery, thenQuery, elseQuery); - BSONObjBuilder bob; - cond->serialize(&bob, opts); - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({ - "$_internalSchemaCond": [ - { - "HASH<age>": { - "$lt": "?number" - } - }, - { - "HASH<job>": { - "$eq": "?string" - } - }, - { - "HASH<job>": { - "$eq": "?string" - } - } - ] - })", - bob.done()); -} - -TEST(SerializeInternalSchema, FmodMatchRedactsCorrectly) { - InternalSchemaFmodMatchExpression m("a"_sd, Decimal128(1.7), Decimal128(2)); - auto opts = SerializationOptions{LiteralSerializationPolicy::kToDebugTypeString}; - BSONObjBuilder bob; - m.serialize(&bob, opts); - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({"a":{"$_internalSchemaFmod":["?number","?number"]}})", - bob.done()); -} - -TEST(SerializeInternalSchema, MatchArrayIndexRedactsCorrectly) { - auto query = fromjson( - "{foo: {$_internalSchemaMatchArrayIndex:" - "{index: 0, namePlaceholder: 'i', expression: {i: {$type: 'number'}}}}}"); - boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); - auto objMatch = MatchExpressionParser::parse(query, expCtx); - ASSERT_OK(objMatch.getStatus()); - - BSONObjBuilder bob; - auto opts = SerializationOptions::kDebugShapeAndMarkIdentifiers_FOR_TEST; - objMatch.getValue()->serialize(&bob, opts); - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({ - "HASH<foo>": { - "$_internalSchemaMatchArrayIndex": { - "index": "?number", - "namePlaceholder": "HASH<i>", - "expression": { - "HASH<i>": { - "$type": ['number'] - } - } - } - } - })", - bob.done()); -} - -TEST(SerializeInternalSchema, MaxItemsRedactsCorrectly) { - InternalSchemaMaxItemsMatchExpression maxItems("a.b"_sd, 2); - auto opts = SerializationOptions::kDebugShapeAndMarkIdentifiers_FOR_TEST; - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({"$_internalSchemaMaxItems":"?number"})", - maxItems.getSerializedRightHandSide(opts)); -} - -TEST(SerializeInternalSchema, MaxLengthRedactsCorrectly) { - InternalSchemaMaxLengthMatchExpression maxLength("a"_sd, 2); - auto opts = SerializationOptions::kDebugShapeAndMarkIdentifiers_FOR_TEST; - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({"$_internalSchemaMaxLength":"?number"})", - maxLength.getSerializedRightHandSide(opts)); -} - -TEST(SerializeInternalSchema, MinItemsRedactsCorrectly) { - InternalSchemaMinItemsMatchExpression minItems("a.b"_sd, 2); - auto opts = SerializationOptions::kDebugShapeAndMarkIdentifiers_FOR_TEST; - - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({"$_internalSchemaMinItems":"?number"})", - minItems.getSerializedRightHandSide(opts)); -} - -TEST(SerializeInternalSchema, MinLengthRedactsCorrectly) { - InternalSchemaMinLengthMatchExpression minLength("a"_sd, 2); - auto opts = SerializationOptions{LiteralSerializationPolicy::kToDebugTypeString}; - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({"$_internalSchemaMinLength":"?number"})", - minLength.getSerializedRightHandSide(opts)); -} - -TEST(SerializeInternalSchema, MinPropertiesRedactsCorrectly) { - InternalSchemaMinPropertiesMatchExpression minProperties(5); - auto opts = SerializationOptions{LiteralSerializationPolicy::kToDebugTypeString}; - - BSONObjBuilder bob; - minProperties.serialize(&bob, opts); - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({"$_internalSchemaMinProperties":"?number"})", - bob.done()); -} - -TEST(SerializeInternalSchema, ObjectMatchRedactsCorrectly) { - auto opts = SerializationOptions::kDebugShapeAndMarkIdentifiers_FOR_TEST; - auto query = fromjson( - " {a: {$_internalSchemaObjectMatch: {" - " c: {$eq: 3}" - " }}}"); - boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); - auto objMatch = MatchExpressionParser::parse(query, expCtx); - ASSERT_OK(objMatch.getStatus()); - - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({"HASH<a>":{"$_internalSchemaObjectMatch":{"HASH<c>":{"$eq":"?number"}}}})", - objMatch.getValue()->serialize(opts)); -} - -TEST(SerializeInternalSchema, RootDocEqRedactsCorrectly) { - auto query = fromjson("{$_internalSchemaRootDocEq: {a:1, b: {c: 1, d: [1]}}}"); - boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); - auto opts = SerializationOptions::kDebugShapeAndMarkIdentifiers_FOR_TEST; - auto objMatch = MatchExpressionParser::parse(query, expCtx); - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({ - "$_internalSchemaRootDocEq": { - "HASH<a>": "?number", - "HASH<b>": { - "HASH<c>": "?number", - "HASH<d>": [ - "?number" - ] - } - } - })", - objMatch.getValue()->serialize(opts)); -} - -TEST(SerializeInternalSchema, BinDataEncryptedTypeRedactsCorrectly) { - MatcherTypeSet typeSet; - typeSet.bsonTypes.insert(BSONType::String); - typeSet.bsonTypes.insert(BSONType::Date); - InternalSchemaBinDataEncryptedTypeExpression e("a"_sd, std::move(typeSet)); - auto opts = SerializationOptions{LiteralSerializationPolicy::kToDebugTypeString}; - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({"$_internalSchemaBinDataEncryptedType":[2,9]})", - e.getSerializedRightHandSide(opts)); -} - -TEST(SerializeInternalSchema, BinDataFLE2EncryptedTypeRedactsCorrectly) { - InternalSchemaBinDataFLE2EncryptedTypeExpression e("ssn"_sd, BSONType::String); - auto opts = SerializationOptions{LiteralSerializationPolicy::kToDebugTypeString}; - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({"$_internalSchemaBinDataFLE2EncryptedType":[2]})", - e.getSerializedRightHandSide(opts)); -} - -TEST(SerializesInternalSchema, MaxPropertiesRedactsCorrectly) { - InternalSchemaMaxPropertiesMatchExpression maxProperties(5); - auto opts = SerializationOptions{LiteralSerializationPolicy::kToDebugTypeString}; - - BSONObjBuilder bob; - maxProperties.serialize(&bob, opts); - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({"$_internalSchemaMaxProperties":"?number"})", - bob.done()); -} - -TEST(SerializesInternalSchema, EqRedactsCorrectly) { - auto opts = SerializationOptions::kDebugShapeAndMarkIdentifiers_FOR_TEST; - auto query = fromjson("{$_internalSchemaEq: {a:1, b: {c: 1, d: [1]}}}"); - BSONObjBuilder bob; - InternalSchemaEqMatchExpression e("a"_sd, query.firstElement()); - e.serialize(&bob, opts); - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({ - "HASH<a>": { - "$_internalSchemaEq": { - "HASH<a>": "?number", - "HASH<b>": { - "HASH<c>": "?number", - "HASH<d>": [ - "?number" - ] - } - } - } - })", - bob.done()); -} - -TEST(InternalSchemaAllElemMatchFromIndexMatchExpression, RedactsExpressionCorrectly) { - auto query = fromjson("{a: {$_internalSchemaAllElemMatchFromIndex: [2, {a: {$lt: 5}}]}}"); - boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); - auto expr = MatchExpressionParser::parse(query, expCtx); - ASSERT_OK(expr.getStatus()); - auto elemMatchExpr = dynamic_cast<const InternalSchemaAllElemMatchFromIndexMatchExpression*>( - expr.getValue().get()); - - auto opts = SerializationOptions::kDebugShapeAndMarkIdentifiers_FOR_TEST; - - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({ - "$_internalSchemaAllElemMatchFromIndex": [ - "?number", - { - "HASH<a>": { - "$lt": "?number" - } - } - ] - })", - elemMatchExpr->getSerializedRightHandSide(opts)); -} - -TEST(SerializeBasic, SerializesNestedElemMatchCorrectly) { - auto query = fromjson(R"({a: {$elemMatch: {$elemMatch: {b: {$lt: 6, $gt: 4}}}}})"); - boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); - auto objMatch = MatchExpressionParser::parse(query, expCtx); - ASSERT_OK(objMatch.getStatus()); - SerializationOptions opts; - opts.literalPolicy = LiteralSerializationPolicy::kToDebugTypeString; - ASSERT_BSONOBJ_EQ_AUTO( - R"({"a": { - "$elemMatch": { - "$elemMatch": { - "$and": [ - { - "b": { - "$lt": "?number" - } - }, - { - "b": { - "$gt": "?number" - } - } - ] - } - } - } - })", - objMatch.getValue()->serialize(opts)); -} } // namespace } // namespace mongo diff --git a/src/mongo/db/matcher/expression_text_base.cpp b/src/mongo/db/matcher/expression_text_base.cpp index ce485de6fbc..56ad05af3d3 100644 --- a/src/mongo/db/matcher/expression_text_base.cpp +++ b/src/mongo/db/matcher/expression_text_base.cpp @@ -56,16 +56,12 @@ void TextMatchExpressionBase::debugString(StringBuilder& debug, int indentationL debug << "\n"; } -void TextMatchExpressionBase::serialize(BSONObjBuilder* out, - const SerializationOptions& opts, - bool includePath) const { +void TextMatchExpressionBase::serialize(BSONObjBuilder* out, bool includePath) const { const fts::FTSQuery& ftsQuery = getFTSQuery(); out->append("$text", - BSON("$search" << opts.serializeLiteral(ftsQuery.getQuery()) << "$language" - << opts.serializeLiteral(ftsQuery.getLanguage()) << "$caseSensitive" - << opts.serializeLiteral(ftsQuery.getCaseSensitive()) - << "$diacriticSensitive" - << opts.serializeLiteral(ftsQuery.getDiacriticSensitive()))); + BSON("$search" << ftsQuery.getQuery() << "$language" << ftsQuery.getLanguage() + << "$caseSensitive" << ftsQuery.getCaseSensitive() + << "$diacriticSensitive" << ftsQuery.getDiacriticSensitive())); } bool TextMatchExpressionBase::equivalent(const MatchExpression* other) const { diff --git a/src/mongo/db/matcher/expression_text_base.h b/src/mongo/db/matcher/expression_text_base.h index 622438e1500..22fc581a2e2 100644 --- a/src/mongo/db/matcher/expression_text_base.h +++ b/src/mongo/db/matcher/expression_text_base.h @@ -60,9 +60,7 @@ public: */ virtual const fts::FTSQuery& getFTSQuery() const = 0; - void appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts = {}, - bool includePath = true) const final { + BSONObj getSerializedRightHandSide() const final { MONGO_UNREACHABLE; } @@ -72,9 +70,7 @@ public: void debugString(StringBuilder& debug, int indentationLevel = 0) const final; - void serialize(BSONObjBuilder* out, - const SerializationOptions& opts = {}, - bool includePath = true) const final; + void serialize(BSONObjBuilder* out, bool includePath) const final; bool equivalent(const MatchExpression* other) const final; diff --git a/src/mongo/db/matcher/expression_tree.cpp b/src/mongo/db/matcher/expression_tree.cpp index 74765949ca5..df70ad278c3 100644 --- a/src/mongo/db/matcher/expression_tree.cpp +++ b/src/mongo/db/matcher/expression_tree.cpp @@ -40,106 +40,16 @@ #include "mongo/db/matcher/expression_text_base.h" namespace mongo { -namespace { - -PathMatchExpression* getEligiblePathMatchForNotSerialization(MatchExpression* expr) { - // Returns a pointer to a PathMatchExpression if 'expr' is such a pointer, otherwise returns - // nullptr. - // - // One exception: while TextMatchExpressionBase derives from PathMatchExpression, text match - // expressions cannot be serialized in the same manner as other PathMatchExpression derivatives. - // This is because the path for a TextMatchExpression is embedded within the $text object, - // whereas for other PathMatchExpressions it is on the left-hand-side, for example {x: {$eq: - // 1}}. - // - // Rather than the following dynamic_cast, we'll do a more performant, but also more verbose - // check. - // dynamic_cast<PathMatchExpression*>(expr) && !dynamic_cast<TextMatchExpressionBase*>(expr) - // - // This version below is less obviously exhaustive, but because this is just a legibility - // optimization, and this function also gets called on the query shape stats recording hot path, - // we think it is worth it. - switch (expr->matchType()) { - // leaf types - case MatchExpression::EQ: - case MatchExpression::LTE: - case MatchExpression::LT: - case MatchExpression::GT: - case MatchExpression::GTE: - case MatchExpression::REGEX: - case MatchExpression::MOD: - case MatchExpression::EXISTS: - case MatchExpression::MATCH_IN: - case MatchExpression::BITS_ALL_SET: - case MatchExpression::BITS_ALL_CLEAR: - case MatchExpression::BITS_ANY_SET: - case MatchExpression::BITS_ANY_CLEAR: - // array types - case MatchExpression::ELEM_MATCH_OBJECT: - case MatchExpression::ELEM_MATCH_VALUE: - case MatchExpression::SIZE: - // special types - case MatchExpression::TYPE_OPERATOR: - case MatchExpression::GEO: - case MatchExpression::GEO_NEAR: - // Internal subclasses of PathMatchExpression: - case MatchExpression::INTERNAL_SCHEMA_ALL_ELEM_MATCH_FROM_INDEX: - case MatchExpression::INTERNAL_SCHEMA_BIN_DATA_ENCRYPTED_TYPE: - case MatchExpression::INTERNAL_SCHEMA_BIN_DATA_FLE2_ENCRYPTED_TYPE: - case MatchExpression::INTERNAL_SCHEMA_BIN_DATA_SUBTYPE: - case MatchExpression::INTERNAL_SCHEMA_MATCH_ARRAY_INDEX: - case MatchExpression::INTERNAL_SCHEMA_MAX_ITEMS: - case MatchExpression::INTERNAL_SCHEMA_MAX_LENGTH: - case MatchExpression::INTERNAL_SCHEMA_MAX_PROPERTIES: - case MatchExpression::INTERNAL_SCHEMA_MIN_ITEMS: - case MatchExpression::INTERNAL_SCHEMA_MIN_LENGTH: - case MatchExpression::INTERNAL_SCHEMA_TYPE: - case MatchExpression::INTERNAL_SCHEMA_UNIQUE_ITEMS: - return static_cast<PathMatchExpression*>(expr); - // purposefully skip TEXT: - case MatchExpression::TEXT: - // Any other type is not considered a PathMatchExpression. - case MatchExpression::AND: - case MatchExpression::OR: - case MatchExpression::NOT: - case MatchExpression::NOR: - case MatchExpression::WHERE: - case MatchExpression::EXPRESSION: - case MatchExpression::ALWAYS_FALSE: - case MatchExpression::ALWAYS_TRUE: - case MatchExpression::INTERNAL_2D_POINT_IN_ANNULUS: - case MatchExpression::INTERNAL_BUCKET_GEO_WITHIN: - case MatchExpression::INTERNAL_EXPR_EQ: - case MatchExpression::INTERNAL_EXPR_GT: - case MatchExpression::INTERNAL_EXPR_GTE: - case MatchExpression::INTERNAL_EXPR_LT: - case MatchExpression::INTERNAL_EXPR_LTE: - case MatchExpression::INTERNAL_SCHEMA_ALLOWED_PROPERTIES: - case MatchExpression::INTERNAL_SCHEMA_COND: - case MatchExpression::INTERNAL_SCHEMA_EQ: - case MatchExpression::INTERNAL_SCHEMA_FMOD: - case MatchExpression::INTERNAL_SCHEMA_MIN_PROPERTIES: - case MatchExpression::INTERNAL_SCHEMA_OBJECT_MATCH: - case MatchExpression::INTERNAL_SCHEMA_ROOT_DOC_EQ: - case MatchExpression::INTERNAL_SCHEMA_XOR: - return nullptr; - default: - MONGO_UNREACHABLE_TASSERT(7800300); - } -}; -} // namespace void ListOfMatchExpression::_debugList(StringBuilder& debug, int indentationLevel) const { for (unsigned i = 0; i < _expressions.size(); i++) _expressions[i]->debugString(debug, indentationLevel + 1); } -void ListOfMatchExpression::_listToBSON(BSONArrayBuilder* out, - const SerializationOptions& opts, - bool includePath) const { +void ListOfMatchExpression::_listToBSON(BSONArrayBuilder* out, bool includePath) const { for (unsigned i = 0; i < _expressions.size(); i++) { BSONObjBuilder childBob(out->subobjStart()); - _expressions[i]->serialize(&childBob, opts, includePath); + _expressions[i]->serialize(&childBob, includePath); } out->doneFast(); } @@ -187,13 +97,12 @@ MatchExpression::ExpressionOptimizerFunc ListOfMatchExpression::getOptimizer() c std::back_inserter(children)); } - // Remove all children of AND that are $alwaysTrue and all children of OR and NOR that are + // Remove all children of AND that are $alwaysTrue and all children of OR that are // $alwaysFalse. - if (matchType == AND || matchType == OR || matchType == NOR) { + if (matchType == AND || matchType == OR) { for (auto& childExpression : children) if ((childExpression->isTriviallyTrue() && matchType == MatchExpression::AND) || - (childExpression->isTriviallyFalse() && matchType == MatchExpression::OR) || - (childExpression->isTriviallyFalse() && matchType == MatchExpression::NOR)) + (childExpression->isTriviallyFalse() && matchType == MatchExpression::OR)) childExpression = nullptr; // We replaced each destroyed child expression with nullptr. Now we remove those @@ -203,17 +112,13 @@ MatchExpression::ExpressionOptimizerFunc ListOfMatchExpression::getOptimizer() c // Check if the above optimizations eliminated all children. An OR with no children is // always false. + // TODO SERVER-34759 It is correct to replace this empty AND with an $alwaysTrue, but we + // need to make enhancements to the planner to make it understand an $alwaysTrue and an + // empty AND as the same thing. The planner can create inferior plans for $alwaysTrue which + // it would not produce for an AND with no children. if (children.empty() && matchType == MatchExpression::OR) { return std::make_unique<AlwaysFalseMatchExpression>(); } - // An AND with no children is always true and we need to return an - // EmptyExpression. This ensures that the empty $and[] will be returned that serializes to - // {} (SERVER-34759). A NOR with no children is always true. We treat an empty $nor[] - // similarly. - if (children.empty() && - (matchType == MatchExpression::AND || matchType == MatchExpression::NOR)) { - return std::make_unique<AndMatchExpression>(); - } if (children.size() == 1) { if ((matchType == AND || matchType == OR || matchType == INTERNAL_SCHEMA_XOR)) { @@ -231,8 +136,7 @@ MatchExpression::ExpressionOptimizerFunc ListOfMatchExpression::getOptimizer() c } } - if (matchType == MatchExpression::AND || matchType == MatchExpression::OR || - matchType == MatchExpression::NOR) { + if (matchType == MatchExpression::AND || matchType == MatchExpression::OR) { for (auto& childExpression : children) { // An AND containing an expression that always evaluates to false can be // optimized to a single $alwaysFalse expression. @@ -245,11 +149,6 @@ MatchExpression::ExpressionOptimizerFunc ListOfMatchExpression::getOptimizer() c if (childExpression->isTriviallyTrue() && matchType == MatchExpression::OR) { return std::make_unique<AlwaysTrueMatchExpression>(); } - // A NOR containing an expression that always evaluates to true can be - // optimized to a single $alwaysFalse expression. - if (childExpression->isTriviallyTrue() && matchType == MatchExpression::NOR) { - return std::make_unique<AlwaysFalseMatchExpression>(); - } } } @@ -318,19 +217,19 @@ MatchExpression::ExpressionOptimizerFunc ListOfMatchExpression::getOptimizer() c if (countEquivEqPaths > 1) { tassert(3401202, "There must be a common path", childPath); auto inExpression = std::make_unique<InMatchExpression>(StringData(*childPath)); - std::vector<std::unique_ptr<MatchExpression>> nonEquivOrChildren; - nonEquivOrChildren.reserve(countNonEquivExpr); + auto nonEquivOrExpr = + (countNonEquivExpr > 0) ? std::make_unique<OrMatchExpression>() : nullptr; BSONArrayBuilder bab; for (auto& childExpression : children) { if (*childPath != childExpression->path()) { - nonEquivOrChildren.push_back(std::move(childExpression)); + nonEquivOrExpr->add(std::move(childExpression)); } else if (childExpression->matchType() == MatchExpression::EQ) { std::unique_ptr<EqualityMatchExpression> eqExpressionPtr{ static_cast<EqualityMatchExpression*>(childExpression.release())}; if (isRegEx(eqExpressionPtr->getData()) || eqExpressionPtr->getCollator() != eqCollator) { - nonEquivOrChildren.push_back(std::move(eqExpressionPtr)); + nonEquivOrExpr->add(std::move(eqExpressionPtr)); } else { bab.append(eqExpressionPtr->getData()); } @@ -345,13 +244,13 @@ MatchExpression::ExpressionOptimizerFunc ListOfMatchExpression::getOptimizer() c "Conversion from OR to IN should always succeed", status == Status::OK()); } else { - nonEquivOrChildren.push_back(std::move(childExpression)); + nonEquivOrExpr->add(std::move(childExpression)); } } children.clear(); tassert(3401204, "Incorrect number of non-equivalent expressions", - nonEquivOrChildren.size() == countNonEquivExpr); + !nonEquivOrExpr || nonEquivOrExpr->numChildren() == countNonEquivExpr); auto backingArr = bab.arr(); std::vector<BSONElement> inEqualities; @@ -375,13 +274,11 @@ MatchExpression::ExpressionOptimizerFunc ListOfMatchExpression::getOptimizer() c if (countNonEquivExpr > 0) { auto parentOrExpr = std::make_unique<OrMatchExpression>(); parentOrExpr->add(std::move(inExpression)); - - // Move all of the non-equivalent children of the original $or so that they - // become children of the newly constructed $or node. - auto&& childVec = *parentOrExpr->getChildVector(); - std::move(std::make_move_iterator(nonEquivOrChildren.begin()), - std::make_move_iterator(nonEquivOrChildren.end()), - std::back_inserter(childVec)); + if (countNonEquivExpr == 1) { + parentOrExpr->add(nonEquivOrExpr->releaseChild(0)); + } else { + parentOrExpr->add(std::move(nonEquivOrExpr)); + } return parentOrExpr; } return inExpression; @@ -444,9 +341,7 @@ void AndMatchExpression::debugString(StringBuilder& debug, int indentationLevel) _debugList(debug, indentationLevel); } -void AndMatchExpression::serialize(BSONObjBuilder* out, - const SerializationOptions& opts, - bool includePath) const { +void AndMatchExpression::serialize(BSONObjBuilder* out, bool includePath) const { if (!numChildren()) { // It is possible for an AndMatchExpression to have no children, resulting in the serialized // expression {$and: []}, which is not a valid query object. @@ -454,7 +349,7 @@ void AndMatchExpression::serialize(BSONObjBuilder* out, } BSONArrayBuilder arrBob(out->subarrayStart("$and")); - _listToBSON(&arrBob, opts, includePath); + _listToBSON(&arrBob, includePath); arrBob.doneFast(); } @@ -490,9 +385,7 @@ void OrMatchExpression::debugString(StringBuilder& debug, int indentationLevel) _debugList(debug, indentationLevel); } -void OrMatchExpression::serialize(BSONObjBuilder* out, - const SerializationOptions& opts, - bool includePath) const { +void OrMatchExpression::serialize(BSONObjBuilder* out, bool includePath) const { if (!numChildren()) { // It is possible for an OrMatchExpression to have no children, resulting in the serialized // expression {$or: []}, which is not a valid query object. An empty $or is logically @@ -501,7 +394,7 @@ void OrMatchExpression::serialize(BSONObjBuilder* out, return; } BSONArrayBuilder arrBob(out->subarrayStart("$or")); - _listToBSON(&arrBob, opts, includePath); + _listToBSON(&arrBob, includePath); } bool OrMatchExpression::isTriviallyFalse() const { @@ -534,11 +427,9 @@ void NorMatchExpression::debugString(StringBuilder& debug, int indentationLevel) _debugList(debug, indentationLevel); } -void NorMatchExpression::serialize(BSONObjBuilder* out, - const SerializationOptions& opts, - bool includePath) const { +void NorMatchExpression::serialize(BSONObjBuilder* out, bool includePath) const { BSONArrayBuilder arrBob(out->subarrayStart("$nor")); - _listToBSON(&arrBob, opts, includePath); + _listToBSON(&arrBob, includePath); } // ------- @@ -551,10 +442,9 @@ void NotMatchExpression::debugString(StringBuilder& debug, int indentationLevel) void NotMatchExpression::serializeNotExpressionToNor(MatchExpression* exp, BSONObjBuilder* out, - const SerializationOptions& opts, bool includePath) { BSONObjBuilder childBob; - exp->serialize(&childBob, opts, includePath); + exp->serialize(&childBob, includePath); BSONObj tempObj = childBob.obj(); BSONArrayBuilder tBob(out->subarrayStart("$nor")); @@ -562,11 +452,9 @@ void NotMatchExpression::serializeNotExpressionToNor(MatchExpression* exp, tBob.doneFast(); } -void NotMatchExpression::serialize(BSONObjBuilder* out, - const SerializationOptions& opts, - bool includePath) const { +void NotMatchExpression::serialize(BSONObjBuilder* out, bool includePath) const { if (_exp->matchType() == MatchType::AND && _exp->numChildren() == 0) { - opts.appendLiteral(out, "$alwaysFalse", 1); + out->append("$alwaysFalse", 1); return; } @@ -577,10 +465,10 @@ void NotMatchExpression::serialize(BSONObjBuilder* out, // internally, so we un-nest it here to be able to re-parse it. if (_exp->matchType() == MatchType::AND) { for (size_t x = 0; x < _exp->numChildren(); ++x) { - _exp->getChild(x)->serialize(¬Bob, opts, includePath); + _exp->getChild(x)->serialize(¬Bob, includePath); } } else { - _exp->serialize(¬Bob, opts, includePath); + _exp->serialize(¬Bob, includePath); } return; } @@ -593,15 +481,20 @@ void NotMatchExpression::serialize(BSONObjBuilder* out, // It is generally easier to be correct if we just always serialize to a $nor, since this will // delegate the path serialization to lower in the tree where we have the information on-hand. // However, for legibility we preserve a $not with a single path-accepting child as a $not. - if (auto pathMatch = getEligiblePathMatchForNotSerialization(expressionToNegate)) { - auto append = [&](StringData path) { - BSONObjBuilder pathBob(out->subobjStart(path)); - pathBob.append("$not", pathMatch->getSerializedRightHandSide(opts)); - }; - append(opts.serializeFieldPathFromString(pathMatch->path())); + // + // One exception: while TextMatchExpressionBase derives from PathMatchExpression, text match + // expressions cannot be serialized in the same manner as other PathMatchExpression derivatives. + // This is because the path for a TextMatchExpression is embedded within the $text object, + // whereas for other PathMatchExpressions it is on the left-hand-side, for example {x: {$eq: + // 1}}. + if (auto pathMatch = dynamic_cast<PathMatchExpression*>(expressionToNegate); + pathMatch && !dynamic_cast<TextMatchExpressionBase*>(expressionToNegate)) { + const auto path = pathMatch->path(); + BSONObjBuilder pathBob(out->subobjStart(path)); + pathBob.append("$not", pathMatch->getSerializedRightHandSide()); return; } - return serializeNotExpressionToNor(expressionToNegate, out, opts); + return serializeNotExpressionToNor(expressionToNegate, out, includePath); } bool NotMatchExpression::equivalent(const MatchExpression* other) const { diff --git a/src/mongo/db/matcher/expression_tree.h b/src/mongo/db/matcher/expression_tree.h index b067c7b5f7e..f8b9179c003 100644 --- a/src/mongo/db/matcher/expression_tree.h +++ b/src/mongo/db/matcher/expression_tree.h @@ -98,9 +98,7 @@ public: protected: void _debugList(StringBuilder& debug, int indentationLevel) const; - void _listToBSON(BSONArrayBuilder* out, - const SerializationOptions& opts = {}, - bool includePath = true) const; + void _listToBSON(BSONArrayBuilder* out, bool includePath) const; private: ExpressionOptimizerFunc getOptimizer() const final; @@ -139,9 +137,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel = 0) const; - virtual void serialize(BSONObjBuilder* out, - const SerializationOptions& opts = {}, - bool includePath = true) const; + virtual void serialize(BSONObjBuilder* out, bool includePath) const; bool isTriviallyTrue() const final; @@ -185,9 +181,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel = 0) const; - virtual void serialize(BSONObjBuilder* out, - const SerializationOptions& opts = {}, - bool includePath = true) const; + virtual void serialize(BSONObjBuilder* out, bool includePath) const; bool isTriviallyFalse() const final; @@ -231,9 +225,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel = 0) const; - virtual void serialize(BSONObjBuilder* out, - const SerializationOptions& opts = {}, - bool includePath = true) const; + virtual void serialize(BSONObjBuilder* out, bool includePath) const; void acceptVisitor(MatchExpressionMutableVisitor* visitor) final { visitor->visit(this); @@ -273,9 +265,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel = 0) const; - virtual void serialize(BSONObjBuilder* out, - const SerializationOptions& opts = {}, - bool includePath = true) const; + virtual void serialize(BSONObjBuilder* out, bool includePath) const; bool equivalent(const MatchExpression* other) const final; @@ -316,8 +306,7 @@ public: private: static void serializeNotExpressionToNor(MatchExpression* exp, BSONObjBuilder* out, - const SerializationOptions& opts = {}, - bool includePath = true); + bool includePath); ExpressionOptimizerFunc getOptimizer() const final; diff --git a/src/mongo/db/matcher/expression_type.h b/src/mongo/db/matcher/expression_type.h index 07e575245c9..c81aade1701 100644 --- a/src/mongo/db/matcher/expression_type.h +++ b/src/mongo/db/matcher/expression_type.h @@ -82,10 +82,12 @@ public: debug << "\n"; } - void appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts = {}, - bool includePath = true) const final { - bob->appendArray(name(), _typeSet.toBSONArray()); + BSONObj getSerializedRightHandSide() const final { + BSONObjBuilder subBuilder; + BSONArrayBuilder arrBuilder(subBuilder.subarrayStart(name())); + _typeSet.toBSONArray(&arrBuilder); + arrBuilder.doneFast(); + return subBuilder.obj(); } bool equivalent(const MatchExpression* other) const final { @@ -254,19 +256,10 @@ public: debug << "\n"; } - void appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts = {}, - bool includePath = true) const final { - if (opts.literalPolicy == LiteralSerializationPolicy::kUnchanged) { - bob->append(name(), _binDataSubType); - } else { - // There is some fancy serialization logic to get the above BSONObjBuilder append to - // work. We just want to make sure we're doing the same thing here. - static_assert(BSONObjAppendFormat<decltype(_binDataSubType)>::value == NumberInt, - "Expecting that the BinData sub type should be specified and serialized " - "as an int."); - opts.appendLiteral(bob, name(), static_cast<int>(_binDataSubType)); - } + BSONObj getSerializedRightHandSide() const final { + BSONObjBuilder bob; + bob.append(name(), _binDataSubType); + return bob.obj(); } bool equivalent(const MatchExpression* other) const final { diff --git a/src/mongo/db/matcher/expression_type_test.cpp b/src/mongo/db/matcher/expression_type_test.cpp index 573ab77860f..bf7d40e836a 100644 --- a/src/mongo/db/matcher/expression_type_test.cpp +++ b/src/mongo/db/matcher/expression_type_test.cpp @@ -217,14 +217,6 @@ TEST(ExpressionTypeTest, InternalSchemaTypeExprWithMultipleTypesMatchesAllSuchTy ASSERT_FALSE(expr.matchesBSON(fromjson("{a: ['str']}"))); } -TEST(ExpressionTypeTest, RedactsTypesCorrectly) { - TypeMatchExpression type(""_sd, String); - auto opts = SerializationOptions{LiteralSerializationPolicy::kToDebugTypeString}; - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({"$type":[2]})", - type.getSerializedRightHandSide(opts)); -} - TEST(ExpressionBinDataSubTypeTest, MatchesBinDataGeneral) { BSONObj match = BSON("a" << BSONBinData(nullptr, 0, BinDataType::BinDataGeneral)); BSONObj notMatch = BSON("a" << BSONBinData(nullptr, 0, BinDataType::bdtCustom)); @@ -273,14 +265,6 @@ TEST(ExpressionBinDataSubTypeTest, MatchesBinDataColumnType) { ASSERT_FALSE(type.matchesSingleElement(notMatch["a"])); } -TEST(ExpressionBinDataSubTypeTest, MatchesBinDataSensitiveType) { - BSONObj match = BSON("a" << BSONBinData(nullptr, 0, BinDataType::Sensitive)); - BSONObj notMatch = BSON("a" << BSONBinData(nullptr, 0, BinDataType::newUUID)); - InternalSchemaBinDataSubTypeExpression type(""_sd, BinDataType::Sensitive); - ASSERT_TRUE(type.matchesSingleElement(match["a"])); - ASSERT_FALSE(type.matchesSingleElement(notMatch["a"])); -} - TEST(ExpressionBinDataSubTypeTest, MatchesBinDataBdtCustom) { BSONObj match = BSON("a" << BSONBinData(nullptr, 0, BinDataType::bdtCustom)); BSONObj notMatch = BSON("a" << BSONBinData(nullptr, 0, BinDataType::Function)); @@ -316,14 +300,6 @@ TEST(ExpressionBinDataSubTypeTest, Equivalent) { ASSERT(!e1.equivalent(&e3)); } -TEST(ExpressionBinDataSubTypeTest, RedactsCorrectly) { - InternalSchemaBinDataSubTypeExpression e("b"_sd, BinDataType::newUUID); - auto opts = SerializationOptions{LiteralSerializationPolicy::kToDebugTypeString}; - ASSERT_BSONOBJ_EQ_AUTO( // NOLINT - R"({"$_internalSchemaBinDataSubType":"?number"})", - e.getSerializedRightHandSide(opts)); -} - TEST(InternalSchemaBinDataEncryptedTypeTest, DoesNotTraverseLeafArrays) { MatcherTypeSet typeSet; typeSet.bsonTypes.insert(BSONType::String); diff --git a/src/mongo/db/matcher/expression_where_base.cpp b/src/mongo/db/matcher/expression_where_base.cpp index b25a8febccf..30148f9a42b 100644 --- a/src/mongo/db/matcher/expression_where_base.cpp +++ b/src/mongo/db/matcher/expression_where_base.cpp @@ -46,10 +46,8 @@ void WhereMatchExpressionBase::debugString(StringBuilder& debug, int indentation debug << "code: " << getCode() << "\n"; } -void WhereMatchExpressionBase::serialize(BSONObjBuilder* out, - const SerializationOptions& opts, - bool includePath) const { - opts.appendLiteral(out, "$where", BSONCode(getCode())); +void WhereMatchExpressionBase::serialize(BSONObjBuilder* out, bool includePath) const { + out->appendCode("$where", getCode()); } bool WhereMatchExpressionBase::equivalent(const MatchExpression* other) const { diff --git a/src/mongo/db/matcher/expression_where_base.h b/src/mongo/db/matcher/expression_where_base.h index 91b6d0985cd..550e94d1d37 100644 --- a/src/mongo/db/matcher/expression_where_base.h +++ b/src/mongo/db/matcher/expression_where_base.h @@ -69,9 +69,7 @@ public: void debugString(StringBuilder& debug, int indentationLevel = 0) const final; - void serialize(BSONObjBuilder* out, - const SerializationOptions& opts = {}, - bool includePath = true) const final; + void serialize(BSONObjBuilder* out, bool includePath) const final; bool equivalent(const MatchExpression* other) const final; diff --git a/src/mongo/db/matcher/parsed_match_expression_for_test.h b/src/mongo/db/matcher/parsed_match_expression_for_test.h deleted file mode 100644 index 4c4d8a9dc43..00000000000 --- a/src/mongo/db/matcher/parsed_match_expression_for_test.h +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Copyright (C) 2023-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * 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 - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * 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 Server Side 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. - */ - -#pragma once - -#include "mongo/db/matcher/expression.h" -#include "mongo/db/pipeline/expression_context_for_test.h" -#include "mongo/unittest/unittest.h" - -namespace mongo { - -/** - * A MatchExpression may store BSONElements as arguments for expressions, to avoid copying large - * values. A BSONElement is essentially a pointer into a BSONObj, so use - * ParsedMatchExpressionForTest to ensure that the BSONObj outlives the MatchExpression, and the - * BSONElement arguments remain pointing to allocated memory. - */ -class ParsedMatchExpressionForTest { -public: - ParsedMatchExpressionForTest(const std::string& str, - const CollatorInterface* collator = nullptr) - : _obj(fromjson(str)) { - _expCtx = make_intrusive<ExpressionContextForTest>(); - _expCtx->setCollator(CollatorInterface::cloneCollator(collator)); - StatusWithMatchExpression result = - MatchExpressionParser::parse(_obj, - _expCtx, - ExtensionsCallbackNoop(), - MatchExpressionParser::kDefaultSpecialFeatures | - MatchExpressionParser::AllowedFeatures::kJavascript); - ASSERT_OK(result.getStatus()); - _expr = std::move(result.getValue()); - } - - const MatchExpression* get() const { - return _expr.get(); - } - - /** - * Relinquishes ownership of the parsed expression and returns it as a unique_ptr to the caller. - * This 'ParsedMatchExpressionForTest' object still must outlive the returned value so that the - * BSONObj used to create it remains alive. - */ - std::unique_ptr<MatchExpression> release() { - return std::move(_expr); - } - - -private: - const BSONObj _obj; - std::unique_ptr<MatchExpression> _expr; - boost::intrusive_ptr<ExpressionContext> _expCtx; -}; -} // namespace mongo diff --git a/src/mongo/db/matcher/rewrite_expr.cpp b/src/mongo/db/matcher/rewrite_expr.cpp index 4f44cd269ef..e61ddeaab78 100644 --- a/src/mongo/db/matcher/rewrite_expr.cpp +++ b/src/mongo/db/matcher/rewrite_expr.cpp @@ -45,7 +45,7 @@ using CmpOp = ExpressionCompare::CmpOp; RewriteExpr::RewriteResult RewriteExpr::rewrite(const boost::intrusive_ptr<Expression>& expression, const CollatorInterface* collator) { LOGV2_DEBUG( - 20725, 5, "Expression prior to rewrite", "expression"_attr = expression->serialize()); + 20725, 5, "Expression prior to rewrite", "expression"_attr = expression->serialize(false)); RewriteExpr rewriteExpr(collator); std::unique_ptr<MatchExpression> matchExpression; @@ -63,9 +63,7 @@ RewriteExpr::RewriteResult RewriteExpr::rewrite(const boost::intrusive_ptr<Expre "expression"_attr = matchExpression->debugString()); } - return {std::move(matchExpression), - std::move(rewriteExpr._matchExprElemStorage), - rewriteExpr._allSubExpressionsRewritten}; + return {std::move(matchExpression), std::move(rewriteExpr._matchExprElemStorage)}; } std::unique_ptr<MatchExpression> RewriteExpr::_rewriteExpression( @@ -88,11 +86,8 @@ std::unique_ptr<MatchExpression> RewriteExpr::_rewriteAndExpression( auto andMatch = std::make_unique<AndMatchExpression>(); for (auto&& child : currExprNode->getOperandList()) - if (auto childMatch = _rewriteExpression(child)) { + if (auto childMatch = _rewriteExpression(child)) andMatch->add(std::move(childMatch)); - } else { - _allSubExpressionsRewritten = false; - } if (andMatch->numChildren() > 0) return andMatch; @@ -107,12 +102,10 @@ std::unique_ptr<MatchExpression> RewriteExpr::_rewriteOrExpression( for (auto&& child : currExprNode->getOperandList()) if (auto childExpr = _rewriteExpression(child)) orMatch->add(std::move(childExpr)); - else { + else // If any child cannot be rewritten to a MatchExpression then we must abandon adding // this $or clause. - _allSubExpressionsRewritten = false; return nullptr; - } if (orMatch->numChildren() > 0) return orMatch; diff --git a/src/mongo/db/matcher/rewrite_expr.h b/src/mongo/db/matcher/rewrite_expr.h index 08e7d82d23d..1752c9726b8 100644 --- a/src/mongo/db/matcher/rewrite_expr.h +++ b/src/mongo/db/matcher/rewrite_expr.h @@ -42,20 +42,14 @@ namespace mongo { class RewriteExpr final { public: /** - * Holds the result of an Expression rewrite operation. $expr expressions can't take advantage - * of indexes. When we rewrite the expressions as a conjunction of internal match expressions, - * the query planner can now use the internal match expressions to potentially generate an index - * scan. We use internal match expressions that are non-type bracketed to match non-type - * bracketed comparison operators inside $expr. + * Holds the result of an Expression rewrite operation. */ class RewriteResult final { public: RewriteResult(std::unique_ptr<MatchExpression> matchExpression, - std::vector<BSONObj> matchExprElemStorage, - bool allSubExpressionsRewritten) + std::vector<BSONObj> matchExprElemStorage) : _matchExpression(std::move(matchExpression)), - _matchExprElemStorage(std::move(matchExprElemStorage)), - _allSubExpressionsRewritten(allSubExpressionsRewritten) {} + _matchExprElemStorage(std::move(matchExprElemStorage)) {} MatchExpression* matchExpression() const { return _matchExpression.get(); @@ -67,11 +61,7 @@ public: RewriteResult clone() const { auto clonedMatch = _matchExpression ? _matchExpression->shallowClone() : nullptr; - return {std::move(clonedMatch), _matchExprElemStorage, _allSubExpressionsRewritten}; - } - - bool allSubExpressionsRewritten() { - return _allSubExpressionsRewritten; + return {std::move(clonedMatch), _matchExprElemStorage}; } private: @@ -81,22 +71,12 @@ public: // owned and expected to outlive the MatchExpression. '_matchExprElemStorage' holds the // underlying BSONObj storage for these arguments. std::vector<BSONObj> _matchExprElemStorage; - - // Defaults to true, is false if there is a child in an $or/$and expression that - // contains children that cannot be rewritten to a MatchExpression. - bool _allSubExpressionsRewritten = true; }; /** - * Attempts to construct a MatchExpression that will match against either an identical set - * or a superset of the documents matched by 'expr'. Due to semantic differences the - * rewritten MatchExpression might match more documents than the ExprMatchExpression. For - * example, - * $_internalExprEq in MatchExpression reaches into arrays, and $eq in ExprMatchExpression - * does not. However, $_internalExprLt/$_internalExprGt are non-type bracketed for - * MatchExpression, just like ExprMatchExpressions. Returns the MatchExpression as a - * RewriteResult. If a rewrite is not possible, RewriteResult::matchExpression() will return - * a nullptr. + * Attempts to construct a MatchExpression that will match against either an identical set or a + * superset of the documents matched by 'expr'. Returns the MatchExpression as a RewriteResult. + * If a rewrite is not possible, RewriteResult::matchExpression() will return a nullptr. */ static RewriteResult rewrite(const boost::intrusive_ptr<Expression>& expr, const CollatorInterface* collator); @@ -127,7 +107,6 @@ private: std::vector<BSONObj> _matchExprElemStorage; const CollatorInterface* _collator; - bool _allSubExpressionsRewritten = true; }; } // namespace mongo diff --git a/src/mongo/db/matcher/rewrite_expr_test.cpp b/src/mongo/db/matcher/rewrite_expr_test.cpp index c4f1a4b7619..948add02c26 100644 --- a/src/mongo/db/matcher/rewrite_expr_test.cpp +++ b/src/mongo/db/matcher/rewrite_expr_test.cpp @@ -57,7 +57,7 @@ void testExprRewrite(BSONObj expr, BSONObj expectedMatch) { if (!expectedMatch.isEmpty()) { ASSERT(result.matchExpression()); BSONObjBuilder bob; - result.matchExpression()->serialize(&bob, {}); + result.matchExpression()->serialize(&bob, true); ASSERT_BSONOBJ_EQ(expectedMatch, bob.obj()); } else { ASSERT_FALSE(result.matchExpression()); diff --git a/src/mongo/db/matcher/schema/assert_serializes_to.h b/src/mongo/db/matcher/schema/assert_serializes_to.h index 7f9b3d14292..e62b5e62e1d 100644 --- a/src/mongo/db/matcher/schema/assert_serializes_to.h +++ b/src/mongo/db/matcher/schema/assert_serializes_to.h @@ -34,9 +34,11 @@ namespace mongo { /** * Asserts that the given MatchExpression 'match' serializes to the BSONObj 'expected'. */ -#define ASSERT_SERIALIZES_TO(match, expected) \ - do { \ - ASSERT_BSONOBJ_EQ(match->serialize(), expected); \ +#define ASSERT_SERIALIZES_TO(match, expected) \ + do { \ + BSONObjBuilder bob; \ + match->serialize(&bob, true); \ + ASSERT_BSONOBJ_EQ(bob.obj(), expected); \ } while (false) } // namespace mongo diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.cpp index caf39e7fcc4..27b2adae68d 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.cpp +++ b/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.cpp @@ -76,11 +76,17 @@ void InternalSchemaAllElemMatchFromIndexMatchExpression::debugString(StringBuild _expression->getFilter()->debugString(debug, indentationLevel + 1); } -void InternalSchemaAllElemMatchFromIndexMatchExpression::appendSerializedRightHandSide( - BSONObjBuilder* bob, const SerializationOptions& opts, bool includePath) const { - bob->append(kName, - BSON_ARRAY(opts.serializeLiteral(_index) - << _expression->getFilter()->serialize(opts, includePath))); +BSONObj InternalSchemaAllElemMatchFromIndexMatchExpression::getSerializedRightHandSide() const { + BSONObjBuilder allElemMatchBob; + BSONArrayBuilder subArray(allElemMatchBob.subarrayStart(kName)); + subArray.append(_index); + { + BSONObjBuilder eBuilder(subArray.subobjStart()); + _expression->getFilter()->serialize(&eBuilder, true); + eBuilder.doneFast(); + } + subArray.doneFast(); + return allElemMatchBob.obj(); } MatchExpression::ExpressionOptimizerFunc diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.h b/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.h index 1d1ff14d6c0..0a613b23303 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.h +++ b/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.h @@ -77,9 +77,7 @@ public: void debugString(StringBuilder& debug, int indentationLevel) const final; - void appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts = {}, - bool includePath = true) const final; + BSONObj getSerializedRightHandSide() const final; bool equivalent(const MatchExpression* other) const final; diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index_test.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index_test.cpp index b3b3234778d..49e1b3b0235 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index_test.cpp +++ b/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index_test.cpp @@ -135,5 +135,6 @@ DEATH_TEST_REGEX(InternalSchemaAllElemMatchFromIndexMatchExpression, objMatch.getValue()->getChild(1); } + } // namespace } // namespace mongo diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_allowed_properties.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_allowed_properties.cpp index 5a6471e2155..64b34aafc3a 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_allowed_properties.cpp +++ b/src/mongo/db/matcher/schema/expression_internal_schema_allowed_properties.cpp @@ -59,7 +59,7 @@ void InternalSchemaAllowedPropertiesMatchExpression::debugString(StringBuilder& _debugAddSpace(debug, indentationLevel); BSONObjBuilder builder; - serialize(&builder, {}); + serialize(&builder, true); debug << builder.obj().toString() << "\n"; const auto* tag = getTag(); @@ -128,29 +128,29 @@ bool InternalSchemaAllowedPropertiesMatchExpression::_matchesBSONObj(const BSONO } void InternalSchemaAllowedPropertiesMatchExpression::serialize(BSONObjBuilder* builder, - const SerializationOptions& opts, bool includePath) const { BSONObjBuilder expressionBuilder( builder->subobjStart(InternalSchemaAllowedPropertiesMatchExpression::kName)); std::vector<StringData> sortedProperties(_properties.begin(), _properties.end()); std::sort(sortedProperties.begin(), sortedProperties.end()); - opts.appendLiteral(&expressionBuilder, "properties", sortedProperties); - // This will be serialized to "i", which is the parser chosen namePlaceholder. Using this - // unmodified will have a similar effect to serializing to "?", however it preserves round trip - // parsing. + expressionBuilder.append("properties", sortedProperties); + expressionBuilder.append("namePlaceholder", _namePlaceholder); BSONArrayBuilder patternPropertiesBuilder(expressionBuilder.subarrayStart("patternProperties")); - for (auto&& [pattern, expression] : _patternProperties) { - patternPropertiesBuilder << BSON( - "regex" << opts.serializeLiteral(BSONRegEx(pattern.rawRegex)) << "expression" - << expression->getFilter()->serialize(opts, includePath)); + for (auto&& item : _patternProperties) { + BSONObjBuilder itemBuilder(patternPropertiesBuilder.subobjStart()); + itemBuilder.appendRegex("regex", item.first.rawRegex); + + BSONObjBuilder subexpressionBuilder(itemBuilder.subobjStart("expression")); + item.second->getFilter()->serialize(&subexpressionBuilder, includePath); + subexpressionBuilder.doneFast(); } patternPropertiesBuilder.doneFast(); BSONObjBuilder otherwiseBuilder(expressionBuilder.subobjStart("otherwise")); - _otherwise->getFilter()->serialize(&otherwiseBuilder, opts, includePath); + _otherwise->getFilter()->serialize(&otherwiseBuilder, includePath); otherwiseBuilder.doneFast(); expressionBuilder.doneFast(); } diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_allowed_properties.h b/src/mongo/db/matcher/schema/expression_internal_schema_allowed_properties.h index ae6de80a7f8..e95d0582d15 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_allowed_properties.h +++ b/src/mongo/db/matcher/schema/expression_internal_schema_allowed_properties.h @@ -136,9 +136,7 @@ public: bool matches(const MatchableDocument* doc, MatchDetails* details) const final; bool matchesSingleElement(const BSONElement& element, MatchDetails* details) const final; - void serialize(BSONObjBuilder* builder, - const SerializationOptions& opts = {}, - bool includePath = true) const final; + void serialize(BSONObjBuilder* builder, bool includePath) const final; std::unique_ptr<MatchExpression> shallowClone() const final; diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_eq.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_eq.cpp index 31f5812256f..263b23cc4e9 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_eq.cpp +++ b/src/mongo/db/matcher/schema/expression_internal_schema_eq.cpp @@ -69,16 +69,10 @@ void InternalSchemaEqMatchExpression::debugString(StringBuilder& debug, debug << "\n"; } -void InternalSchemaEqMatchExpression::appendSerializedRightHandSide( - BSONObjBuilder* bob, const SerializationOptions& opts, bool includePath) const { - if (opts.literalPolicy != LiteralSerializationPolicy::kUnchanged && _rhsElem.isABSONObj()) { - BSONObjBuilder exprSpec(bob->subobjStart(kName)); - opts.addHmacedObjToBuilder(&exprSpec, _rhsElem.Obj()); - exprSpec.doneFast(); - return; - } - // If the element is not an object it must be a literal. - opts.appendLiteral(bob, kName, _rhsElem); +BSONObj InternalSchemaEqMatchExpression::getSerializedRightHandSide() const { + BSONObjBuilder eqObj; + eqObj.appendAs(_rhsElem, kName); + return eqObj.obj(); } bool InternalSchemaEqMatchExpression::equivalent(const MatchExpression* other) const { diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_eq.h b/src/mongo/db/matcher/schema/expression_internal_schema_eq.h index 5a24d5787d2..3f604294f60 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_eq.h +++ b/src/mongo/db/matcher/schema/expression_internal_schema_eq.h @@ -59,9 +59,7 @@ public: void debugString(StringBuilder& debug, int indentationLevel) const final; - void appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts = {}, - bool includePath = true) const final; + BSONObj getSerializedRightHandSide() const final; bool equivalent(const MatchExpression* other) const final; diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_fmod.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_fmod.cpp index fbd267bcbbf..e86e4d0240d 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_fmod.cpp +++ b/src/mongo/db/matcher/schema/expression_internal_schema_fmod.cpp @@ -76,10 +76,13 @@ void InternalSchemaFmodMatchExpression::debugString(StringBuilder& debug, debug << "\n"; } -void InternalSchemaFmodMatchExpression::appendSerializedRightHandSide( - BSONObjBuilder* bob, const SerializationOptions& opts, bool includePath) const { - bob->append("$_internalSchemaFmod"_sd, - BSON_ARRAY(opts.serializeLiteral(_divisor) << opts.serializeLiteral(_remainder))); +BSONObj InternalSchemaFmodMatchExpression::getSerializedRightHandSide() const { + BSONObjBuilder objMatchBob; + BSONArrayBuilder arrBuilder(objMatchBob.subarrayStart("$_internalSchemaFmod")); + arrBuilder.append(_divisor); + arrBuilder.append(_remainder); + arrBuilder.doneFast(); + return objMatchBob.obj(); } bool InternalSchemaFmodMatchExpression::equivalent(const MatchExpression* other) const { diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_fmod.h b/src/mongo/db/matcher/schema/expression_internal_schema_fmod.h index e808e079b27..ae147f3a4fe 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_fmod.h +++ b/src/mongo/db/matcher/schema/expression_internal_schema_fmod.h @@ -58,9 +58,7 @@ public: void debugString(StringBuilder& debug, int indentationLevel) const final; - void appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts = {}, - bool includePath = true) const final; + BSONObj getSerializedRightHandSide() const final; bool equivalent(const MatchExpression* other) const final; diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_match_array_index.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_match_array_index.cpp index 8af85dd6436..a09ad27ff22 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_match_array_index.cpp +++ b/src/mongo/db/matcher/schema/expression_internal_schema_match_array_index.cpp @@ -51,7 +51,7 @@ void InternalSchemaMatchArrayIndexMatchExpression::debugString(StringBuilder& de _debugAddSpace(debug, indentationLevel); BSONObjBuilder builder; - serialize(&builder, {}); + serialize(&builder, true); debug << builder.obj().toString() << "\n"; const auto* tag = getTag(); @@ -72,14 +72,20 @@ bool InternalSchemaMatchArrayIndexMatchExpression::equivalent(const MatchExpress _expression->equivalent(other->_expression.get()); } -void InternalSchemaMatchArrayIndexMatchExpression::appendSerializedRightHandSide( - BSONObjBuilder* bob, const SerializationOptions& opts, bool includePath) const { - bob->append( - kName, - BSON( - "index" << opts.serializeLiteral(_index) << "namePlaceholder" - << opts.serializeFieldPathFromString(_expression->getPlaceholder().value_or("")) - << "expression" << _expression->getFilter()->serialize(opts, includePath))); +BSONObj InternalSchemaMatchArrayIndexMatchExpression::getSerializedRightHandSide() const { + BSONObjBuilder objBuilder; + { + BSONObjBuilder matchArrayElemSubobj(objBuilder.subobjStart(kName)); + matchArrayElemSubobj.append("index", _index); + matchArrayElemSubobj.append("namePlaceholder", _expression->getPlaceholder().value_or("")); + { + BSONObjBuilder subexprSubObj(matchArrayElemSubobj.subobjStart("expression")); + _expression->getFilter()->serialize(&subexprSubObj, true); + subexprSubObj.doneFast(); + } + matchArrayElemSubobj.doneFast(); + } + return objBuilder.obj(); } std::unique_ptr<MatchExpression> InternalSchemaMatchArrayIndexMatchExpression::shallowClone() diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_match_array_index.h b/src/mongo/db/matcher/schema/expression_internal_schema_match_array_index.h index cea0149a028..8fff1225bf3 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_match_array_index.h +++ b/src/mongo/db/matcher/schema/expression_internal_schema_match_array_index.h @@ -73,9 +73,7 @@ public: return _expression->matchesBSONElement(element, details); } - void appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts = {}, - bool includePath = true) const final; + BSONObj getSerializedRightHandSide() const final; std::unique_ptr<MatchExpression> shallowClone() const final; diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_num_array_items.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_num_array_items.cpp index c6d20f63409..6fde9a327cf 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_num_array_items.cpp +++ b/src/mongo/db/matcher/schema/expression_internal_schema_num_array_items.cpp @@ -56,9 +56,10 @@ void InternalSchemaNumArrayItemsMatchExpression::debugString(StringBuilder& debu debug << "\n"; } -void InternalSchemaNumArrayItemsMatchExpression::appendSerializedRightHandSide( - BSONObjBuilder* bob, const SerializationOptions& opts, bool includePath) const { - opts.appendLiteral(bob, _name, _numItems); +BSONObj InternalSchemaNumArrayItemsMatchExpression::getSerializedRightHandSide() const { + BSONObjBuilder objBuilder; + objBuilder.append(_name, _numItems); + return objBuilder.obj(); } bool InternalSchemaNumArrayItemsMatchExpression::equivalent(const MatchExpression* other) const { diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_num_array_items.h b/src/mongo/db/matcher/schema/expression_internal_schema_num_array_items.h index e566692b642..99ed79ec5d6 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_num_array_items.h +++ b/src/mongo/db/matcher/schema/expression_internal_schema_num_array_items.h @@ -52,9 +52,7 @@ public: void debugString(StringBuilder& debug, int indentationLevel) const final; - void appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts = {}, - bool includePath = true) const final; + BSONObj getSerializedRightHandSide() const final; bool equivalent(const MatchExpression* other) const final; diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_num_properties.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_num_properties.cpp index 91b764dd8f5..e640a58ec71 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_num_properties.cpp +++ b/src/mongo/db/matcher/schema/expression_internal_schema_num_properties.cpp @@ -37,14 +37,13 @@ void InternalSchemaNumPropertiesMatchExpression::debugString(StringBuilder& debu int indentationLevel) const { _debugAddSpace(debug, indentationLevel); BSONObjBuilder builder; - serialize(&builder, {}); + serialize(&builder, true); debug << builder.obj().toString() << "\n"; } void InternalSchemaNumPropertiesMatchExpression::serialize(BSONObjBuilder* out, - const SerializationOptions& opts, bool includePath) const { - opts.appendLiteral(out, _name, _numProperties); + out->append(_name, _numProperties); } bool InternalSchemaNumPropertiesMatchExpression::equivalent(const MatchExpression* other) const { diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_num_properties.h b/src/mongo/db/matcher/schema/expression_internal_schema_num_properties.h index 679aaaca609..29fd4fc6145 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_num_properties.h +++ b/src/mongo/db/matcher/schema/expression_internal_schema_num_properties.h @@ -71,9 +71,7 @@ public: void debugString(StringBuilder& debug, int indentationLevel) const final; - void serialize(BSONObjBuilder* out, - const SerializationOptions& opts = {}, - bool includePath = true) const final; + void serialize(BSONObjBuilder* out, bool includePath) const final; bool equivalent(const MatchExpression* other) const final; diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_object_match.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_object_match.cpp index 317c316374d..ec5a0943fad 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_object_match.cpp +++ b/src/mongo/db/matcher/schema/expression_internal_schema_object_match.cpp @@ -61,9 +61,12 @@ void InternalSchemaObjectMatchExpression::debugString(StringBuilder& debug, _sub->debugString(debug, indentationLevel + 1); } -void InternalSchemaObjectMatchExpression::appendSerializedRightHandSide( - BSONObjBuilder* bob, const SerializationOptions& opts, bool includePath) const { - bob->append(kName, _sub->serialize(opts, includePath)); +BSONObj InternalSchemaObjectMatchExpression::getSerializedRightHandSide() const { + BSONObjBuilder objMatchBob; + BSONObjBuilder subBob(objMatchBob.subobjStart(kName)); + _sub->serialize(&subBob, true); + subBob.doneFast(); + return objMatchBob.obj(); } bool InternalSchemaObjectMatchExpression::equivalent(const MatchExpression* other) const { diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_object_match.h b/src/mongo/db/matcher/schema/expression_internal_schema_object_match.h index a41190d9545..93b6e1b765e 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_object_match.h +++ b/src/mongo/db/matcher/schema/expression_internal_schema_object_match.h @@ -49,9 +49,7 @@ public: void debugString(StringBuilder& debug, int indentationLevel = 0) const final; - void appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts = {}, - bool includePath = true) const final; + BSONObj getSerializedRightHandSide() const final; bool equivalent(const MatchExpression* other) const final; diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_root_doc_eq.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_root_doc_eq.cpp index 300d1a49109..c4b1e3f7aa2 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_root_doc_eq.cpp +++ b/src/mongo/db/matcher/schema/expression_internal_schema_root_doc_eq.cpp @@ -55,11 +55,9 @@ void InternalSchemaRootDocEqMatchExpression::debugString(StringBuilder& debug, } void InternalSchemaRootDocEqMatchExpression::serialize(BSONObjBuilder* out, - const SerializationOptions& opts, bool includePath) const { BSONObjBuilder subObj(out->subobjStart(kName)); - SerializationOptions options = opts; - options.addHmacedObjToBuilder(&subObj, _rhsObj); + subObj.appendElements(_rhsObj); subObj.doneFast(); } diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_root_doc_eq.h b/src/mongo/db/matcher/schema/expression_internal_schema_root_doc_eq.h index 4418e101d43..62dfb6d66a2 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_root_doc_eq.h +++ b/src/mongo/db/matcher/schema/expression_internal_schema_root_doc_eq.h @@ -72,9 +72,7 @@ public: void debugString(StringBuilder& debug, int indentationLevel = 0) const final; - void serialize(BSONObjBuilder* out, - const SerializationOptions& opts = {}, - bool includePath = true) const final; + void serialize(BSONObjBuilder* out, bool includePath) const final; bool equivalent(const MatchExpression* other) const final; diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_str_length.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_str_length.cpp index bdf2f5c2c4c..87d6396fd0f 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_str_length.cpp +++ b/src/mongo/db/matcher/schema/expression_internal_schema_str_length.cpp @@ -56,9 +56,10 @@ void InternalSchemaStrLengthMatchExpression::debugString(StringBuilder& debug, debug << "\n"; } -void InternalSchemaStrLengthMatchExpression::appendSerializedRightHandSide( - BSONObjBuilder* bob, const SerializationOptions& opts, bool includePath) const { - opts.appendLiteral(bob, _name, _strLen); +BSONObj InternalSchemaStrLengthMatchExpression::getSerializedRightHandSide() const { + BSONObjBuilder objBuilder; + objBuilder.append(_name, _strLen); + return objBuilder.obj(); } bool InternalSchemaStrLengthMatchExpression::equivalent(const MatchExpression* other) const { diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_str_length.h b/src/mongo/db/matcher/schema/expression_internal_schema_str_length.h index 6796c532972..43483be1ce3 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_str_length.h +++ b/src/mongo/db/matcher/schema/expression_internal_schema_str_length.h @@ -62,9 +62,7 @@ public: void debugString(StringBuilder& debug, int indentationLevel) const final; - void appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts = {}, - bool includePath = true) const final; + BSONObj getSerializedRightHandSide() const final; bool equivalent(const MatchExpression* other) const final; diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_unique_items.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_unique_items.cpp index afe8f8422a9..f78dca0c37f 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_unique_items.cpp +++ b/src/mongo/db/matcher/schema/expression_internal_schema_unique_items.cpp @@ -39,7 +39,7 @@ void InternalSchemaUniqueItemsMatchExpression::debugString(StringBuilder& debug, _debugAddSpace(debug, indentationLevel); BSONObjBuilder builder; - serialize(&builder, {}); + serialize(&builder, true); debug << builder.obj().toString() << "\n"; const auto* tag = getTag(); @@ -59,9 +59,10 @@ bool InternalSchemaUniqueItemsMatchExpression::equivalent(const MatchExpression* return path() == other->path(); } -void InternalSchemaUniqueItemsMatchExpression::appendSerializedRightHandSide( - BSONObjBuilder* bob, const SerializationOptions& opts, bool includePath) const { - bob->append(kName, true); +BSONObj InternalSchemaUniqueItemsMatchExpression::getSerializedRightHandSide() const { + BSONObjBuilder bob; + bob.append(kName, true); + return bob.obj(); } std::unique_ptr<MatchExpression> InternalSchemaUniqueItemsMatchExpression::shallowClone() const { diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_unique_items.h b/src/mongo/db/matcher/schema/expression_internal_schema_unique_items.h index dc6e76af59f..ddb86c89403 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_unique_items.h +++ b/src/mongo/db/matcher/schema/expression_internal_schema_unique_items.h @@ -86,9 +86,7 @@ public: bool equivalent(const MatchExpression* other) const final; - void appendSerializedRightHandSide(BSONObjBuilder* bob, - const SerializationOptions& opts = {}, - bool includePath = true) const final; + BSONObj getSerializedRightHandSide() const final; std::unique_ptr<MatchExpression> shallowClone() const final; diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_xor.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_xor.cpp index 04c5bbb045a..ad265266f52 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_xor.cpp +++ b/src/mongo/db/matcher/schema/expression_internal_schema_xor.cpp @@ -73,10 +73,8 @@ void InternalSchemaXorMatchExpression::debugString(StringBuilder& debug, _debugList(debug, indentationLevel); } -void InternalSchemaXorMatchExpression::serialize(BSONObjBuilder* out, - const SerializationOptions& opts, - bool includePath) const { +void InternalSchemaXorMatchExpression::serialize(BSONObjBuilder* out, bool includePath) const { BSONArrayBuilder arrBob(out->subarrayStart(kName)); - _listToBSON(&arrBob, opts, includePath); + _listToBSON(&arrBob, includePath); } } // namespace mongo diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_xor.h b/src/mongo/db/matcher/schema/expression_internal_schema_xor.h index 57825bff060..928a13413f1 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_xor.h +++ b/src/mongo/db/matcher/schema/expression_internal_schema_xor.h @@ -69,9 +69,7 @@ public: void debugString(StringBuilder& debug, int indentationLevel = 0) const final; - void serialize(BSONObjBuilder* out, - const SerializationOptions& opts = {}, - bool includePath = true) const final; + void serialize(BSONObjBuilder* out, bool includePath) const final; void acceptVisitor(MatchExpressionMutableVisitor* visitor) final { visitor->visit(this); diff --git a/src/mongo/db/matcher/schema/object_keywords_test.cpp b/src/mongo/db/matcher/schema/object_keywords_test.cpp index 468c0bb8cc9..73bb4b7d7dd 100644 --- a/src/mongo/db/matcher/schema/object_keywords_test.cpp +++ b/src/mongo/db/matcher/schema/object_keywords_test.cpp @@ -256,11 +256,11 @@ TEST(JSONSchemaObjectKeywordTest, SharedJsonAndBsonTypeAliasesTranslateIdentical ASSERT_OK(bsonTypeResult.getStatus()); BSONObjBuilder typeBuilder; - MatchExpression::optimize(std::move(typeResult.getValue()))->serialize(&typeBuilder, {}); + MatchExpression::optimize(std::move(typeResult.getValue()))->serialize(&typeBuilder, true); BSONObjBuilder bsonTypeBuilder; MatchExpression::optimize(std::move(bsonTypeResult.getValue())) - ->serialize(&bsonTypeBuilder, {}); + ->serialize(&bsonTypeBuilder, true); ASSERT_BSONOBJ_EQ(typeBuilder.obj(), bsonTypeBuilder.obj()); } |
