diff options
author | Charlie Swanson <charlie.swanson@mongodb.com> | 2023-02-10 20:24:21 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2023-02-11 03:25:32 +0000 |
commit | 36d9f82090f1d84f38d3d444319ce17393455353 (patch) | |
tree | 69a87cb94a0f7b3161c185439047427f8899bad2 | |
parent | 1e55535078cb529ae36f32198086e382d0a7d4bd (diff) | |
download | mongo-36d9f82090f1d84f38d3d444319ce17393455353.tar.gz |
SERVER-73141 Generate query shape for expressions in expression_leaf.h
77 files changed, 1055 insertions, 720 deletions
diff --git a/src/mongo/db/exec/bucket_unpacker.cpp b/src/mongo/db/exec/bucket_unpacker.cpp index 6ba30b07503..472c1ea369b 100644 --- a/src/mongo/db/exec/bucket_unpacker.cpp +++ b/src/mongo/db/exec/bucket_unpacker.cpp @@ -910,9 +910,9 @@ std::pair<bool, BSONObj> BucketSpec::pushdownPredicate( BSONObjBuilder result; if (metaOnlyPredicate) - metaOnlyPredicate->serialize(&result); + metaOnlyPredicate->serialize(&result, {}); if (bucketMetricPredicate) - bucketMetricPredicate->serialize(&result); + bucketMetricPredicate->serialize(&result, {}); return std::make_pair(bucketMetricPredicate.get(), result.obj()); } diff --git a/src/mongo/db/exec/collection_scan.cpp b/src/mongo/db/exec/collection_scan.cpp index 6cbacb0c997..e9deeaa9069 100644 --- a/src/mongo/db/exec/collection_scan.cpp +++ b/src/mongo/db/exec/collection_scan.cpp @@ -471,9 +471,7 @@ void CollectionScan::doReattachToOperationContext() { unique_ptr<PlanStageStats> CollectionScan::getStats() { // Add a BSON representation of the filter to the stats tree, if there is one. if (nullptr != _filter) { - BSONObjBuilder bob; - _filter->serialize(&bob); - _commonStats.filter = bob.obj(); + _commonStats.filter = _filter->serialize(); } unique_ptr<PlanStageStats> ret = std::make_unique<PlanStageStats>(_commonStats, STAGE_COLLSCAN); diff --git a/src/mongo/db/exec/fetch.cpp b/src/mongo/db/exec/fetch.cpp index 2bdcb890bc8..70718070292 100644 --- a/src/mongo/db/exec/fetch.cpp +++ b/src/mongo/db/exec/fetch.cpp @@ -191,10 +191,8 @@ unique_ptr<PlanStageStats> FetchStage::getStats() { _commonStats.isEOF = isEOF(); // Add a BSON representation of the filter to the stats tree, if there is one. - if (nullptr != _filter) { - BSONObjBuilder bob; - _filter->serialize(&bob); - _commonStats.filter = bob.obj(); + if (_filter) { + _commonStats.filter = _filter->serialize(); } unique_ptr<PlanStageStats> ret = std::make_unique<PlanStageStats>(_commonStats, STAGE_FETCH); diff --git a/src/mongo/db/exec/index_scan.cpp b/src/mongo/db/exec/index_scan.cpp index 03b29817202..1d89f2c3957 100644 --- a/src/mongo/db/exec/index_scan.cpp +++ b/src/mongo/db/exec/index_scan.cpp @@ -294,9 +294,7 @@ std::unique_ptr<PlanStageStats> IndexScan::getStats() { // Add a BSON representation of the filter to the stats tree, if there is one. if (nullptr != _filter) { - BSONObjBuilder bob; - _filter->serialize(&bob); - _commonStats.filter = bob.obj(); + _commonStats.filter = _filter->serialize(); } // These specific stats fields never change. diff --git a/src/mongo/db/exec/or.cpp b/src/mongo/db/exec/or.cpp index ec0d680ac37..078765ffc84 100644 --- a/src/mongo/db/exec/or.cpp +++ b/src/mongo/db/exec/or.cpp @@ -122,10 +122,8 @@ unique_ptr<PlanStageStats> OrStage::getStats() { _commonStats.isEOF = isEOF(); // Add a BSON representation of the filter to the stats tree, if there is one. - if (nullptr != _filter) { - BSONObjBuilder bob; - _filter->serialize(&bob); - _commonStats.filter = bob.obj(); + if (_filter) { + _commonStats.filter = _filter->serialize(); } unique_ptr<PlanStageStats> ret = std::make_unique<PlanStageStats>(_commonStats, STAGE_OR); diff --git a/src/mongo/db/exec/text_or.cpp b/src/mongo/db/exec/text_or.cpp index c7a9f3d7b17..2d477ca49e5 100644 --- a/src/mongo/db/exec/text_or.cpp +++ b/src/mongo/db/exec/text_or.cpp @@ -105,9 +105,7 @@ std::unique_ptr<PlanStageStats> TextOrStage::getStats() { _commonStats.isEOF = isEOF(); if (_filter) { - BSONObjBuilder bob; - _filter->serialize(&bob); - _commonStats.filter = bob.obj(); + _commonStats.filter = _filter->serialize(); } unique_ptr<PlanStageStats> ret = std::make_unique<PlanStageStats>(_commonStats, STAGE_TEXT_OR); diff --git a/src/mongo/db/matcher/expression.h b/src/mongo/db/matcher/expression.h index a56139a343c..afc50fe73f0 100644 --- a/src/mongo/db/matcher/expression.h +++ b/src/mongo/db/matcher/expression.h @@ -41,6 +41,7 @@ #include "mongo/db/matcher/match_details.h" #include "mongo/db/matcher/matchable.h" #include "mongo/db/pipeline/dependencies.h" +#include "mongo/db/query/serialization_options.h" #include "mongo/util/fail_point.h" namespace mongo { @@ -473,19 +474,27 @@ public: void setCollator(const CollatorInterface* collator); /** - * 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. + * 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.replacementForLiteralArgs' is set, 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). */ - virtual void serialize(BSONObjBuilder* out, bool includePath = true) const = 0; + virtual void serialize(BSONObjBuilder* out, SerializationOptions options) const = 0; /** - * Convenience method which serializes this MatchExpression to a BSONObj. + * Convenience method which serializes this MatchExpression to a BSONObj. See the override with + * a BSONObjBuilder* argument for details. */ - BSONObj serialize(bool includePath = true) const { + BSONObj serialize(SerializationOptions options = {}) const { BSONObjBuilder bob; - serialize(&bob, includePath); + serialize(&bob, options); return bob.obj(); } diff --git a/src/mongo/db/matcher/expression_algo_test.cpp b/src/mongo/db/matcher/expression_algo_test.cpp index c1fcb232736..fc810253af8 100644 --- a/src/mongo/db/matcher/expression_algo_test.cpp +++ b/src/mongo/db/matcher/expression_algo_test.cpp @@ -36,6 +36,7 @@ #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" @@ -44,36 +45,8 @@ 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()); - } - - const MatchExpression* get() const { - return _expr.get(); - } - std::unique_ptr<MatchExpression> extractExpr() { - return std::move(_expr); - } - -private: - const BSONObj _obj; - std::unique_ptr<MatchExpression> _expr; - boost::intrusive_ptr<ExpressionContext> _expCtx; -}; - -void assertMatchesEqual(const ParsedMatchExpression& expected, +void assertMatchesEqual(const ParsedMatchExpressionForTest& expected, const std::unique_ptr<MatchExpression>& actual) { if (expected.get() == nullptr) { ASSERT(actual == nullptr); @@ -91,29 +64,29 @@ TEST(ExpressionAlgoIsSubsetOf, NullAndOmittedField) { ASSERT_EQUALS(ErrorCodes::BadValue, MatchExpressionParser::parse(undefined, std::move(expCtx)).getStatus()); - ParsedMatchExpression empty("{}"); - ParsedMatchExpression null("{a: null}"); + ParsedMatchExpressionForTest empty("{}"); + ParsedMatchExpressionForTest null("{a: null}"); ASSERT_TRUE(expression::isSubsetOf(null.get(), empty.get())); ASSERT_FALSE(expression::isSubsetOf(empty.get(), null.get())); - ParsedMatchExpression b1("{b: 1}"); - ParsedMatchExpression aNullB1("{a: null, b: 1}"); + ParsedMatchExpressionForTest b1("{b: 1}"); + ParsedMatchExpressionForTest aNullB1("{a: null, b: 1}"); ASSERT_TRUE(expression::isSubsetOf(aNullB1.get(), b1.get())); ASSERT_FALSE(expression::isSubsetOf(b1.get(), aNullB1.get())); - ParsedMatchExpression a1C3("{a: 1, c: 3}"); - ParsedMatchExpression a1BNullC3("{a: 1, b: null, c: 3}"); + ParsedMatchExpressionForTest a1C3("{a: 1, c: 3}"); + ParsedMatchExpressionForTest 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) { - ParsedMatchExpression eqNull("{x: null}"); - ParsedMatchExpression inNull("{x: {$in: [null]}}"); - ParsedMatchExpression inNullOr2("{x: {$in: [null, 2]}}"); + ParsedMatchExpressionForTest eqNull("{x: null}"); + ParsedMatchExpressionForTest inNull("{x: {$in: [null]}}"); + ParsedMatchExpressionForTest inNullOr2("{x: {$in: [null, 2]}}"); ASSERT_TRUE(expression::isSubsetOf(inNull.get(), eqNull.get())); ASSERT_FALSE(expression::isSubsetOf(inNullOr2.get(), eqNull.get())); @@ -123,19 +96,19 @@ TEST(ExpressionAlgoIsSubsetOf, NullAndIn) { } TEST(ExpressionAlgoIsSubsetOf, NullAndExists) { - ParsedMatchExpression null("{x: null}"); - ParsedMatchExpression exists("{x: {$exists: true}}"); + ParsedMatchExpressionForTest null("{x: null}"); + ParsedMatchExpressionForTest exists("{x: {$exists: true}}"); ASSERT_FALSE(expression::isSubsetOf(null.get(), exists.get())); ASSERT_FALSE(expression::isSubsetOf(exists.get(), null.get())); } TEST(ExpressionAlgoIsSubsetOf, Compare_NaN) { - 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]}}"); + 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]}}"); ASSERT_TRUE(expression::isSubsetOf(nan.get(), nan.get())); ASSERT_FALSE(expression::isSubsetOf(nan.get(), lt.get())); @@ -149,7 +122,7 @@ TEST(ExpressionAlgoIsSubsetOf, Compare_NaN) { ASSERT_FALSE(expression::isSubsetOf(nan.get(), in.get())); ASSERT_FALSE(expression::isSubsetOf(in.get(), nan.get())); - ParsedMatchExpression decNan("{x : NumberDecimal(\"NaN\") }"); + ParsedMatchExpressionForTest 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())); @@ -164,9 +137,9 @@ TEST(ExpressionAlgoIsSubsetOf, Compare_NaN) { } TEST(ExpressionAlgoIsSubsetOf, Compare_EQ) { - ParsedMatchExpression a5("{a: 5}"); - ParsedMatchExpression a6("{a: 6}"); - ParsedMatchExpression b5("{b: 5}"); + ParsedMatchExpressionForTest a5("{a: 5}"); + ParsedMatchExpressionForTest a6("{a: 6}"); + ParsedMatchExpressionForTest b5("{b: 5}"); ASSERT_TRUE(expression::isSubsetOf(a5.get(), a5.get())); ASSERT_FALSE(expression::isSubsetOf(a5.get(), a6.get())); @@ -174,10 +147,10 @@ TEST(ExpressionAlgoIsSubsetOf, Compare_EQ) { } TEST(ExpressionAlgoIsSubsetOf, CompareAnd_EQ) { - ParsedMatchExpression a1B2("{a: 1, b: 2}"); - ParsedMatchExpression a1B7("{a: 1, b: 7}"); - ParsedMatchExpression a1("{a: 1}"); - ParsedMatchExpression b2("{b: 2}"); + ParsedMatchExpressionForTest a1B2("{a: 1, b: 2}"); + ParsedMatchExpressionForTest a1B7("{a: 1, b: 7}"); + ParsedMatchExpressionForTest a1("{a: 1}"); + ParsedMatchExpressionForTest b2("{b: 2}"); ASSERT_TRUE(expression::isSubsetOf(a1B2.get(), a1B2.get())); ASSERT_FALSE(expression::isSubsetOf(a1B2.get(), a1B7.get())); @@ -188,74 +161,74 @@ TEST(ExpressionAlgoIsSubsetOf, CompareAnd_EQ) { } TEST(ExpressionAlgoIsSubsetOf, CompareAnd_GT) { - ParsedMatchExpression filter("{a: {$gt: 5}, b: {$gt: 6}}"); - ParsedMatchExpression query("{a: {$gt: 5}, b: {$gt: 6}, c: {$gt: 7}}"); + 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) { - ParsedMatchExpression filter("{a: {$gt: 5, $lt: 7}}"); - ParsedMatchExpression query("{a: {$gt: 5, $lt: 6}}"); + ParsedMatchExpressionForTest filter("{a: {$gt: 5, $lt: 7}}"); + ParsedMatchExpressionForTest query("{a: {$gt: 5, $lt: 6}}"); ASSERT_TRUE(expression::isSubsetOf(query.get(), filter.get())); ASSERT_FALSE(expression::isSubsetOf(filter.get(), query.get())); } TEST(ExpressionAlgoIsSubsetOf, CompareOr_LT) { - 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}]}"); + 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}]}"); ASSERT_TRUE(expression::isSubsetOf(eq2OrEq3.get(), lt5.get())); ASSERT_FALSE(expression::isSubsetOf(eq4OrEq5.get(), lt5.get())); ASSERT_FALSE(expression::isSubsetOf(eq4OrEq6.get(), lt5.get())); - ParsedMatchExpression lt4OrLt5("{$or: [{a: {$lt: 4}}, {a: {$lt: 5}}]}"); + ParsedMatchExpressionForTest lt4OrLt5("{$or: [{a: {$lt: 4}}, {a: {$lt: 5}}]}"); ASSERT_TRUE(expression::isSubsetOf(lt4OrLt5.get(), lt5.get())); ASSERT_TRUE(expression::isSubsetOf(lt5.get(), lt4OrLt5.get())); - ParsedMatchExpression lt7OrLt8("{$or: [{a: {$lt: 7}}, {a: {$lt: 8}}]}"); + ParsedMatchExpressionForTest 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) { - 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}]}"); + 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}]}"); ASSERT_FALSE(expression::isSubsetOf(eq4OrEq6.get(), gte5.get())); ASSERT_TRUE(expression::isSubsetOf(eq5OrEq6.get(), gte5.get())); ASSERT_TRUE(expression::isSubsetOf(eq7OrEq8.get(), gte5.get())); - ParsedMatchExpression gte5OrGte6("{$or: [{a: {$gte: 5}}, {a: {$gte: 6}}]}"); + ParsedMatchExpressionForTest gte5OrGte6("{$or: [{a: {$gte: 5}}, {a: {$gte: 6}}]}"); ASSERT_TRUE(expression::isSubsetOf(gte5OrGte6.get(), gte5.get())); ASSERT_TRUE(expression::isSubsetOf(gte5.get(), gte5OrGte6.get())); - ParsedMatchExpression gte3OrGte4("{$or: [{a: {$gte: 3}}, {a: {$gte: 4}}]}"); + ParsedMatchExpressionForTest 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) { - ParsedMatchExpression number("{x: {$gt: 1}}"); - ParsedMatchExpression string("{x: {$gt: 'a'}}"); + ParsedMatchExpressionForTest number("{x: {$gt: 1}}"); + ParsedMatchExpressionForTest string("{x: {$gt: 'a'}}"); ASSERT_FALSE(expression::isSubsetOf(number.get(), string.get())); ASSERT_FALSE(expression::isSubsetOf(string.get(), number.get())); } TEST(ExpressionAlgoIsSubsetOf, DifferentNumberTypes) { - ParsedMatchExpression numberDouble("{x: 5.0}"); - ParsedMatchExpression numberInt("{x: NumberInt(5)}"); - ParsedMatchExpression numberLong("{x: NumberLong(5)}"); + ParsedMatchExpressionForTest numberDouble("{x: 5.0}"); + ParsedMatchExpressionForTest numberInt("{x: NumberInt(5)}"); + ParsedMatchExpressionForTest numberLong("{x: NumberLong(5)}"); ASSERT_TRUE(expression::isSubsetOf(numberDouble.get(), numberInt.get())); ASSERT_TRUE(expression::isSubsetOf(numberDouble.get(), numberLong.get())); @@ -266,15 +239,15 @@ TEST(ExpressionAlgoIsSubsetOf, DifferentNumberTypes) { } TEST(ExpressionAlgoIsSubsetOf, PointInUnboundedRange) { - ParsedMatchExpression a4("{a: 4}"); - ParsedMatchExpression a5("{a: 5}"); - ParsedMatchExpression a6("{a: 6}"); - ParsedMatchExpression b5("{b: 5}"); + ParsedMatchExpressionForTest a4("{a: 4}"); + ParsedMatchExpressionForTest a5("{a: 5}"); + ParsedMatchExpressionForTest a6("{a: 6}"); + ParsedMatchExpressionForTest b5("{b: 5}"); - ParsedMatchExpression lt5("{a: {$lt: 5}}"); - ParsedMatchExpression lte5("{a: {$lte: 5}}"); - ParsedMatchExpression gte5("{a: {$gte: 5}}"); - ParsedMatchExpression gt5("{a: {$gt: 5}}"); + ParsedMatchExpressionForTest lt5("{a: {$lt: 5}}"); + ParsedMatchExpressionForTest lte5("{a: {$lte: 5}}"); + ParsedMatchExpressionForTest gte5("{a: {$gte: 5}}"); + ParsedMatchExpressionForTest gt5("{a: {$gt: 5}}"); ASSERT_TRUE(expression::isSubsetOf(a4.get(), lte5.get())); ASSERT_TRUE(expression::isSubsetOf(a5.get(), lte5.get())); @@ -306,26 +279,26 @@ TEST(ExpressionAlgoIsSubsetOf, PointInUnboundedRange) { } TEST(ExpressionAlgoIsSubsetOf, PointInBoundedRange) { - ParsedMatchExpression filter("{a: {$gt: 5, $lt: 10}}"); - ParsedMatchExpression query("{a: 6}"); + ParsedMatchExpressionForTest filter("{a: {$gt: 5, $lt: 10}}"); + ParsedMatchExpressionForTest query("{a: 6}"); ASSERT_TRUE(expression::isSubsetOf(query.get(), filter.get())); ASSERT_FALSE(expression::isSubsetOf(filter.get(), query.get())); } TEST(ExpressionAlgoIsSubsetOf, PointInBoundedRange_FakeAnd) { - ParsedMatchExpression filter("{a: {$gt: 5, $lt: 10}}"); - ParsedMatchExpression query("{$and: [{a: 6}, {a: 6}]}"); + ParsedMatchExpressionForTest filter("{a: {$gt: 5, $lt: 10}}"); + ParsedMatchExpressionForTest 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) { - 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]}}"); + 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]}}"); ASSERT_TRUE(expression::isSubsetOf(queryAllInside.get(), filter.get())); ASSERT_FALSE(expression::isSubsetOf(queryStraddleLower.get(), filter.get())); @@ -333,18 +306,18 @@ TEST(ExpressionAlgoIsSubsetOf, MultiplePointsInBoundedRange) { } TEST(ExpressionAlgoIsSubsetOf, PointInCompoundRange) { - ParsedMatchExpression filter("{a: {$gt: 5}, b: {$gt: 6}, c: {$gt: 7}}"); - ParsedMatchExpression query("{a: 10, b: 10, c: 10}"); + ParsedMatchExpressionForTest filter("{a: {$gt: 5}, b: {$gt: 6}, c: {$gt: 7}}"); + ParsedMatchExpressionForTest 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) { - ParsedMatchExpression lte4("{x: {$lte: 4}}"); - ParsedMatchExpression lt5("{x: {$lt: 5}}"); - ParsedMatchExpression lte5("{x: {$lte: 5}}"); - ParsedMatchExpression lt6("{x: {$lt: 6}}"); + ParsedMatchExpressionForTest lte4("{x: {$lte: 4}}"); + ParsedMatchExpressionForTest lt5("{x: {$lt: 5}}"); + ParsedMatchExpressionForTest lte5("{x: {$lte: 5}}"); + ParsedMatchExpressionForTest lt6("{x: {$lt: 6}}"); ASSERT_TRUE(expression::isSubsetOf(lte4.get(), lte5.get())); ASSERT_TRUE(expression::isSubsetOf(lt5.get(), lte5.get())); @@ -358,10 +331,10 @@ TEST(ExpressionAlgoIsSubsetOf, Compare_LT_LTE) { } TEST(ExpressionAlgoIsSubsetOf, Compare_GT_GTE) { - ParsedMatchExpression gte6("{x: {$gte: 6}}"); - ParsedMatchExpression gt5("{x: {$gt: 5}}"); - ParsedMatchExpression gte5("{x: {$gte: 5}}"); - ParsedMatchExpression gt4("{x: {$gt: 4}}"); + ParsedMatchExpressionForTest gte6("{x: {$gte: 6}}"); + ParsedMatchExpressionForTest gt5("{x: {$gt: 5}}"); + ParsedMatchExpressionForTest gte5("{x: {$gte: 5}}"); + ParsedMatchExpressionForTest gt4("{x: {$gt: 4}}"); ASSERT_TRUE(expression::isSubsetOf(gte6.get(), gte5.get())); ASSERT_TRUE(expression::isSubsetOf(gt5.get(), gte5.get())); @@ -375,18 +348,19 @@ TEST(ExpressionAlgoIsSubsetOf, Compare_GT_GTE) { } TEST(ExpressionAlgoIsSubsetOf, BoundedRangeInUnboundedRange) { - ParsedMatchExpression filter("{a: {$gt: 1}}"); - ParsedMatchExpression query("{a: {$gt: 5, $lt: 10}}"); + ParsedMatchExpressionForTest filter("{a: {$gt: 1}}"); + ParsedMatchExpressionForTest 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) { - 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}}]}"); + 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}}]}"); ASSERT_FALSE(expression::isSubsetOf(negative.get(), filter.get())); ASSERT_TRUE(expression::isSubsetOf(unbounded.get(), filter.get())); @@ -394,10 +368,10 @@ TEST(ExpressionAlgoIsSubsetOf, MultipleRangesInUnboundedRange) { } TEST(ExpressionAlgoIsSubsetOf, MultipleFields) { - 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}]}"); + 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}]}"); ASSERT_FALSE(expression::isSubsetOf(onlyA.get(), filter.get())); ASSERT_FALSE(expression::isSubsetOf(onlyB.get(), filter.get())); @@ -405,18 +379,18 @@ TEST(ExpressionAlgoIsSubsetOf, MultipleFields) { } TEST(ExpressionAlgoIsSubsetOf, Compare_LT_In) { - ParsedMatchExpression lt("{a: {$lt: 5}}"); + ParsedMatchExpressionForTest lt("{a: {$lt: 5}}"); - ParsedMatchExpression inLt("{a: {$in: [4.9]}}"); - ParsedMatchExpression inEq("{a: {$in: [5]}}"); - ParsedMatchExpression inGt("{a: {$in: [5.1]}}"); - ParsedMatchExpression inNull("{a: {$in: [null]}}"); + ParsedMatchExpressionForTest inLt("{a: {$in: [4.9]}}"); + ParsedMatchExpressionForTest inEq("{a: {$in: [5]}}"); + ParsedMatchExpressionForTest inGt("{a: {$in: [5.1]}}"); + ParsedMatchExpressionForTest inNull("{a: {$in: [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]}}"); + 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]}}"); ASSERT_TRUE(expression::isSubsetOf(inLt.get(), lt.get())); ASSERT_FALSE(expression::isSubsetOf(inEq.get(), lt.get())); @@ -433,18 +407,18 @@ TEST(ExpressionAlgoIsSubsetOf, Compare_LT_In) { } TEST(ExpressionAlgoIsSubsetOf, Compare_LTE_In) { - ParsedMatchExpression lte("{a: {$lte: 5}}"); + ParsedMatchExpressionForTest lte("{a: {$lte: 5}}"); - ParsedMatchExpression inLt("{a: {$in: [4.9]}}"); - ParsedMatchExpression inEq("{a: {$in: [5]}}"); - ParsedMatchExpression inGt("{a: {$in: [5.1]}}"); - ParsedMatchExpression inNull("{a: {$in: [null]}}"); + ParsedMatchExpressionForTest inLt("{a: {$in: [4.9]}}"); + ParsedMatchExpressionForTest inEq("{a: {$in: [5]}}"); + ParsedMatchExpressionForTest inGt("{a: {$in: [5.1]}}"); + ParsedMatchExpressionForTest inNull("{a: {$in: [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]}}"); + 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]}}"); ASSERT_TRUE(expression::isSubsetOf(inLt.get(), lte.get())); ASSERT_TRUE(expression::isSubsetOf(inEq.get(), lte.get())); @@ -461,16 +435,16 @@ TEST(ExpressionAlgoIsSubsetOf, Compare_LTE_In) { } TEST(ExpressionAlgoIsSubsetOf, Compare_EQ_In) { - ParsedMatchExpression eq("{a: 5}"); + ParsedMatchExpressionForTest eq("{a: 5}"); - ParsedMatchExpression inLt("{a: {$in: [4.9]}}"); - ParsedMatchExpression inEq("{a: {$in: [5]}}"); - ParsedMatchExpression inGt("{a: {$in: [5.1]}}"); - ParsedMatchExpression inNull("{a: {$in: [null]}}"); + ParsedMatchExpressionForTest inLt("{a: {$in: [4.9]}}"); + ParsedMatchExpressionForTest inEq("{a: {$in: [5]}}"); + ParsedMatchExpressionForTest inGt("{a: {$in: [5.1]}}"); + ParsedMatchExpressionForTest inNull("{a: {$in: [null]}}"); - ParsedMatchExpression inAllEq("{a: {$in: [5, 5.0]}}"); - ParsedMatchExpression inStraddle("{a: {$in: [4, 6]}}"); - ParsedMatchExpression inEqAndNull("{a: {$in: [5, null]}}"); + ParsedMatchExpressionForTest inAllEq("{a: {$in: [5, 5.0]}}"); + ParsedMatchExpressionForTest inStraddle("{a: {$in: [4, 6]}}"); + ParsedMatchExpressionForTest inEqAndNull("{a: {$in: [5, null]}}"); ASSERT_FALSE(expression::isSubsetOf(inLt.get(), eq.get())); ASSERT_TRUE(expression::isSubsetOf(inEq.get(), eq.get())); @@ -485,18 +459,18 @@ TEST(ExpressionAlgoIsSubsetOf, Compare_EQ_In) { } TEST(ExpressionAlgoIsSubsetOf, Compare_GT_In) { - ParsedMatchExpression gt("{a: {$gt: 5}}"); + ParsedMatchExpressionForTest gt("{a: {$gt: 5}}"); - ParsedMatchExpression inLt("{a: {$in: [4.9]}}"); - ParsedMatchExpression inEq("{a: {$in: [5]}}"); - ParsedMatchExpression inGt("{a: {$in: [5.1]}}"); - ParsedMatchExpression inNull("{a: {$in: [null]}}"); + ParsedMatchExpressionForTest inLt("{a: {$in: [4.9]}}"); + ParsedMatchExpressionForTest inEq("{a: {$in: [5]}}"); + ParsedMatchExpressionForTest inGt("{a: {$in: [5.1]}}"); + ParsedMatchExpressionForTest inNull("{a: {$in: [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]}}"); + 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]}}"); ASSERT_FALSE(expression::isSubsetOf(inLt.get(), gt.get())); ASSERT_FALSE(expression::isSubsetOf(inEq.get(), gt.get())); @@ -513,18 +487,18 @@ TEST(ExpressionAlgoIsSubsetOf, Compare_GT_In) { } TEST(ExpressionAlgoIsSubsetOf, Compare_GTE_In) { - ParsedMatchExpression gte("{a: {$gte: 5}}"); + ParsedMatchExpressionForTest gte("{a: {$gte: 5}}"); - ParsedMatchExpression inLt("{a: {$in: [4.9]}}"); - ParsedMatchExpression inEq("{a: {$in: [5]}}"); - ParsedMatchExpression inGt("{a: {$in: [5.1]}}"); - ParsedMatchExpression inNull("{a: {$in: [null]}}"); + ParsedMatchExpressionForTest inLt("{a: {$in: [4.9]}}"); + ParsedMatchExpressionForTest inEq("{a: {$in: [5]}}"); + ParsedMatchExpressionForTest inGt("{a: {$in: [5.1]}}"); + ParsedMatchExpressionForTest inNull("{a: {$in: [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]}}"); + 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]}}"); ASSERT_FALSE(expression::isSubsetOf(inLt.get(), gte.get())); ASSERT_TRUE(expression::isSubsetOf(inEq.get(), gte.get())); @@ -541,12 +515,12 @@ TEST(ExpressionAlgoIsSubsetOf, Compare_GTE_In) { } TEST(ExpressionAlgoIsSubsetOf, RegexAndIn) { - 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]}}"); + 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]}}"); ASSERT_FALSE(expression::isSubsetOf(inRegexAOrEq1.get(), eq1.get())); ASSERT_FALSE(expression::isSubsetOf(inRegexA.get(), eqA.get())); @@ -558,10 +532,10 @@ TEST(ExpressionAlgoIsSubsetOf, RegexAndIn) { } TEST(ExpressionAlgoIsSubsetOf, Exists) { - 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}"); + 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}"); ASSERT_TRUE(expression::isSubsetOf(aExists.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(aExists.get(), bExists.get())); @@ -576,10 +550,10 @@ TEST(ExpressionAlgoIsSubsetOf, Exists) { } TEST(ExpressionAlgoIsSubsetOf, Compare_Exists) { - ParsedMatchExpression exists("{a: {$exists: true}}"); - ParsedMatchExpression eq("{a: 1}"); - ParsedMatchExpression gt("{a: {$gt: 4}}"); - ParsedMatchExpression lte("{a: {$lte: 7}}"); + ParsedMatchExpressionForTest exists("{a: {$exists: true}}"); + ParsedMatchExpressionForTest eq("{a: 1}"); + ParsedMatchExpressionForTest gt("{a: {$gt: 4}}"); + ParsedMatchExpressionForTest lte("{a: {$lte: 7}}"); ASSERT_TRUE(expression::isSubsetOf(eq.get(), exists.get())); ASSERT_TRUE(expression::isSubsetOf(gt.get(), exists.get())); @@ -591,9 +565,9 @@ TEST(ExpressionAlgoIsSubsetOf, Compare_Exists) { } TEST(ExpressionAlgoIsSubsetOf, Type) { - ParsedMatchExpression aType1("{a: {$type: 1}}"); - ParsedMatchExpression aType2("{a: {$type: 2}}"); - ParsedMatchExpression bType2("{b: {$type: 2}}"); + ParsedMatchExpressionForTest aType1("{a: {$type: 1}}"); + ParsedMatchExpressionForTest aType2("{a: {$type: 2}}"); + ParsedMatchExpressionForTest bType2("{b: {$type: 2}}"); ASSERT_FALSE(expression::isSubsetOf(aType1.get(), aType2.get())); ASSERT_FALSE(expression::isSubsetOf(aType2.get(), aType1.get())); @@ -603,9 +577,9 @@ TEST(ExpressionAlgoIsSubsetOf, Type) { } TEST(ExpressionAlgoIsSubsetOf, TypeAndExists) { - ParsedMatchExpression aExists("{a: {$exists: true}}"); - ParsedMatchExpression aType2("{a: {$type: 2}}"); - ParsedMatchExpression bType2("{b: {$type: 2}}"); + ParsedMatchExpressionForTest aExists("{a: {$exists: true}}"); + ParsedMatchExpressionForTest aType2("{a: {$type: 2}}"); + ParsedMatchExpressionForTest bType2("{b: {$type: 2}}"); ASSERT_TRUE(expression::isSubsetOf(aType2.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(aExists.get(), aType2.get())); @@ -613,10 +587,10 @@ TEST(ExpressionAlgoIsSubsetOf, TypeAndExists) { } TEST(ExpressionAlgoIsSubsetOf, AllAndExists) { - 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']}}"); + 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']}}"); ASSERT_TRUE(expression::isSubsetOf(aAll.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(bAll.get(), aExists.get())); @@ -624,10 +598,10 @@ TEST(ExpressionAlgoIsSubsetOf, AllAndExists) { } TEST(ExpressionAlgoIsSubsetOf, ElemMatchAndExists_Value) { - 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}}}"); + 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}}}"); ASSERT_TRUE(expression::isSubsetOf(aElemMatch.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(aExists.get(), aElemMatch.get())); @@ -636,10 +610,10 @@ TEST(ExpressionAlgoIsSubsetOf, ElemMatchAndExists_Value) { } TEST(ExpressionAlgoIsSubsetOf, ElemMatchAndExists_Object) { - 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}}}"); + 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}}}"); ASSERT_TRUE(expression::isSubsetOf(aElemMatch.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(aExists.get(), aElemMatch.get())); @@ -648,11 +622,11 @@ TEST(ExpressionAlgoIsSubsetOf, ElemMatchAndExists_Object) { } TEST(ExpressionAlgoIsSubsetOf, SizeAndExists) { - 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}}"); + 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}}"); ASSERT_TRUE(expression::isSubsetOf(aSize0.get(), aExists.get())); ASSERT_TRUE(expression::isSubsetOf(aSize1.get(), aExists.get())); @@ -662,28 +636,28 @@ TEST(ExpressionAlgoIsSubsetOf, SizeAndExists) { } TEST(ExpressionAlgoIsSubsetOf, ModAndExists) { - ParsedMatchExpression aExists("{a: {$exists: true}}"); - ParsedMatchExpression aMod5("{a: {$mod: [5, 0]}}"); - ParsedMatchExpression bMod5("{b: {$mod: [5, 0]}}"); + ParsedMatchExpressionForTest aExists("{a: {$exists: true}}"); + ParsedMatchExpressionForTest aMod5("{a: {$mod: [5, 0]}}"); + ParsedMatchExpressionForTest bMod5("{b: {$mod: [5, 0]}}"); ASSERT_TRUE(expression::isSubsetOf(aMod5.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(bMod5.get(), aExists.get())); } TEST(ExpressionAlgoIsSubsetOf, RegexAndExists) { - ParsedMatchExpression aExists("{a: {$exists: true}}"); - ParsedMatchExpression aRegex("{a: {$regex: 'pattern'}}"); - ParsedMatchExpression bRegex("{b: {$regex: 'pattern'}}"); + ParsedMatchExpressionForTest aExists("{a: {$exists: true}}"); + ParsedMatchExpressionForTest aRegex("{a: {$regex: 'pattern'}}"); + ParsedMatchExpressionForTest bRegex("{b: {$regex: 'pattern'}}"); ASSERT_TRUE(expression::isSubsetOf(aRegex.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(bRegex.get(), aExists.get())); } TEST(ExpressionAlgoIsSubsetOf, InAndExists) { - 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]}}"); + 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]}}"); ASSERT_TRUE(expression::isSubsetOf(aIn.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(bIn.get(), aExists.get())); @@ -695,10 +669,10 @@ TEST(ExpressionAlgoIsSubsetOf, InAndExists) { } TEST(ExpressionAlgoIsSubsetOf, NinAndExists) { - 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]}}"); + 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]}}"); ASSERT_FALSE(expression::isSubsetOf(aNin.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(bNin.get(), aExists.get())); @@ -706,10 +680,10 @@ TEST(ExpressionAlgoIsSubsetOf, NinAndExists) { } TEST(ExpressionAlgoIsSubsetOf, Compare_Exists_NE) { - ParsedMatchExpression aExists("{a: {$exists: true}}"); - ParsedMatchExpression aNotEqual1("{a: {$ne: 1}}"); - ParsedMatchExpression bNotEqual1("{b: {$ne: 1}}"); - ParsedMatchExpression aNotEqualNull("{a: {$ne: null}}"); + ParsedMatchExpressionForTest aExists("{a: {$exists: true}}"); + ParsedMatchExpressionForTest aNotEqual1("{a: {$ne: 1}}"); + ParsedMatchExpressionForTest bNotEqual1("{b: {$ne: 1}}"); + ParsedMatchExpressionForTest aNotEqualNull("{a: {$ne: null}}"); ASSERT_FALSE(expression::isSubsetOf(aNotEqual1.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(bNotEqual1.get(), aExists.get())); @@ -718,13 +692,13 @@ TEST(ExpressionAlgoIsSubsetOf, Compare_Exists_NE) { TEST(ExpressionAlgoIsSubsetOf, CollationAwareStringComparison) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - ParsedMatchExpression lhs("{a: {$gt: 'abc'}}", &collator); - ParsedMatchExpression rhs("{a: {$gt: 'cba'}}", &collator); + ParsedMatchExpressionForTest lhs("{a: {$gt: 'abc'}}", &collator); + ParsedMatchExpressionForTest rhs("{a: {$gt: 'cba'}}", &collator); ASSERT_TRUE(expression::isSubsetOf(lhs.get(), rhs.get())); - ParsedMatchExpression lhsLT("{a: {$lt: 'abc'}}", &collator); - ParsedMatchExpression rhsLT("{a: {$lt: 'cba'}}", &collator); + ParsedMatchExpressionForTest lhsLT("{a: {$lt: 'abc'}}", &collator); + ParsedMatchExpressionForTest rhsLT("{a: {$lt: 'cba'}}", &collator); ASSERT_FALSE(expression::isSubsetOf(lhsLT.get(), rhsLT.get())); } @@ -732,27 +706,27 @@ TEST(ExpressionAlgoIsSubsetOf, CollationAwareStringComparison) { TEST(ExpressionAlgoIsSubsetOf, NonMatchingCollationsStringComparison) { CollatorInterfaceMock collatorAlwaysEqual(CollatorInterfaceMock::MockType::kAlwaysEqual); CollatorInterfaceMock collatorReverseString(CollatorInterfaceMock::MockType::kReverseString); - ParsedMatchExpression lhs("{a: {$gt: 'abc'}}", &collatorAlwaysEqual); - ParsedMatchExpression rhs("{a: {$gt: 'cba'}}", &collatorReverseString); + ParsedMatchExpressionForTest lhs("{a: {$gt: 'abc'}}", &collatorAlwaysEqual); + ParsedMatchExpressionForTest rhs("{a: {$gt: 'cba'}}", &collatorReverseString); ASSERT_FALSE(expression::isSubsetOf(lhs.get(), rhs.get())); - ParsedMatchExpression lhsLT("{a: {$lt: 'abc'}}", &collatorAlwaysEqual); - ParsedMatchExpression rhsLT("{a: {$lt: 'cba'}}", &collatorReverseString); + ParsedMatchExpressionForTest lhsLT("{a: {$lt: 'abc'}}", &collatorAlwaysEqual); + ParsedMatchExpressionForTest rhsLT("{a: {$lt: 'cba'}}", &collatorReverseString); ASSERT_FALSE(expression::isSubsetOf(lhsLT.get(), rhsLT.get())); } TEST(ExpressionAlgoIsSubsetOf, CollationAwareStringComparisonIn) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - ParsedMatchExpression lhsAllGTcba("{a: {$in: ['abc', 'cbc']}}", &collator); - ParsedMatchExpression lhsSomeGTcba("{a: {$in: ['abc', 'aba']}}", &collator); - ParsedMatchExpression rhs("{a: {$gt: 'cba'}}", &collator); + ParsedMatchExpressionForTest lhsAllGTcba("{a: {$in: ['abc', 'cbc']}}", &collator); + ParsedMatchExpressionForTest lhsSomeGTcba("{a: {$in: ['abc', 'aba']}}", &collator); + ParsedMatchExpressionForTest rhs("{a: {$gt: 'cba'}}", &collator); ASSERT_TRUE(expression::isSubsetOf(lhsAllGTcba.get(), rhs.get())); ASSERT_FALSE(expression::isSubsetOf(lhsSomeGTcba.get(), rhs.get())); - ParsedMatchExpression rhsLT("{a: {$lt: 'cba'}}", &collator); + ParsedMatchExpressionForTest rhsLT("{a: {$lt: 'cba'}}", &collator); ASSERT_FALSE(expression::isSubsetOf(lhsAllGTcba.get(), rhsLT.get())); ASSERT_FALSE(expression::isSubsetOf(lhsSomeGTcba.get(), rhsLT.get())); @@ -762,8 +736,8 @@ TEST(ExpressionAlgoIsSubsetOf, CollationAwareStringComparisonIn) { TEST(ExpressionAlgoIsSubsetOf, NonMatchingCollationsNoStringComparisonLHS) { CollatorInterfaceMock collatorAlwaysEqual(CollatorInterfaceMock::MockType::kAlwaysEqual); CollatorInterfaceMock collatorReverseString(CollatorInterfaceMock::MockType::kReverseString); - ParsedMatchExpression lhs("{a: {b: 1}}", &collatorAlwaysEqual); - ParsedMatchExpression rhs("{a: {$lt: {b: 'abc'}}}", &collatorReverseString); + ParsedMatchExpressionForTest lhs("{a: {b: 1}}", &collatorAlwaysEqual); + ParsedMatchExpressionForTest rhs("{a: {$lt: {b: 'abc'}}}", &collatorReverseString); ASSERT_FALSE(expression::isSubsetOf(lhs.get(), rhs.get())); } @@ -771,72 +745,75 @@ TEST(ExpressionAlgoIsSubsetOf, NonMatchingCollationsNoStringComparisonLHS) { TEST(ExpressionAlgoIsSubsetOf, NonMatchingCollationsNoStringComparison) { CollatorInterfaceMock collatorAlwaysEqual(CollatorInterfaceMock::MockType::kAlwaysEqual); CollatorInterfaceMock collatorReverseString(CollatorInterfaceMock::MockType::kReverseString); - ParsedMatchExpression lhs("{a: 1}", &collatorAlwaysEqual); - ParsedMatchExpression rhs("{a: {$gt: 0}}", &collatorReverseString); + ParsedMatchExpressionForTest lhs("{a: 1}", &collatorAlwaysEqual); + ParsedMatchExpressionForTest rhs("{a: {$gt: 0}}", &collatorReverseString); ASSERT_TRUE(expression::isSubsetOf(lhs.get(), rhs.get())); } TEST(ExpressionAlgoIsSubsetOf, InternalExprEqIsSubsetOfNothing) { - ParsedMatchExpression exprEq("{a: {$_internalExprEq: 0}}"); - ParsedMatchExpression regularEq("{a: {$eq: 0}}"); + ParsedMatchExpressionForTest exprEq("{a: {$_internalExprEq: 0}}"); + ParsedMatchExpressionForTest regularEq("{a: {$eq: 0}}"); { - ParsedMatchExpression rhs("{a: {$gte: 0}}"); + ParsedMatchExpressionForTest rhs("{a: {$gte: 0}}"); ASSERT_FALSE(expression::isSubsetOf(exprEq.get(), rhs.get())); ASSERT_TRUE(expression::isSubsetOf(regularEq.get(), rhs.get())); } { - ParsedMatchExpression rhs("{a: {$lte: 0}}"); + ParsedMatchExpressionForTest rhs("{a: {$lte: 0}}"); ASSERT_FALSE(expression::isSubsetOf(exprEq.get(), rhs.get())); ASSERT_TRUE(expression::isSubsetOf(regularEq.get(), rhs.get())); } } TEST(ExpressionAlgoIsSubsetOf, IsSubsetOfRHSAndWithinOr) { - ParsedMatchExpression rhs("{$or: [{a: 3}, {$and: [{a: 5}, {b: 5}]}]}"); + ParsedMatchExpressionForTest rhs("{$or: [{a: 3}, {$and: [{a: 5}, {b: 5}]}]}"); { - ParsedMatchExpression lhs("{a:5, b:5}"); + ParsedMatchExpressionForTest lhs("{a:5, b:5}"); ASSERT_TRUE(expression::isSubsetOf(lhs.get(), rhs.get())); } } TEST(ExpressionAlgoIsSubsetOf, IsSubsetOfComplexRHSExpression) { - ParsedMatchExpression complex("{$or: [{z: 1}, {$and: [{x: 1}, {$or: [{y: 1}, {y: 2}]}]}]}"); + ParsedMatchExpressionForTest complex( + "{$or: [{z: 1}, {$and: [{x: 1}, {$or: [{y: 1}, {y: 2}]}]}]}"); { - ParsedMatchExpression lhs("{z: 1}"); + ParsedMatchExpressionForTest lhs("{z: 1}"); ASSERT_TRUE(expression::isSubsetOf(lhs.get(), complex.get())); } { - ParsedMatchExpression lhs("{z: 1, x: 1, y:2}"); + ParsedMatchExpressionForTest lhs("{z: 1, x: 1, y:2}"); ASSERT_TRUE(expression::isSubsetOf(lhs.get(), complex.get())); } { - ParsedMatchExpression lhs("{$or: [{z: 1}, {$and: [{x: 1}, {$or: [{y: 1}, {y: 2}]}]}]}"); + ParsedMatchExpressionForTest lhs( + "{$or: [{z: 1}, {$and: [{x: 1}, {$or: [{y: 1}, {y: 2}]}]}]}"); ASSERT_TRUE(expression::isSubsetOf(lhs.get(), complex.get())); } { - ParsedMatchExpression lhs("{$or: [{z: 2}, {$and: [{x: 2}, {$or: [{y: 3}, {y: 4}]}]}]}"); + ParsedMatchExpressionForTest lhs( + "{$or: [{z: 2}, {$and: [{x: 2}, {$or: [{y: 3}, {y: 4}]}]}]}"); ASSERT_FALSE(expression::isSubsetOf(lhs.get(), complex.get())); } { - ParsedMatchExpression lhs("{z: 1, y:2}"); + ParsedMatchExpressionForTest lhs("{z: 1, y:2}"); ASSERT_TRUE(expression::isSubsetOf(lhs.get(), complex.get())); } { - ParsedMatchExpression lhs("{z: 2, y: 1}"); + ParsedMatchExpressionForTest lhs("{z: 2, y: 1}"); ASSERT_FALSE(expression::isSubsetOf(lhs.get(), complex.get())); } { - ParsedMatchExpression lhs("{x: 1, y: 3}"); + ParsedMatchExpressionForTest lhs("{x: 1, y: 3}"); ASSERT_FALSE(expression::isSubsetOf(lhs.get(), complex.get())); } } @@ -1010,15 +987,11 @@ 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(firstBob.obj(), fromjson("{a: {$eq: 1}}")); - ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{b: {$eq: 1}}")); + ASSERT_BSONOBJ_EQ(splitExpr.first->serialize(), fromjson("{a: {$eq: 1}}")); + ASSERT_BSONOBJ_EQ(splitExpr.second->serialize(), fromjson("{b: {$eq: 1}}")); } TEST(SplitMatchExpression, NorWithIndependentChildrenIsSplittable) { @@ -1032,15 +1005,11 @@ 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(firstBob.obj(), fromjson("{$nor: [{a: {$eq: 1}}]}")); - ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{$nor: [{b: {$eq: 1}}]}")); + ASSERT_BSONOBJ_EQ(splitExpr.first->serialize(), fromjson("{$nor: [{a: {$eq: 1}}]}")); + ASSERT_BSONOBJ_EQ(splitExpr.second->serialize(), fromjson("{$nor: [{b: {$eq: 1}}]}")); } TEST(SplitMatchExpression, NotWithIndependentChildIsSplittable) { @@ -1054,10 +1023,8 @@ 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(firstBob.obj(), fromjson("{x: {$not: {$gt: 4}}}")); + ASSERT_BSONOBJ_EQ(splitExpr.first->serialize(), fromjson("{x: {$not: {$gt: 4}}}")); ASSERT_FALSE(splitExpr.second); } @@ -1072,11 +1039,10 @@ 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(bob.obj(), fromjson("{$or: [{a: {$eq: 1}}, {b: {$eq: 1}}]}")); + ASSERT_BSONOBJ_EQ(splitExpr.second->serialize(), + fromjson("{$or: [{a: {$eq: 1}}, {b: {$eq: 1}}]}")); } TEST(SplitMatchExpression, ComplexMatchExpressionSplitsCorrectly) { @@ -1093,15 +1059,12 @@ 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(firstBob.obj(), fromjson("{$or: [{'a.b': {$eq: 3}}, {'a.b.c': {$eq: 4}}]}")); - ASSERT_BSONOBJ_EQ(secondBob.obj(), + ASSERT_BSONOBJ_EQ(splitExpr.first->serialize(), + fromjson("{$or: [{'a.b': {$eq: 3}}, {'a.b.c': {$eq: 4}}]}")); + ASSERT_BSONOBJ_EQ(splitExpr.second->serialize(), fromjson("{$and: [{x: {$not: {$size: 2}}}, {$nor: [{x: {$gt: 4}}, {$and: " "[{x: {$not: {$eq: 1}}}, {y: {$eq: 3}}]}]}]}")); } @@ -1118,15 +1081,12 @@ 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(firstBob.obj(), fromjson("{'a.c': {$eq: 1}}")); - ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{$and: [{a: {$eq: 1}}, {'a.b': {$eq: 1}}]}")); + 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}}]}")); } TEST(SplitMatchExpression, ShouldMoveIndependentLeafPredicateAcrossRename) { @@ -1140,9 +1100,7 @@ TEST(SplitMatchExpression, ShouldMoveIndependentLeafPredicateAcrossRename) { expression::splitMatchExpressionBy(std::move(matcher.getValue()), {}, renames); ASSERT_TRUE(splitExpr.first.get()); - BSONObjBuilder firstBob; - splitExpr.first->serialize(&firstBob, true); - ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{b: {$eq: 1}}")); + ASSERT_BSONOBJ_EQ(splitExpr.first->serialize(), fromjson("{b: {$eq: 1}}")); ASSERT_FALSE(splitExpr.second.get()); } @@ -1158,9 +1116,8 @@ TEST(SplitMatchExpression, ShouldMoveIndependentAndPredicateAcrossRename) { expression::splitMatchExpressionBy(std::move(matcher.getValue()), {}, renames); ASSERT_TRUE(splitExpr.first.get()); - BSONObjBuilder firstBob; - splitExpr.first->serialize(&firstBob, true); - ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{$and: [{c: {$eq: 1}}, {b: {$eq: 2}}]}")); + ASSERT_BSONOBJ_EQ(splitExpr.first->serialize(), + fromjson("{$and: [{c: {$eq: 1}}, {b: {$eq: 2}}]}")); ASSERT_FALSE(splitExpr.second.get()); } @@ -1176,14 +1133,10 @@ TEST(SplitMatchExpression, ShouldSplitPartiallyDependentAndPredicateAcrossRename expression::splitMatchExpressionBy(std::move(matcher.getValue()), {"b"}, renames); ASSERT_TRUE(splitExpr.first.get()); - BSONObjBuilder firstBob; - splitExpr.first->serialize(&firstBob, true); - ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{c: {$eq: 1}}")); + ASSERT_BSONOBJ_EQ(splitExpr.first->serialize(), fromjson("{c: {$eq: 1}}")); ASSERT_TRUE(splitExpr.second.get()); - BSONObjBuilder secondBob; - splitExpr.second->serialize(&secondBob, true); - ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{b: {$eq: 2}}")); + ASSERT_BSONOBJ_EQ(splitExpr.second->serialize(), fromjson("{b: {$eq: 2}}")); } TEST(SplitMatchExpression, ShouldSplitPartiallyDependentComplexPredicateMultipleRenames) { @@ -1197,14 +1150,11 @@ TEST(SplitMatchExpression, ShouldSplitPartiallyDependentComplexPredicateMultiple expression::splitMatchExpressionBy(std::move(matcher.getValue()), {"a"}, renames); ASSERT_TRUE(splitExpr.first.get()); - BSONObjBuilder firstBob; - splitExpr.first->serialize(&firstBob, true); - ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{$or: [{d: {$eq: 2}}, {e: {$eq: 3}}]}")); + ASSERT_BSONOBJ_EQ(splitExpr.first->serialize(), + fromjson("{$or: [{d: {$eq: 2}}, {e: {$eq: 3}}]}")); ASSERT_TRUE(splitExpr.second.get()); - BSONObjBuilder secondBob; - splitExpr.second->serialize(&secondBob, true); - ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{a: {$eq: 1}}")); + ASSERT_BSONOBJ_EQ(splitExpr.second->serialize(), fromjson("{a: {$eq: 1}}")); } TEST(SplitMatchExpression, @@ -1219,14 +1169,11 @@ TEST(SplitMatchExpression, expression::splitMatchExpressionBy(std::move(matcher.getValue()), {"a"}, renames); ASSERT_TRUE(splitExpr.first.get()); - BSONObjBuilder firstBob; - splitExpr.first->serialize(&firstBob, true); - ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{$or: [{x: {$eq: 2}}, {y: {$eq: 3}}]}")); + ASSERT_BSONOBJ_EQ(splitExpr.first->serialize(), + fromjson("{$or: [{x: {$eq: 2}}, {y: {$eq: 3}}]}")); ASSERT_TRUE(splitExpr.second.get()); - BSONObjBuilder secondBob; - splitExpr.second->serialize(&secondBob, true); - ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{a: {$eq: 1}}")); + ASSERT_BSONOBJ_EQ(splitExpr.second->serialize(), fromjson("{a: {$eq: 1}}")); } TEST(SplitMatchExpression, ShouldNotMoveElemMatchObjectAcrossRename) { @@ -1242,9 +1189,7 @@ TEST(SplitMatchExpression, ShouldNotMoveElemMatchObjectAcrossRename) { ASSERT_FALSE(splitExpr.first.get()); ASSERT_TRUE(splitExpr.second.get()); - BSONObjBuilder secondBob; - splitExpr.second->serialize(&secondBob, true); - ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{a: {$elemMatch: {b: {$eq: 3}}}}")); + ASSERT_BSONOBJ_EQ(splitExpr.second->serialize(), fromjson("{a: {$elemMatch: {b: {$eq: 3}}}}")); } TEST(SplitMatchExpression, ShouldNotMoveElemMatchValueAcrossRename) { @@ -1260,9 +1205,7 @@ TEST(SplitMatchExpression, ShouldNotMoveElemMatchValueAcrossRename) { ASSERT_FALSE(splitExpr.first.get()); ASSERT_TRUE(splitExpr.second.get()); - BSONObjBuilder secondBob; - splitExpr.second->serialize(&secondBob, true); - ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{a: {$elemMatch: {$eq: 3}}}")); + ASSERT_BSONOBJ_EQ(splitExpr.second->serialize(), fromjson("{a: {$elemMatch: {$eq: 3}}}")); } TEST(SplitMatchExpression, ShouldMoveTypeAcrossRename) { @@ -1275,10 +1218,7 @@ TEST(SplitMatchExpression, ShouldMoveTypeAcrossRename) { std::pair<unique_ptr<MatchExpression>, unique_ptr<MatchExpression>> splitExpr = expression::splitMatchExpressionBy(std::move(matcher.getValue()), {}, renames); - ASSERT_TRUE(splitExpr.first.get()); - BSONObjBuilder firstBob; - splitExpr.first->serialize(&firstBob, true); - ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{c: {$type: [16]}}")); + ASSERT_BSONOBJ_EQ(splitExpr.first->serialize(), fromjson("{c: {$type: [16]}}")); ASSERT_FALSE(splitExpr.second.get()); } @@ -1296,9 +1236,7 @@ TEST(SplitMatchExpression, ShouldNotMoveSizeAcrossRename) { ASSERT_FALSE(splitExpr.first.get()); ASSERT_TRUE(splitExpr.second.get()); - BSONObjBuilder secondBob; - splitExpr.second->serialize(&secondBob, true); - ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{a: {$size: 3}}")); + ASSERT_BSONOBJ_EQ(splitExpr.second->serialize(), fromjson("{a: {$size: 3}}")); } TEST(SplitMatchExpression, ShouldNotMoveMinItemsAcrossRename) { @@ -1314,9 +1252,8 @@ TEST(SplitMatchExpression, ShouldNotMoveMinItemsAcrossRename) { ASSERT_FALSE(splitExpr.first.get()); ASSERT_TRUE(splitExpr.second.get()); - BSONObjBuilder secondBob; - splitExpr.second->serialize(&secondBob, true); - ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{a: {$_internalSchemaMinItems: 3}}")); + ASSERT_BSONOBJ_EQ(splitExpr.second->serialize(), + fromjson("{a: {$_internalSchemaMinItems: 3}}")); } TEST(SplitMatchExpression, ShouldNotMoveMaxItemsAcrossRename) { @@ -1332,9 +1269,8 @@ TEST(SplitMatchExpression, ShouldNotMoveMaxItemsAcrossRename) { ASSERT_FALSE(splitExpr.first.get()); ASSERT_TRUE(splitExpr.second.get()); - BSONObjBuilder secondBob; - splitExpr.second->serialize(&secondBob, true); - ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{a: {$_internalSchemaMaxItems: 3}}")); + ASSERT_BSONOBJ_EQ(splitExpr.second->serialize(), + fromjson("{a: {$_internalSchemaMaxItems: 3}}")); } TEST(SplitMatchExpression, ShouldNotMoveMaxItemsInLogicalExpressionAcrossRename) { @@ -1352,9 +1288,7 @@ TEST(SplitMatchExpression, ShouldNotMoveMaxItemsInLogicalExpressionAcrossRename) ASSERT_FALSE(splitExpr.first.get()); ASSERT_TRUE(splitExpr.second.get()); - BSONObjBuilder secondBob; - splitExpr.second->serialize(&secondBob, true); - ASSERT_BSONOBJ_EQ(secondBob.obj(), + ASSERT_BSONOBJ_EQ(splitExpr.second->serialize(), fromjson("{$or: [{a: {$_internalSchemaMaxItems: 3}}," " {a: {$_internalSchemaMaxItems: 4}}]}")); } @@ -1374,9 +1308,7 @@ TEST(SplitMatchExpression, ShouldNotMoveInternalSchemaObjectMatchInLogicalExpres ASSERT_FALSE(splitExpr.first.get()); ASSERT_TRUE(splitExpr.second.get()); - BSONObjBuilder secondBob; - splitExpr.second->serialize(&secondBob, true); - ASSERT_BSONOBJ_EQ(secondBob.obj(), + ASSERT_BSONOBJ_EQ(splitExpr.second->serialize(), fromjson("{$or: [{a: {$_internalSchemaObjectMatch: {b: {$eq: 1}}}}," " {a: {$_internalSchemaObjectMatch: {b: {$eq: 1}}}}]}")); } @@ -1392,9 +1324,8 @@ TEST(SplitMatchExpression, ShouldMoveMinLengthAcrossRename) { expression::splitMatchExpressionBy(std::move(matcher.getValue()), {}, renames); ASSERT_TRUE(splitExpr.first.get()); - BSONObjBuilder firstBob; - splitExpr.first->serialize(&firstBob, true); - ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{c: {$_internalSchemaMinLength: 3}}")); + ASSERT_BSONOBJ_EQ(splitExpr.first->serialize(), + fromjson("{c: {$_internalSchemaMinLength: 3}}")); ASSERT_FALSE(splitExpr.second.get()); } @@ -1410,9 +1341,8 @@ TEST(SplitMatchExpression, ShouldMoveMaxLengthAcrossRename) { expression::splitMatchExpressionBy(std::move(matcher.getValue()), {}, renames); ASSERT_TRUE(splitExpr.first.get()); - BSONObjBuilder firstBob; - splitExpr.first->serialize(&firstBob, true); - ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{c: {$_internalSchemaMaxLength: 3}}")); + ASSERT_BSONOBJ_EQ(splitExpr.first->serialize(), + fromjson("{c: {$_internalSchemaMaxLength: 3}}")); ASSERT_FALSE(splitExpr.second.get()); } @@ -1429,9 +1359,7 @@ TEST(SplitMatchExpression, ShouldMoveIndependentPredicateWhenThereAreMultipleRen expression::splitMatchExpressionBy(std::move(matcher.getValue()), {}, renames); ASSERT_TRUE(splitExpr.first.get()); - BSONObjBuilder firstBob; - splitExpr.first->serialize(&firstBob, true); - ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{x: {$eq: 3}}")); + ASSERT_BSONOBJ_EQ(splitExpr.first->serialize(), fromjson("{x: {$eq: 3}}")); ASSERT_FALSE(splitExpr.second.get()); } @@ -1449,9 +1377,7 @@ TEST(SplitMatchExpression, ShouldNotSplitWhenRand) { ASSERT_FALSE(split.get()); ASSERT_TRUE(residual.get()); - BSONObjBuilder oldBob; - residual->serialize(&oldBob, true); - ASSERT_BSONOBJ_EQ(oldBob.obj(), fromjson(randExpr)); + ASSERT_BSONOBJ_EQ(residual->serialize(), fromjson(randExpr)); }; // We should not push down a $match with a $rand expression. @@ -1462,85 +1388,73 @@ TEST(SplitMatchExpression, ShouldNotSplitWhenRand) { } TEST(SplitMatchExpression, ShouldSplitJsonSchemaRequiredByMetaField) { - ParsedMatchExpression matcher(R"({$and: [{b: 1}, {$jsonSchema: {required: ["a"]}}]})"); + ParsedMatchExpressionForTest matcher(R"({$and: [{b: 1}, {$jsonSchema: {required: ["a"]}}]})"); auto [splitOutExpr, residualExpr] = - expression::splitMatchExpressionBy(matcher.extractExpr(), {"a"}, {}); + expression::splitMatchExpressionBy(matcher.release(), {"a"}, {}); ASSERT_TRUE(splitOutExpr.get()); - BSONObjBuilder splitOutBob; - splitOutExpr->serialize(&splitOutBob, true); - ASSERT_BSONOBJ_EQ(splitOutBob.obj(), fromjson("{b: {$eq: 1}}")); + ASSERT_BSONOBJ_EQ(splitOutExpr->serialize(), fromjson("{b: {$eq: 1}}")); ASSERT_TRUE(residualExpr.get()); - BSONObjBuilder residualBob; - residualExpr->serialize(&residualBob, true); - ASSERT_BSONOBJ_EQ(residualBob.obj(), fromjson("{a: {$exists: true}}")); + ASSERT_BSONOBJ_EQ(residualExpr->serialize(), fromjson("{a: {$exists: true}}")); } TEST(SplitMatchExpression, ShouldSplitOutAndRenameJsonSchemaRequiredAndTheRestIsNullByIsOnlyDependentOn) { - ParsedMatchExpression matcher(R"({$jsonSchema: {required: ["a"]}})"); + ParsedMatchExpressionForTest matcher(R"({$jsonSchema: {required: ["a"]}})"); // $jsonSchema expression will be split out by the meta field "a" and the meta field "a" will be // renamed to "meta". auto [splitOutExpr, residualExpr] = expression::splitMatchExpressionBy( - matcher.extractExpr(), {"a"}, {{"a", "meta"}}, expression::isOnlyDependentOn); + matcher.release(), {"a"}, {{"a", "meta"}}, expression::isOnlyDependentOn); ASSERT_TRUE(splitOutExpr.get()); - BSONObjBuilder splitOutBob; - splitOutExpr->serialize(&splitOutBob, true); - ASSERT_BSONOBJ_EQ(splitOutBob.obj(), fromjson("{$and: [{$and: [{meta: {$exists: true}}]}]}")); + ASSERT_BSONOBJ_EQ(splitOutExpr->serialize(), + fromjson("{$and: [{$and: [{meta: {$exists: true}}]}]}")); ASSERT_FALSE(residualExpr.get()); } TEST(SplitMatchExpression, ShouldSplitOutAndRenameJsonSchemaRequiredAndTheRestIs_NOT_NullByIsOnlyDependentOn) { - ParsedMatchExpression matcher(R"({$jsonSchema: {required: ["a", "b"]}})"); + ParsedMatchExpressionForTest matcher(R"({$jsonSchema: {required: ["a", "b"]}})"); // $jsonSchema expression will be split out by the meta field "a" and the meta field "a" will be // renamed to "meta". The expression for the non-meta field "b" remains. auto [splitOutExpr, residualExpr] = expression::splitMatchExpressionBy( - matcher.extractExpr(), {"a"}, {{"a", "meta"}}, expression::isOnlyDependentOn); + matcher.release(), {"a"}, {{"a", "meta"}}, expression::isOnlyDependentOn); ASSERT_TRUE(splitOutExpr.get()); - BSONObjBuilder splitOutBob; - splitOutExpr->serialize(&splitOutBob, true); - ASSERT_BSONOBJ_EQ(splitOutBob.obj(), fromjson("{meta: {$exists: true}}")); + ASSERT_BSONOBJ_EQ(splitOutExpr->serialize(), fromjson("{meta: {$exists: true}}")); ASSERT_TRUE(residualExpr.get()); - BSONObjBuilder residualBob; - residualExpr->serialize(&residualBob, true); - ASSERT_BSONOBJ_EQ(residualBob.obj(), fromjson("{b: {$exists: true}}")); + ASSERT_BSONOBJ_EQ(residualExpr->serialize(), fromjson("{b: {$exists: true}}")); } TEST(SplitMatchExpression, ShouldSplitOutAndRenameJsonSchemaRequiredByIsOnlyDependentOn) { - ParsedMatchExpression matcher(R"({$and: [{b: 1}, {$jsonSchema: {required: ["a"]}}]})"); + ParsedMatchExpressionForTest matcher(R"({$and: [{b: 1}, {$jsonSchema: {required: ["a"]}}]})"); // $jsonSchema expression will be split out by the meta field "a" and the meta field "a" will be // renamed to "meta". We have a residual expression too in this test case. auto [splitOutExpr, residualExpr] = expression::splitMatchExpressionBy( - matcher.extractExpr(), {"a"}, {{"a", "meta"}}, expression::isOnlyDependentOn); + matcher.release(), {"a"}, {{"a", "meta"}}, expression::isOnlyDependentOn); ASSERT_TRUE(splitOutExpr.get()); - BSONObjBuilder splitOutBob; - splitOutExpr->serialize(&splitOutBob, true); - ASSERT_BSONOBJ_EQ(splitOutBob.obj(), fromjson("{$and: [{$and: [{meta: {$exists: true}}]}]}")); + ASSERT_BSONOBJ_EQ(splitOutExpr->serialize(), + fromjson("{$and: [{$and: [{meta: {$exists: true}}]}]}")); ASSERT_TRUE(residualExpr.get()); - BSONObjBuilder residualBob; - residualExpr->serialize(&residualBob, true); - ASSERT_BSONOBJ_EQ(residualBob.obj(), fromjson("{b: {$eq: 1}}")); + ASSERT_BSONOBJ_EQ(residualExpr->serialize(), fromjson("{b: {$eq: 1}}")); } // Verifies that $jsonSchema 'type' is supported by splitMatchExpressionBy(). TEST(SplitMatchExpression, ShouldSplitOutAndRenameJsonSchemaTypeByIsOnlyDependentOn) { - ParsedMatchExpression matcher(R"({$jsonSchema: {properties: {a: {type: "number"}}}})"); + ParsedMatchExpressionForTest matcher(R"({$jsonSchema: {properties: {a: {type: "number"}}}})"); // $jsonSchema expression will be split out by the meta field "a" and the meta field "a" will be // renamed to "meta". auto [splitOutExpr, residualExpr] = expression::splitMatchExpressionBy( - matcher.extractExpr(), {"a"}, {{"a", "meta"}}, expression::isOnlyDependentOn); + matcher.release(), {"a"}, {{"a", "meta"}}, expression::isOnlyDependentOn); ASSERT_TRUE(splitOutExpr.get()); ASSERT_BSONOBJ_EQ(splitOutExpr->serialize(), fromjson(R"( @@ -1561,12 +1475,12 @@ TEST(SplitMatchExpression, ShouldSplitOutAndRenameJsonSchemaTypeByIsOnlyDependen // Verifies that $jsonSchema 'bsonType' is supported by splitMatchExpressionBy(). TEST(SplitMatchExpression, ShouldSplitOutAndRenameJsonSchemaBsonTypeByIsOnlyDependentOn) { - ParsedMatchExpression matcher(R"({$jsonSchema: {properties: {a: {bsonType: "long"}}}})"); + ParsedMatchExpressionForTest matcher(R"({$jsonSchema: {properties: {a: {bsonType: "long"}}}})"); // $jsonSchema expression will be split out by the meta field "a" and the meta field "a" will be // renamed to "meta". auto [splitOutExpr, residualExpr] = expression::splitMatchExpressionBy( - matcher.extractExpr(), {"a"}, {{"a", "meta"}}, expression::isOnlyDependentOn); + matcher.release(), {"a"}, {{"a", "meta"}}, expression::isOnlyDependentOn); ASSERT_TRUE(splitOutExpr.get()); ASSERT_BSONOBJ_EQ(splitOutExpr->serialize(), fromjson(R"( @@ -1584,12 +1498,12 @@ TEST(SplitMatchExpression, ShouldSplitOutAndRenameJsonSchemaBsonTypeByIsOnlyDepe // Verifies that $jsonSchema 'enum' is supported by splitMatchExpressionBy(). TEST(SplitMatchExpression, ShouldSplitOutAndRenameJsonSchemaEnumByIsOnlyDependentOn) { - ParsedMatchExpression matcher(R"({$jsonSchema: {properties: {a: {enum: [1,2]}}}})"); + ParsedMatchExpressionForTest matcher(R"({$jsonSchema: {properties: {a: {enum: [1,2]}}}})"); // $jsonSchema expression will be split out by the meta field "a" and the meta field "a" will be // renamed to "meta". auto [splitOutExpr, residualExpr] = expression::splitMatchExpressionBy( - matcher.extractExpr(), {"a"}, {{"a", "meta"}}, expression::isOnlyDependentOn); + matcher.release(), {"a"}, {{"a", "meta"}}, expression::isOnlyDependentOn); ASSERT_TRUE(splitOutExpr.get()); ASSERT_BSONOBJ_EQ(splitOutExpr->serialize(), fromjson(R"( @@ -1610,12 +1524,13 @@ TEST(SplitMatchExpression, ShouldSplitOutAndRenameJsonSchemaEnumByIsOnlyDependen // Verifies that $jsonSchema 'minimum' and 'maximum' are supported by splitMatchExpressionBy(). TEST(SplitMatchExpression, ShouldSplitOutAndRenameJsonSchemaMinMaxByIsOnlyDependentOn) { - ParsedMatchExpression matcher("{$jsonSchema: {properties: {a: {minimum: 1, maximum: 10}}}}"); + ParsedMatchExpressionForTest matcher( + "{$jsonSchema: {properties: {a: {minimum: 1, maximum: 10}}}}"); // $jsonSchema expression will be split out by the meta field "a" and the meta field "a" will be // renamed to "meta". auto [splitOutExpr, residualExpr] = expression::splitMatchExpressionBy( - matcher.extractExpr(), {"a"}, {{"a", "meta"}}, expression::isOnlyDependentOn); + matcher.release(), {"a"}, {{"a", "meta"}}, expression::isOnlyDependentOn); ASSERT_TRUE(splitOutExpr.get()); ASSERT_BSONOBJ_EQ(splitOutExpr->serialize(), fromjson(R"( @@ -1651,13 +1566,13 @@ TEST(SplitMatchExpression, ShouldSplitOutAndRenameJsonSchemaMinMaxByIsOnlyDepend // Verifies that $jsonSchema 'minLength' and 'maxLength' are supported by splitMatchExpressionBy(). TEST(SplitMatchExpression, ShouldSplitOutAndRenameJsonSchemaMinMaxLengthByIsOnlyDependentOn) { - ParsedMatchExpression matcher( + ParsedMatchExpressionForTest matcher( "{$jsonSchema: {properties: {a: {minLength: 1, maxLength: 10}}}}"); // $jsonSchema expression will be split out by the meta field "a" and the meta field "a" will be // renamed to "meta". auto [splitOutExpr, residualExpr] = expression::splitMatchExpressionBy( - matcher.extractExpr(), {"a"}, {{"a", "meta"}}, expression::isOnlyDependentOn); + matcher.release(), {"a"}, {{"a", "meta"}}, expression::isOnlyDependentOn); ASSERT_TRUE(splitOutExpr.get()); ASSERT_BSONOBJ_EQ(splitOutExpr->serialize(), fromjson(R"( @@ -1693,12 +1608,12 @@ TEST(SplitMatchExpression, ShouldSplitOutAndRenameJsonSchemaMinMaxLengthByIsOnly // Verifies that $jsonSchema 'multipleOf' is supported by splitMatchExpressionBy(). TEST(SplitMatchExpression, ShouldSplitOutAndRenameJsonSchemaMultipleOfByIsOnlyDependentOn) { - ParsedMatchExpression matcher("{$jsonSchema: {properties: {a: {multipleOf: 10}}}}"); + ParsedMatchExpressionForTest matcher("{$jsonSchema: {properties: {a: {multipleOf: 10}}}}"); // $jsonSchema expression will be split out by the meta field "a" and the meta field "a" will be // renamed to "meta". auto [splitOutExpr, residualExpr] = expression::splitMatchExpressionBy( - matcher.extractExpr(), {"a"}, {{"a", "meta"}}, expression::isOnlyDependentOn); + matcher.release(), {"a"}, {{"a", "meta"}}, expression::isOnlyDependentOn); ASSERT_TRUE(splitOutExpr.get()); ASSERT_BSONOBJ_EQ(splitOutExpr->serialize(), fromjson(R"( @@ -1726,8 +1641,9 @@ TEST(SplitMatchExpression, ShouldSplitOutAndRenameJsonSchemaMultipleOfByIsOnlyDe // Verifies that $jsonSchema 'pattern' is supported by splitMatchExpressionBy(). TEST(SplitMatchExpression, ShouldSplitOutAndRenameJsonSchemaPatternByIsOnlyDependentOn) { - ParsedMatchExpression matcher(R"({$jsonSchema: {properties: {a: {pattern: "[0-9]*"}}}})"); - auto originalExpr = matcher.extractExpr(); + ParsedMatchExpressionForTest matcher( + R"({$jsonSchema: {properties: {a: {pattern: "[0-9]*"}}}})"); + auto originalExpr = matcher.release(); auto originalExprCopy = originalExpr->shallowClone(); // $jsonSchema expression will be split out by the meta field "a" and the meta field "a" will be @@ -1938,7 +1854,7 @@ TEST(HasExistencePredicateOnPath, ReturnsFalseWhenExistsOnSubpath) { } TEST(SplitMatchExpressionForColumns, PreservesEmptyPredicates) { - ParsedMatchExpression empty("{}"); + ParsedMatchExpressionForTest empty("{}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(empty.get()); ASSERT_EQ(splitUp.size(), 0) << splitUp.size(); ASSERT(residual == nullptr); @@ -1947,7 +1863,7 @@ TEST(SplitMatchExpressionForColumns, PreservesEmptyPredicates) { TEST(SplitMatchExpressionForColumns, RejectsUnsupportedPredicates) { { // Future work. - ParsedMatchExpression orClause("{$or: [{a: 1}, {b: 2}]}"); + ParsedMatchExpressionForTest orClause("{$or: [{a: 1}, {b: 2}]}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(orClause.get()); ASSERT_EQ(splitUp.size(), 0) << splitUp.size(); assertMatchesEqual(orClause, residual); @@ -1955,7 +1871,7 @@ TEST(SplitMatchExpressionForColumns, RejectsUnsupportedPredicates) { { // Would match missing values, not safe for a columnar index. - ParsedMatchExpression alwaysTrue("{$alwaysTrue: 1}"); + ParsedMatchExpressionForTest alwaysTrue("{$alwaysTrue: 1}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(alwaysTrue.get()); ASSERT_EQ(splitUp.size(), 0) << splitUp.size(); assertMatchesEqual(alwaysTrue, residual); @@ -1963,7 +1879,7 @@ TEST(SplitMatchExpressionForColumns, RejectsUnsupportedPredicates) { { // Future work. - ParsedMatchExpression exprClause("{$expr: {$eq: ['$x', 0]}}"); + ParsedMatchExpressionForTest exprClause("{$expr: {$eq: ['$x', 0]}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(exprClause.get()); ASSERT_EQ(splitUp.size(), 0) << splitUp.size(); assertMatchesEqual(exprClause, residual); @@ -1974,7 +1890,7 @@ TEST(SplitMatchExpressionForColumns, RejectsUnsupportedPredicates) { TEST(SplitMatchExpressionForColumns, SplitsSafeEqualities) { { - ParsedMatchExpression singleEqualsNumber("{albatross: 1}"); + ParsedMatchExpressionForTest singleEqualsNumber("{albatross: 1}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(singleEqualsNumber.get()); ASSERT_EQ(splitUp.size(), 1) << splitUp.size(); @@ -1985,7 +1901,7 @@ TEST(SplitMatchExpressionForColumns, SplitsSafeEqualities) { } { - ParsedMatchExpression singleEqualsString("{albatross: 'flying'}"); + ParsedMatchExpressionForTest singleEqualsString("{albatross: 'flying'}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(singleEqualsString.get()); ASSERT_EQ(splitUp.size(), 1) << splitUp.size(); @@ -1996,7 +1912,7 @@ TEST(SplitMatchExpressionForColumns, SplitsSafeEqualities) { } { - ParsedMatchExpression doubleEqualsNumber("{albatross: 1, blackbird: 2}"); + ParsedMatchExpressionForTest doubleEqualsNumber("{albatross: 1, blackbird: 2}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(doubleEqualsNumber.get()); ASSERT_EQ(splitUp.size(), 2) << splitUp.size(); @@ -2010,7 +1926,7 @@ TEST(SplitMatchExpressionForColumns, SplitsSafeEqualities) { } { - ParsedMatchExpression mixedEquals( + ParsedMatchExpressionForTest mixedEquals( "{albatross: 1," " blackbird: 'flying'," " cowbird: {$eq: /oreo/}," @@ -2023,7 +1939,7 @@ TEST(SplitMatchExpressionForColumns, SplitsSafeEqualities) { " kiwi: NumberDecimal('22')," " 'loggerhead shrike': {$minKey: 1}," " mallard: {$maxKey: 1}}"); - ParsedMatchExpression minMaxKeyComps( + ParsedMatchExpressionForTest minMaxKeyComps( "{'loggerhead shrike': {$minKey: 1}," " mallard: {$maxKey: 1}}"); @@ -2043,7 +1959,7 @@ TEST(SplitMatchExpressionForColumns, SplitsSafeEqualities) { TEST(SplitMatchExpressionForColumns, SupportsEqualityToEmptyObjects) { { - ParsedMatchExpression equalsEmptyObj("{albatross: {}}"); + ParsedMatchExpressionForTest equalsEmptyObj("{albatross: {}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(equalsEmptyObj.get()); ASSERT_EQ(splitUp.size(), 1) << splitUp.size(); @@ -2056,7 +1972,7 @@ TEST(SplitMatchExpressionForColumns, SupportsEqualityToEmptyObjects) { TEST(SplitMatchExpressionForColumns, SupportsEqualityToEmptyArray) { { - ParsedMatchExpression equalsEmptyArray("{albatross: []}"); + ParsedMatchExpressionForTest equalsEmptyArray("{albatross: []}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(equalsEmptyArray.get()); ASSERT_EQ(splitUp.size(), 1) << splitUp.size(); @@ -2069,7 +1985,7 @@ TEST(SplitMatchExpressionForColumns, SupportsEqualityToEmptyArray) { TEST(SplitMatchExpressionForColumns, DoesNotSupportEqualsNull) { { - ParsedMatchExpression equalsNull("{a: null}"); + ParsedMatchExpressionForTest equalsNull("{a: null}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(equalsNull.get()); ASSERT_EQ(splitUp.size(), 0) << splitUp.size(); assertMatchesEqual(equalsNull, residual); @@ -2078,28 +1994,28 @@ TEST(SplitMatchExpressionForColumns, DoesNotSupportEqualsNull) { TEST(SplitMatchExpressionForColumns, DoesNotSupportCompoundEquals) { { - ParsedMatchExpression implicitEqualsArray("{a: [1, 2]}"); + ParsedMatchExpressionForTest implicitEqualsArray("{a: [1, 2]}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(implicitEqualsArray.get()); ASSERT_EQ(splitUp.size(), 0) << splitUp.size(); assertMatchesEqual(implicitEqualsArray, residual); } { - ParsedMatchExpression explicitEqualsArray("{a: {$eq: [1, 2]}}"); + ParsedMatchExpressionForTest explicitEqualsArray("{a: {$eq: [1, 2]}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(explicitEqualsArray.get()); ASSERT_EQ(splitUp.size(), 0) << splitUp.size(); assertMatchesEqual(explicitEqualsArray, residual); } { - ParsedMatchExpression implicitEqualsObject("{a: {boats: 1, planes: 2}}"); + ParsedMatchExpressionForTest implicitEqualsObject("{a: {boats: 1, planes: 2}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(implicitEqualsObject.get()); ASSERT_EQ(splitUp.size(), 0) << splitUp.size(); assertMatchesEqual(implicitEqualsObject, residual); } { - ParsedMatchExpression explicitEqualsObject("{a: {$eq: {boats: 1, planes: 2}}}"); + ParsedMatchExpressionForTest explicitEqualsObject("{a: {$eq: {boats: 1, planes: 2}}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(explicitEqualsObject.get()); ASSERT_EQ(splitUp.size(), 0) << splitUp.size(); @@ -2107,7 +2023,7 @@ TEST(SplitMatchExpressionForColumns, DoesNotSupportCompoundEquals) { } // We should be able to do dotted path version though, as a potential workaround. { - ParsedMatchExpression equalsDotted("{'a.boats': 1, 'a.planes': 2}"); + ParsedMatchExpressionForTest equalsDotted("{'a.boats': 1, 'a.planes': 2}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(equalsDotted.get()); ASSERT_GT(splitUp.size(), 0); ASSERT(splitUp.size() == 2); @@ -2125,7 +2041,7 @@ TEST(SplitMatchExpressionForColumns, DoesNotSupportCompoundEquals) { TEST(SplitMatchExpressionForColumns, SupportsComparisonsLikeEqualities) { { - ParsedMatchExpression singleLtNumber("{albatross: {$lt: 1}}"); + ParsedMatchExpressionForTest singleLtNumber("{albatross: {$lt: 1}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(singleLtNumber.get()); ASSERT_EQ(splitUp.size(), 1) << splitUp.size(); @@ -2135,7 +2051,7 @@ TEST(SplitMatchExpressionForColumns, SupportsComparisonsLikeEqualities) { ASSERT(residual == nullptr); } { - ParsedMatchExpression singleLteNumber("{albatross: {$lte: 1}}"); + ParsedMatchExpressionForTest singleLteNumber("{albatross: {$lte: 1}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(singleLteNumber.get()); ASSERT_EQ(splitUp.size(), 1) << splitUp.size(); @@ -2145,7 +2061,7 @@ TEST(SplitMatchExpressionForColumns, SupportsComparisonsLikeEqualities) { ASSERT(residual == nullptr); } { - ParsedMatchExpression singleGtNumber("{albatross: {$gt: 1}}"); + ParsedMatchExpressionForTest singleGtNumber("{albatross: {$gt: 1}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(singleGtNumber.get()); ASSERT_EQ(splitUp.size(), 1) << splitUp.size(); @@ -2155,7 +2071,7 @@ TEST(SplitMatchExpressionForColumns, SupportsComparisonsLikeEqualities) { ASSERT(residual == nullptr); } { - ParsedMatchExpression singleGteNumber("{albatross: {$gte: 1}}"); + ParsedMatchExpressionForTest singleGteNumber("{albatross: {$gte: 1}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(singleGteNumber.get()); ASSERT_EQ(splitUp.size(), 1) << splitUp.size(); @@ -2165,7 +2081,7 @@ TEST(SplitMatchExpressionForColumns, SupportsComparisonsLikeEqualities) { ASSERT(residual == nullptr); } { - ParsedMatchExpression combinationPredicate( + ParsedMatchExpressionForTest combinationPredicate( "{" " albatross: {$lt: 100}," " blackbird: {$gt: 0}," @@ -2190,49 +2106,49 @@ TEST(SplitMatchExpressionForColumns, SupportsComparisonsLikeEqualities) { // While equality to [] or {} is OK, inequality is not so obvious. Left as future work. TEST(SplitMatchExpressionForColumns, DoesNotSupportInequalitiesToObjectsOrArrays) { { - ParsedMatchExpression ltArray("{albatross: {$lt: []}}"); + ParsedMatchExpressionForTest ltArray("{albatross: {$lt: []}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(ltArray.get()); ASSERT_EQ(splitUp.size(), 0) << splitUp.size(); assertMatchesEqual(ltArray, residual); } { - ParsedMatchExpression ltObject("{albatross: {$lt: {}}}"); + ParsedMatchExpressionForTest ltObject("{albatross: {$lt: {}}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(ltObject.get()); ASSERT_EQ(splitUp.size(), 0) << splitUp.size(); assertMatchesEqual(ltObject, residual); } { - ParsedMatchExpression lteArray("{albatross: {$lte: []}}"); + ParsedMatchExpressionForTest lteArray("{albatross: {$lte: []}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(lteArray.get()); ASSERT_EQ(splitUp.size(), 0) << splitUp.size(); assertMatchesEqual(lteArray, residual); } { - ParsedMatchExpression lteObject("{albatross: {$lte: {}}}"); + ParsedMatchExpressionForTest lteObject("{albatross: {$lte: {}}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(lteObject.get()); ASSERT_EQ(splitUp.size(), 0) << splitUp.size(); assertMatchesEqual(lteObject, residual); } { - ParsedMatchExpression gtArray("{albatross: {$gt: []}}"); + ParsedMatchExpressionForTest gtArray("{albatross: {$gt: []}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(gtArray.get()); ASSERT_EQ(splitUp.size(), 0) << splitUp.size(); assertMatchesEqual(gtArray, residual); } { - ParsedMatchExpression gtObject("{albatross: {$gt: {}}}"); + ParsedMatchExpressionForTest gtObject("{albatross: {$gt: {}}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(gtObject.get()); ASSERT_EQ(splitUp.size(), 0) << splitUp.size(); assertMatchesEqual(gtObject, residual); } { - ParsedMatchExpression gteArray("{albatross: {$gte: []}}"); + ParsedMatchExpressionForTest gteArray("{albatross: {$gte: []}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(gteArray.get()); ASSERT_EQ(splitUp.size(), 0) << splitUp.size(); assertMatchesEqual(gteArray, residual); } { - ParsedMatchExpression gteObject("{albatross: {$gte: {}}}"); + ParsedMatchExpressionForTest gteObject("{albatross: {$gte: {}}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(gteObject.get()); ASSERT_EQ(splitUp.size(), 0) << splitUp.size(); assertMatchesEqual(gteObject, residual); @@ -2241,7 +2157,7 @@ TEST(SplitMatchExpressionForColumns, DoesNotSupportInequalitiesToObjectsOrArrays // Tests that comparisons which only match values of a certain type are allowed. TEST(SplitMatchExpressionForColumns, SupportsTypeSpecificPredicates) { - ParsedMatchExpression combinationPredicate( + ParsedMatchExpressionForTest combinationPredicate( "{" " albatross: /oreo/," " blackbird: {$mod: [2, 0]}," @@ -2275,7 +2191,7 @@ TEST(SplitMatchExpressionForColumns, SupportsTypeSpecificPredicates) { } TEST(SplitMatchExpressionForColumns, SupportsExistsTrue) { - ParsedMatchExpression existsPredicate("{albatross: {$exists: true}}"); + ParsedMatchExpressionForTest existsPredicate("{albatross: {$exists: true}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(existsPredicate.get()); ASSERT_EQ(splitUp.size(), 1) << splitUp.size(); ASSERT(splitUp.contains("albatross")); @@ -2285,7 +2201,7 @@ TEST(SplitMatchExpressionForColumns, SupportsExistsTrue) { } TEST(SplitMatchExpressionForColumns, DoesNotSupportExistsFalse) { - ParsedMatchExpression existsPredicate("{albatross: {$exists: false}}"); + ParsedMatchExpressionForTest existsPredicate("{albatross: {$exists: false}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(existsPredicate.get()); ASSERT_EQ(splitUp.size(), 0) << splitUp.size(); assertMatchesEqual(existsPredicate, residual); @@ -2295,7 +2211,7 @@ TEST(SplitMatchExpressionForColumns, DoesNotSupportExistsFalse) { // next test. TEST(SplitMatchExpressionForColumns, SupportsSomeInPredicates) { { - ParsedMatchExpression emptyIn("{albatross: {$in: []}}"); + ParsedMatchExpressionForTest emptyIn("{albatross: {$in: []}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(emptyIn.get()); ASSERT_EQ(splitUp.size(), 1) << splitUp.size(); ASSERT(splitUp.contains("albatross")); @@ -2304,7 +2220,7 @@ TEST(SplitMatchExpressionForColumns, SupportsSomeInPredicates) { ASSERT(residual == nullptr); } { - ParsedMatchExpression singleElementIn("{albatross: {$in: [4]}}"); + ParsedMatchExpressionForTest singleElementIn("{albatross: {$in: [4]}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(singleElementIn.get()); ASSERT_EQ(splitUp.size(), 1) << splitUp.size(); @@ -2314,7 +2230,7 @@ TEST(SplitMatchExpressionForColumns, SupportsSomeInPredicates) { ASSERT(residual == nullptr); } { - ParsedMatchExpression multiElementIn("{albatross: {$in: [4, 5, 9]}}"); + ParsedMatchExpressionForTest multiElementIn("{albatross: {$in: [4, 5, 9]}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(multiElementIn.get()); ASSERT_EQ(splitUp.size(), 1) << splitUp.size(); @@ -2324,7 +2240,7 @@ TEST(SplitMatchExpressionForColumns, SupportsSomeInPredicates) { ASSERT(residual == nullptr); } { - ParsedMatchExpression inWithEmptyArray("{albatross: {$in: [[]]}}"); + ParsedMatchExpressionForTest inWithEmptyArray("{albatross: {$in: [[]]}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(inWithEmptyArray.get()); ASSERT_EQ(splitUp.size(), 1) << splitUp.size(); @@ -2334,7 +2250,7 @@ TEST(SplitMatchExpressionForColumns, SupportsSomeInPredicates) { ASSERT(residual == nullptr); } { - ParsedMatchExpression inWithEmptyObject("{albatross: {$in: [{}]}}"); + ParsedMatchExpressionForTest inWithEmptyObject("{albatross: {$in: [{}]}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(inWithEmptyObject.get()); ASSERT_EQ(splitUp.size(), 1) << splitUp.size(); @@ -2345,7 +2261,7 @@ TEST(SplitMatchExpressionForColumns, SupportsSomeInPredicates) { } { - ParsedMatchExpression mixedTypeIn("{albatross: {$in: [4, {}, [], 'string']}}"); + ParsedMatchExpressionForTest mixedTypeIn("{albatross: {$in: [4, {}, [], 'string']}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(mixedTypeIn.get()); ASSERT_EQ(splitUp.size(), 1) << splitUp.size(); ASSERT(splitUp.contains("albatross")); @@ -2357,7 +2273,7 @@ TEST(SplitMatchExpressionForColumns, SupportsSomeInPredicates) { TEST(SplitMatchExpressionForColumns, DoesNotSupportSomeInPredicates) { { - ParsedMatchExpression regexIn("{albatross: {$in: [/regex/]}}"); + ParsedMatchExpressionForTest regexIn("{albatross: {$in: [/regex/]}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(regexIn.get()); ASSERT_EQ(splitUp.size(), 0) << splitUp.size(); ASSERT(residual != nullptr); @@ -2365,7 +2281,7 @@ TEST(SplitMatchExpressionForColumns, DoesNotSupportSomeInPredicates) { } { - ParsedMatchExpression nonEmptyObj("{albatross: {$in: [{a: 1}]}}"); + ParsedMatchExpressionForTest nonEmptyObj("{albatross: {$in: [{a: 1}]}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(nonEmptyObj.get()); ASSERT_EQ(splitUp.size(), 0) << splitUp.size(); ASSERT(residual != nullptr); @@ -2373,7 +2289,7 @@ TEST(SplitMatchExpressionForColumns, DoesNotSupportSomeInPredicates) { } { - ParsedMatchExpression nonEmptyArr("{albatross: {$in: [[1, 2]]}}"); + ParsedMatchExpressionForTest nonEmptyArr("{albatross: {$in: [[1, 2]]}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(nonEmptyArr.get()); ASSERT_EQ(splitUp.size(), 0) << splitUp.size(); ASSERT(residual != nullptr); @@ -2384,25 +2300,25 @@ TEST(SplitMatchExpressionForColumns, DoesNotSupportSomeInPredicates) { // We can't support compound types, just like for equality. TEST(SplitMatchExpressionForColumns, DoesNotSupportCertainInEdgeCases) { { - ParsedMatchExpression inWithArray("{albatross: {$in: [[2,3]]}}"); + ParsedMatchExpressionForTest inWithArray("{albatross: {$in: [[2,3]]}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(inWithArray.get()); ASSERT_EQ(splitUp.size(), 0) << splitUp.size(); assertMatchesEqual(inWithArray, residual); } { - ParsedMatchExpression inWithObject("{albatross: {$in: [{wings: 2}]}}"); + ParsedMatchExpressionForTest inWithObject("{albatross: {$in: [{wings: 2}]}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(inWithObject.get()); ASSERT_EQ(splitUp.size(), 0) << splitUp.size(); assertMatchesEqual(inWithObject, residual); } { - ParsedMatchExpression inWithNull("{albatross: {$in: [null]}}"); + ParsedMatchExpressionForTest inWithNull("{albatross: {$in: [null]}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(inWithNull.get()); ASSERT_EQ(splitUp.size(), 0) << splitUp.size(); assertMatchesEqual(inWithNull, residual); } { - ParsedMatchExpression unsupportedMixedInWithSupported( + ParsedMatchExpressionForTest unsupportedMixedInWithSupported( "{albatross: {$in: ['strings', 1, null, {x: 4}, [0, 0], 4]}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(unsupportedMixedInWithSupported.get()); @@ -2412,7 +2328,7 @@ TEST(SplitMatchExpressionForColumns, DoesNotSupportCertainInEdgeCases) { } TEST(SplitMatchExpressionForColumns, SupportsTypePredicatesInt) { - ParsedMatchExpression intFilter("{albatross: {$type: 'int'}}"); + ParsedMatchExpressionForTest intFilter("{albatross: {$type: 'int'}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(intFilter.get()); ASSERT_GT(splitUp.size(), 0); ASSERT(splitUp.contains("albatross")); @@ -2423,7 +2339,7 @@ TEST(SplitMatchExpressionForColumns, SupportsTypePredicatesInt) { } TEST(SplitMatchExpressionForColumns, SupportsTypePredicatesNumber) { - ParsedMatchExpression numberFilter("{albatross: {$type: 'number'}}"); + ParsedMatchExpressionForTest numberFilter("{albatross: {$type: 'number'}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(numberFilter.get()); ASSERT_GT(splitUp.size(), 0); ASSERT(splitUp.contains("albatross")); @@ -2434,7 +2350,7 @@ TEST(SplitMatchExpressionForColumns, SupportsTypePredicatesNumber) { } TEST(SplitMatchExpressionForColumns, SupportsTypePredicatesMultiple) { - ParsedMatchExpression stringFilter("{albatross: {$type: ['string', 'double']}}"); + ParsedMatchExpressionForTest stringFilter("{albatross: {$type: ['string', 'double']}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(stringFilter.get()); ASSERT_EQ(splitUp.size(), 1) << splitUp.size(); ASSERT(splitUp.contains("albatross")); @@ -2444,7 +2360,7 @@ TEST(SplitMatchExpressionForColumns, SupportsTypePredicatesMultiple) { } TEST(SplitMatchExpressionForColumns, SupportsTypePredicatesNull) { - ParsedMatchExpression nullFilter("{albatross: {$type: 'null'}}"); + ParsedMatchExpressionForTest nullFilter("{albatross: {$type: 'null'}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(nullFilter.get()); ASSERT_EQ(splitUp.size(), 1) << splitUp.size(); ASSERT(splitUp.contains("albatross")); @@ -2454,7 +2370,7 @@ TEST(SplitMatchExpressionForColumns, SupportsTypePredicatesNull) { } TEST(SplitMatchExpressionForColumns, SupportsTypePredicatesObject) { - ParsedMatchExpression objectFilter("{albatross: {$type: 'object'}}"); + ParsedMatchExpressionForTest objectFilter("{albatross: {$type: 'object'}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(objectFilter.get()); ASSERT_EQ(splitUp.size(), 1) << splitUp.size(); ASSERT(splitUp.contains("albatross")); @@ -2464,7 +2380,7 @@ TEST(SplitMatchExpressionForColumns, SupportsTypePredicatesObject) { } TEST(SplitMatchExpressionForColumns, SupportsTypePredicatesArray) { - ParsedMatchExpression arrayFilter("{albatross: {$type: 'array'}}"); + ParsedMatchExpressionForTest arrayFilter("{albatross: {$type: 'array'}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(arrayFilter.get()); ASSERT_EQ(splitUp.size(), 1) << splitUp.size(); ASSERT(splitUp.contains("albatross")); @@ -2474,7 +2390,7 @@ TEST(SplitMatchExpressionForColumns, SupportsTypePredicatesArray) { } TEST(SplitMatchExpressionForColumns, SupportsCombiningPredicatesWithAnd) { - ParsedMatchExpression compoundFilter( + ParsedMatchExpressionForTest compoundFilter( "{" " albatross: {$gte: 100}," " albatross: {$lt: 200}," @@ -2502,7 +2418,7 @@ TEST(SplitMatchExpressionForColumns, SupportsCombiningPredicatesWithAnd) { } TEST(SplitMatchExpressionForColumns, SupportsAndFlattensNestedAnd) { - ParsedMatchExpression compoundFilter( + ParsedMatchExpressionForTest compoundFilter( "{" " $and: [" " {albatross: {$gte: 100}}," @@ -2532,13 +2448,13 @@ TEST(SplitMatchExpressionForColumns, SupportsAndFlattensNestedAnd) { TEST(SplitMatchExpressionForColumns, DoesNotSupportStandaloneNotQueries) { { - ParsedMatchExpression notEqFilter("{albatross: {$not: {$eq: 2}}}"); + ParsedMatchExpressionForTest notEqFilter("{albatross: {$not: {$eq: 2}}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(notEqFilter.get()); ASSERT_EQ(splitUp.size(), 0) << splitUp.size(); assertMatchesEqual(notEqFilter, residual); } { - ParsedMatchExpression notAndFilter("{albatross: {$not: {$type: 'number'}}}"); + ParsedMatchExpressionForTest notAndFilter("{albatross: {$not: {$type: 'number'}}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(notAndFilter.get()); ASSERT_EQ(splitUp.size(), 0) << splitUp.size(); assertMatchesEqual(notAndFilter, residual); @@ -2547,7 +2463,7 @@ TEST(SplitMatchExpressionForColumns, DoesNotSupportStandaloneNotQueries) { TEST(SplitMatchExpressionForColumns, SupportsNotQueriesInPresenceOfOtherSupportedOnSamePath) { { - ParsedMatchExpression notEqFilter( + ParsedMatchExpressionForTest notEqFilter( "{$and: [{a: {$gt: 0, $lt: 50}}, {a: {$ne: 2}}, {a: {$ne: 20}}]}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(notEqFilter.get()); ASSERT_EQ(splitUp.size(), 1) << splitUp.size(); @@ -2559,29 +2475,30 @@ TEST(SplitMatchExpressionForColumns, SupportsNotQueriesInPresenceOfOtherSupporte // {$ne: null} is the same as {$not: {$eq: null}} and while it could be evaluated against // the index, because {$eq: null} isn't supported for push down, we don't push down its // negation either, but {$ne: 2} should be lowered. - ParsedMatchExpression notAndFilter( + ParsedMatchExpressionForTest notAndFilter( "{$and: [{a: {$exists: true}}, {a: {$ne: 2}}, {a: {$ne: null}}]}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(notAndFilter.get()); ASSERT_EQ(splitUp.size(), 1) << splitUp.size(); ASSERT(splitUp.at("a")->matchType() == MatchExpression::AND) << splitUp.at("a")->toString(); - assertMatchesEqual(ParsedMatchExpression("{a: {$ne: null}}"), residual); + assertMatchesEqual(ParsedMatchExpressionForTest("{a: {$ne: null}}"), residual); } { // $not on multiple paths should only be lowered if there are supported predicates on the // same path with them - ParsedMatchExpression notEqFilter("{a: {$gt: 0, $ne: 2}, b:{$ne: 2, $lt: 5}, c: {$ne: 2}}"); + ParsedMatchExpressionForTest notEqFilter( + "{a: {$gt: 0, $ne: 2}, b:{$ne: 2, $lt: 5}, c: {$ne: 2}}"); auto&& [splitUp, residual] = expression::splitMatchExpressionForColumns(notEqFilter.get()); ASSERT_EQ(splitUp.size(), 2) << splitUp.size(); ASSERT(splitUp.contains("a")); ASSERT(splitUp.at("a")->matchType() == MatchExpression::AND) << splitUp.at("a")->toString(); ASSERT(splitUp.contains("b")); ASSERT(splitUp.at("b")->matchType() == MatchExpression::AND) << splitUp.at("a")->toString(); - assertMatchesEqual(ParsedMatchExpression("{c: {$ne: 2}}"), residual); + assertMatchesEqual(ParsedMatchExpressionForTest("{c: {$ne: 2}}"), residual); } } TEST(SplitMatchExpressionForColumns, CanSplitPredicate) { - ParsedMatchExpression complexPredicate(R"({ + ParsedMatchExpressionForTest complexPredicate(R"({ a: {$gte: 0}, unsubscribed: false, specialAddress: {$exists: false} @@ -2592,12 +2509,12 @@ TEST(SplitMatchExpressionForColumns, CanSplitPredicate) { ASSERT(splitUp.at("a")->matchType() == MatchExpression::GTE) << splitUp.at("a")->toString(); ASSERT(splitUp.contains("unsubscribed")); ASSERT(!splitUp.contains("specialAddress")); - ParsedMatchExpression expectedResidual("{specialAddress: {$exists: false}}"); + ParsedMatchExpressionForTest expectedResidual("{specialAddress: {$exists: false}}"); assertMatchesEqual(expectedResidual, residual); } TEST(SplitMatchExpressionForColumns, SupportsDottedPaths) { - ParsedMatchExpression compoundFilter( + ParsedMatchExpressionForTest compoundFilter( "{" " albatross: /oreo/," " \"blackbird.feet\": {$mod: [2, 0]}," @@ -2631,7 +2548,7 @@ TEST(SplitMatchExpressionForColumns, SupportsDottedPaths) { } TEST(SplitMatchExpressionForColumns, LeavesOriginalMatchExpressionFunctional) { - ParsedMatchExpression combinationPredicate( + ParsedMatchExpressionForTest 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 6a97367aff5..468e34ccab8 100644 --- a/src/mongo/db/matcher/expression_always_boolean.h +++ b/src/mongo/db/matcher/expression_always_boolean.h @@ -62,7 +62,8 @@ public: debug << name() << ": 1\n"; } - void serialize(BSONObjBuilder* out, bool includePath) const final { + void serialize(BSONObjBuilder* out, SerializationOptions opts) const final { + // TODO support 'opts.redactFieldNames.' out->append(name(), 1); } diff --git a/src/mongo/db/matcher/expression_arity.h b/src/mongo/db/matcher/expression_arity.h index a16dcf346a2..e699b6ae363 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, true); + serialize(&builder, {}); debug << builder.obj().toString(); } @@ -107,11 +107,12 @@ public: /** * Serializes each subexpression sequentially in a BSONArray. */ - void serialize(BSONObjBuilder* builder, bool includePath) const final { + void serialize(BSONObjBuilder* builder, SerializationOptions opts) const final { + // TODO SERVER-73678 respect 'opts'. BSONArrayBuilder exprArray(builder->subarrayStart(name())); for (const auto& expr : _expressions) { BSONObjBuilder exprBuilder(exprArray.subobjStart()); - expr->serialize(&exprBuilder, includePath); + expr->serialize(&exprBuilder, opts); exprBuilder.doneFast(); } exprArray.doneFast(); diff --git a/src/mongo/db/matcher/expression_array.cpp b/src/mongo/db/matcher/expression_array.cpp index 833390eaa62..6ed79d1de5e 100644 --- a/src/mongo/db/matcher/expression_array.cpp +++ b/src/mongo/db/matcher/expression_array.cpp @@ -103,10 +103,10 @@ void ElemMatchObjectMatchExpression::debugString(StringBuilder& debug, int inden _sub->debugString(debug, indentationLevel + 1); } -BSONObj ElemMatchObjectMatchExpression::getSerializedRightHandSide() const { - BSONObjBuilder subBob; - _sub->serialize(&subBob, true); - return BSON("$elemMatch" << subBob.obj()); +BSONObj ElemMatchObjectMatchExpression::getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const { + // TODO SERVER-73673 respect and test 'replacementForLiteralArgs'. + return BSON("$elemMatch" << _sub->serialize()); } MatchExpression::ExpressionOptimizerFunc ElemMatchObjectMatchExpression::getOptimizer() const { @@ -174,11 +174,15 @@ void ElemMatchValueMatchExpression::debugString(StringBuilder& debug, int indent } } -BSONObj ElemMatchValueMatchExpression::getSerializedRightHandSide() const { +BSONObj ElemMatchValueMatchExpression::getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const { BSONObjBuilder emBob; + // TODO SERVER-73673 respect and test 'replacementForLiteralArgs'. for (auto&& child : _subs) { - child->serialize(&emBob, false); + SerializationOptions opts; + opts.includePath = false; + child->serialize(&emBob, opts); } return BSON("$elemMatch" << emBob.obj()); @@ -219,7 +223,9 @@ void SizeMatchExpression::debugString(StringBuilder& debug, int indentationLevel } } -BSONObj SizeMatchExpression::getSerializedRightHandSide() const { +BSONObj SizeMatchExpression::getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const { + // TODO SERVER-73676 respect 'replacementForLiteralArgs.' return BSON("$size" << _size); } diff --git a/src/mongo/db/matcher/expression_array.h b/src/mongo/db/matcher/expression_array.h index 5c37046a201..39ccc69acff 100644 --- a/src/mongo/db/matcher/expression_array.h +++ b/src/mongo/db/matcher/expression_array.h @@ -90,7 +90,8 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel) const; - BSONObj getSerializedRightHandSide() const final; + BSONObj getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const final; std::vector<std::unique_ptr<MatchExpression>>* getChildVector() final { return nullptr; @@ -158,7 +159,8 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel) const; - BSONObj getSerializedRightHandSide() const final; + BSONObj getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const final; std::vector<std::unique_ptr<MatchExpression>>* getChildVector() final { return &_subs; @@ -233,7 +235,8 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel) const; - BSONObj getSerializedRightHandSide() const final; + BSONObj getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) 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 a1d151b8e1c..cd7f27fa9ba 100644 --- a/src/mongo/db/matcher/expression_expr.cpp +++ b/src/mongo/db/matcher/expression_expr.cpp @@ -77,7 +77,8 @@ Value ExprMatchExpression::evaluateExpression(const MatchableDocument* doc) cons return _expression->evaluate(document, &variables); } -void ExprMatchExpression::serialize(BSONObjBuilder* out, bool includePath) const { +void ExprMatchExpression::serialize(BSONObjBuilder* out, SerializationOptions opts) const { + // TODO aggregation expressions should support some of the new options. *out << "$expr" << _expression->serialize(false); } diff --git a/src/mongo/db/matcher/expression_expr.h b/src/mongo/db/matcher/expression_expr.h index d6966479df4..51a1c273c59 100644 --- a/src/mongo/db/matcher/expression_expr.h +++ b/src/mongo/db/matcher/expression_expr.h @@ -75,7 +75,7 @@ public: debug << "$expr " << _expression->serialize(false).toString(); } - void serialize(BSONObjBuilder* out, bool includePath) const final; + void serialize(BSONObjBuilder* out, SerializationOptions opts) const final; bool isTriviallyTrue() const final; diff --git a/src/mongo/db/matcher/expression_expr_test.cpp b/src/mongo/db/matcher/expression_expr_test.cpp index 01bd01818b0..6401357cc61 100644 --- a/src/mongo/db/matcher/expression_expr_test.cpp +++ b/src/mongo/db/matcher/expression_expr_test.cpp @@ -724,17 +724,10 @@ 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(serialized, expectedSerialization); + ASSERT_BSONOBJ_EQ(optimized->serialize(), expectedSerialization); } TEST(ExprMatchTest, OptimizingExprRemovesTrueConstantExpression) { diff --git a/src/mongo/db/matcher/expression_geo.cpp b/src/mongo/db/matcher/expression_geo.cpp index 68b9af7fa55..437cfd2b32f 100644 --- a/src/mongo/db/matcher/expression_geo.cpp +++ b/src/mongo/db/matcher/expression_geo.cpp @@ -434,7 +434,7 @@ void GeoMatchExpression::debugString(StringBuilder& debug, int indentationLevel) _debugAddSpace(debug, indentationLevel); BSONObjBuilder builder; - serialize(&builder, true); + serialize(&builder, {}); debug << "GEO raw = " << builder.obj().toString(); MatchExpression::TagData* td = getTag(); @@ -445,8 +445,11 @@ void GeoMatchExpression::debugString(StringBuilder& debug, int indentationLevel) debug << "\n"; } -BSONObj GeoMatchExpression::getSerializedRightHandSide() const { +BSONObj GeoMatchExpression::getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const { BSONObjBuilder subobj; + // TODO SERVER-73672 looks like we'll need to traverse '_rawObj' if 'replacementForLiteralArgs' + // is set. subobj.appendElements(_rawObj); return subobj.obj(); } @@ -503,7 +506,10 @@ void GeoNearMatchExpression::debugString(StringBuilder& debug, int indentationLe debug << "\n"; } -BSONObj GeoNearMatchExpression::getSerializedRightHandSide() const { +BSONObj GeoNearMatchExpression::getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const { + // TODO SERVER-73672 looks like we'll need to traverse '_rawObj' if 'replacementForLiteralArgs' + // is set. BSONObjBuilder objBuilder; objBuilder.appendElements(_rawObj); return objBuilder.obj(); diff --git a/src/mongo/db/matcher/expression_geo.h b/src/mongo/db/matcher/expression_geo.h index f2a15c2db89..01968db7fd4 100644 --- a/src/mongo/db/matcher/expression_geo.h +++ b/src/mongo/db/matcher/expression_geo.h @@ -106,7 +106,8 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel = 0) const; - BSONObj getSerializedRightHandSide() const final; + BSONObj getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const final; virtual bool equivalent(const MatchExpression* other) const; @@ -208,7 +209,8 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel = 0) const; - BSONObj getSerializedRightHandSide() const final; + BSONObj getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const final; virtual bool equivalent(const MatchExpression* other) const; @@ -248,7 +250,7 @@ public: TwoDPtInAnnulusExpression(const R2Annulus& annulus, boost::optional<StringData> twoDPath) : LeafMatchExpression(INTERNAL_2D_POINT_IN_ANNULUS, twoDPath), _annulus(annulus) {} - void serialize(BSONObjBuilder* out, bool includePath) const final { + void serialize(BSONObjBuilder* out, SerializationOptions opts) const final { out->append("TwoDPtInAnnulusExpression", true); } @@ -267,7 +269,9 @@ public: // These won't be called. // - BSONObj getSerializedRightHandSide() const final { + BSONObj getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const final { + // TODO SERVER-73676 is this going to be a problem? I don't see how this is unreachable... MONGO_UNREACHABLE; } 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 5bf75f8218f..860384bf102 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, true); + serialize(&builder, {}); debug << builder.obj().toString() << "\n"; const auto* tag = getTag(); @@ -194,7 +194,8 @@ bool InternalBucketGeoWithinMatchExpression::_matchesBSONObj(const BSONObj& obj) } void InternalBucketGeoWithinMatchExpression::serialize(BSONObjBuilder* builder, - bool includePath) const { + SerializationOptions opts) const { + // TODO SERVER-73676 respect 'opts'. BSONObjBuilder bob(builder->subobjStart(InternalBucketGeoWithinMatchExpression::kName)); BSONObjBuilder withinRegionBob( bob.subobjStart(InternalBucketGeoWithinMatchExpression::kWithinRegion)); 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 2f0986a7008..5c7f32ef77f 100644 --- a/src/mongo/db/matcher/expression_internal_bucket_geo_within.h +++ b/src/mongo/db/matcher/expression_internal_bucket_geo_within.h @@ -94,7 +94,7 @@ public: return false; } - void serialize(BSONObjBuilder* builder, bool includePath) const final; + void serialize(BSONObjBuilder* builder, SerializationOptions opts) const final; std::unique_ptr<MatchExpression> shallowClone() const final; 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 d1be964fc7c..445f49c9f12 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, true); + eq.serialize(&bob, {}); 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 9ee94f9a7c3..7e0bd6f2791 100644 --- a/src/mongo/db/matcher/expression_leaf.cpp +++ b/src/mongo/db/matcher/expression_leaf.cpp @@ -94,8 +94,13 @@ void ComparisonMatchExpressionBase::debugString(StringBuilder& debug, int indent debug << "\n"; } -BSONObj ComparisonMatchExpressionBase::getSerializedRightHandSide() const { - return BSON(name() << _rhs); +BSONObj ComparisonMatchExpressionBase::getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const { + if (replacementForLiteralArgs) { + return BSON(name() << *replacementForLiteralArgs); + } else { + return BSON(name() << _rhs); + } } ComparisonMatchExpression::ComparisonMatchExpression(MatchType type, @@ -285,12 +290,13 @@ void RegexMatchExpression::debugString(StringBuilder& debug, int indentationLeve debug << "\n"; } -BSONObj RegexMatchExpression::getSerializedRightHandSide() const { +BSONObj RegexMatchExpression::getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const { BSONObjBuilder regexBuilder; - regexBuilder.append("$regex", _regex); + regexBuilder.append("$regex", replacementForLiteralArgs.value_or(_regex)); if (!_flags.empty()) { - regexBuilder.append("$options", _flags); + regexBuilder.append("$options", replacementForLiteralArgs.value_or(_flags)); } return regexBuilder.obj(); @@ -366,8 +372,13 @@ void ModMatchExpression::debugString(StringBuilder& debug, int indentationLevel) debug << "\n"; } -BSONObj ModMatchExpression::getSerializedRightHandSide() const { - return BSON("$mod" << BSON_ARRAY(_divisor << _remainder)); +BSONObj ModMatchExpression::getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const { + if (auto str = replacementForLiteralArgs) { + return BSON("$mod" << *str); + } else { + return BSON("$mod" << BSON_ARRAY(_divisor << _remainder)); + } } bool ModMatchExpression::equivalent(const MatchExpression* other) const { @@ -402,8 +413,13 @@ void ExistsMatchExpression::debugString(StringBuilder& debug, int indentationLev debug << "\n"; } -BSONObj ExistsMatchExpression::getSerializedRightHandSide() const { - return BSON("$exists" << true); +BSONObj ExistsMatchExpression::getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const { + if (replacementForLiteralArgs) { + return BSON("$exists" << *replacementForLiteralArgs); + } else { + return BSON("$exists" << true); + } } bool ExistsMatchExpression::equivalent(const MatchExpression* other) const { @@ -487,7 +503,13 @@ void InMatchExpression::debugString(StringBuilder& debug, int indentationLevel) debug << "\n"; } -BSONObj InMatchExpression::getSerializedRightHandSide() const { +BSONObj InMatchExpression::getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const { + if (replacementForLiteralArgs) { + // In this case, treat an '$in' with any number of arguments as equivalent. + return BSON("$in" << BSON_ARRAY(*replacementForLiteralArgs)); + } + BSONObjBuilder inBob; BSONArrayBuilder arrBob(inBob.subarrayStart("$in")); for (auto&& _equality : _equalitySet) { @@ -841,7 +863,8 @@ void BitTestMatchExpression::debugString(StringBuilder& debug, int indentationLe } } -BSONObj BitTestMatchExpression::getSerializedRightHandSide() const { +BSONObj BitTestMatchExpression::getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const { std::string opString = ""; switch (matchType()) { @@ -861,6 +884,10 @@ BSONObj BitTestMatchExpression::getSerializedRightHandSide() const { MONGO_UNREACHABLE; } + if (replacementForLiteralArgs) { + return BSON(opString << *replacementForLiteralArgs); + } + BSONArrayBuilder arrBob; for (auto bitPosition : _bitPositions) { arrBob.append(static_cast<int32_t>(bitPosition)); diff --git a/src/mongo/db/matcher/expression_leaf.h b/src/mongo/db/matcher/expression_leaf.h index 64c20b4b2fe..f18c55e9cc3 100644 --- a/src/mongo/db/matcher/expression_leaf.h +++ b/src/mongo/db/matcher/expression_leaf.h @@ -161,7 +161,8 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel = 0) const; - BSONObj getSerializedRightHandSide() const final; + BSONObj getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const final; virtual bool equivalent(const MatchExpression* other) const; @@ -506,7 +507,8 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel) const; - BSONObj getSerializedRightHandSide() const final; + BSONObj getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const final; void serializeToBSONTypeRegex(BSONObjBuilder* out) const; @@ -586,7 +588,8 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel) const; - BSONObj getSerializedRightHandSide() const final; + BSONObj getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const final; virtual bool equivalent(const MatchExpression* other) const; @@ -651,7 +654,8 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel) const; - BSONObj getSerializedRightHandSide() const final; + BSONObj getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const final; virtual bool equivalent(const MatchExpression* other) const; @@ -683,7 +687,8 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel) const; - BSONObj getSerializedRightHandSide() const final; + BSONObj getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const final; virtual bool equivalent(const MatchExpression* other) const; @@ -826,7 +831,8 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel) const; - BSONObj getSerializedRightHandSide() const final; + BSONObj getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const final; virtual bool equivalent(const MatchExpression* other) const; diff --git a/src/mongo/db/matcher/expression_optimize_test.cpp b/src/mongo/db/matcher/expression_optimize_test.cpp index 204a2ed10ad..1cc91d127e4 100644 --- a/src/mongo/db/matcher/expression_optimize_test.cpp +++ b/src/mongo/db/matcher/expression_optimize_test.cpp @@ -351,18 +351,14 @@ 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)); - BSONObjBuilder bob; - optimizedMatchExpression->serialize(&bob, true); - ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{$alwaysFalse: 1}")); + ASSERT_BSONOBJ_EQ(optimizedMatchExpression->serialize(), 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)); - BSONObjBuilder bob; - optimizedMatchExpression->serialize(&bob, true); - ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{a: {$eq: 1}}")); + ASSERT_BSONOBJ_EQ(optimizedMatchExpression->serialize(), fromjson("{a: {$eq: 1}}")); } TEST(ExpressionOptimizeTest, AndWithSingleChildAlwaysTrueOptimizesToEmptyAnd) { @@ -371,9 +367,7 @@ 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())); - BSONObjBuilder bob; - optimizedMatchExpression->serialize(&bob, true); - ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{}")); + ASSERT_BSONOBJ_EQ(optimizedMatchExpression->serialize(), fromjson("{}")); } TEST(ExpressionOptimizeTest, AndWithEachChildAlwaysTrueOptimizesToEmptyAnd) { @@ -382,36 +376,28 @@ 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())); - BSONObjBuilder bob; - optimizedMatchExpression->serialize(&bob, true); - ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{}")); + ASSERT_BSONOBJ_EQ(optimizedMatchExpression->serialize(), 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)); - BSONObjBuilder bob; - optimizedMatchExpression->serialize(&bob, true); - ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{$alwaysFalse: 1}")); + ASSERT_BSONOBJ_EQ(optimizedMatchExpression->serialize(), 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)); - BSONObjBuilder bob; - optimizedMatchExpression->serialize(&bob, true); - ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{$alwaysTrue: 1}")); + ASSERT_BSONOBJ_EQ(optimizedMatchExpression->serialize(), 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)); - BSONObjBuilder bob; - optimizedMatchExpression->serialize(&bob, true); - ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{a: {$eq: 1}}")); + ASSERT_BSONOBJ_EQ(optimizedMatchExpression->serialize(), fromjson("{a: {$eq: 1}}")); } TEST(ExpressionOptimizeTest, OrPromotesSingleAlwaysFalseAfterOptimize) { @@ -420,9 +406,7 @@ TEST(ExpressionOptimizeTest, OrPromotesSingleAlwaysFalseAfterOptimize) { std::unique_ptr<MatchExpression> matchExpression(parseMatchExpression(obj)); auto optimizedMatchExpression = MatchExpression::optimize(std::move(matchExpression)); ASSERT_TRUE(dynamic_cast<AlwaysFalseMatchExpression*>(optimizedMatchExpression.get())); - BSONObjBuilder bob; - optimizedMatchExpression->serialize(&bob, true); - ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{$alwaysFalse: 1}")); + ASSERT_BSONOBJ_EQ(optimizedMatchExpression->serialize(), fromjson("{$alwaysFalse: 1}")); } TEST(ExpressionOptimizeTest, OrPromotesSingleAlwaysFalse) { @@ -430,9 +414,7 @@ TEST(ExpressionOptimizeTest, OrPromotesSingleAlwaysFalse) { std::unique_ptr<MatchExpression> matchExpression(parseMatchExpression(obj)); auto optimizedMatchExpression = MatchExpression::optimize(std::move(matchExpression)); ASSERT_TRUE(dynamic_cast<AlwaysFalseMatchExpression*>(optimizedMatchExpression.get())); - BSONObjBuilder bob; - optimizedMatchExpression->serialize(&bob, true); - ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{$alwaysFalse: 1}")); + ASSERT_BSONOBJ_EQ(optimizedMatchExpression->serialize(), fromjson("{$alwaysFalse: 1}")); } TEST(ExpressionOptimizeTest, OrPromotesMultipleAlwaysFalse) { @@ -440,18 +422,14 @@ TEST(ExpressionOptimizeTest, OrPromotesMultipleAlwaysFalse) { std::unique_ptr<MatchExpression> matchExpression(parseMatchExpression(obj)); auto optimizedMatchExpression = MatchExpression::optimize(std::move(matchExpression)); ASSERT_TRUE(dynamic_cast<AlwaysFalseMatchExpression*>(optimizedMatchExpression.get())); - BSONObjBuilder bob; - optimizedMatchExpression->serialize(&bob, true); - ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{$alwaysFalse: 1}")); + ASSERT_BSONOBJ_EQ(optimizedMatchExpression->serialize(), 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)); - BSONObjBuilder bob; - optimizedMatchExpression->serialize(&bob, true); - ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{$alwaysTrue: 1}")); + ASSERT_BSONOBJ_EQ(optimizedMatchExpression->serialize(), fromjson("{$alwaysTrue: 1}")); } TEST(ExpressionOptimizeTest, OrRewrittenToIn) { @@ -478,9 +456,7 @@ TEST(ExpressionOptimizeTest, OrRewrittenToIn) { auto obj = fromjson(exprStr); std::unique_ptr<MatchExpression> matchExpression(parseMatchExpression(obj)); auto optimizedMatchExpression = MatchExpression::optimize(std::move(matchExpression)); - BSONObjBuilder bob; - optimizedMatchExpression->serialize(&bob, true); - return bob.obj(); + return optimizedMatchExpression->serialize(); }; ASSERT_BSONOBJ_EQ(optimizeExpr(queries[0].first), fromjson(queries[0].second)); diff --git a/src/mongo/db/matcher/expression_path.h b/src/mongo/db/matcher/expression_path.h index 99387a2c080..14a1fbf2edb 100644 --- a/src/mongo/db/matcher/expression_path.h +++ b/src/mongo/db/matcher/expression_path.h @@ -143,11 +143,17 @@ public: } } - void serialize(BSONObjBuilder* out, bool includePath) const override { - if (includePath) { - out->append(path(), getSerializedRightHandSide()); + void serialize(BSONObjBuilder* out, SerializationOptions opts) const override { + // TODO SERVER-73678 do we need to pass 'includePath' or other options to + // `getSerializedRightHandSide()` here? I don't think we need 'includePath' for + // LeafMatchExpression subclasses, but the class comment on PathMatchExpression leaves me a + // bit confused over 'includePath' semantics here. Before we changed anything for query + // shape, it looks like 'includePath' was not forwarded through, so it's either not needed + // or there was a pre-existing bug. + if (opts.includePath) { + out->append(path(), getSerializedRightHandSide(opts.replacementForLiteralArgs)); } else { - out->appendElements(getSerializedRightHandSide()); + out->appendElements(getSerializedRightHandSide(opts.replacementForLiteralArgs)); } } @@ -156,8 +162,13 @@ public: * 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. + * + * If the 'replacementForLiteralArgs' option is set, then any literal argument (like the number + * 1 in the example above), should be replaced with this string. 'literal' here is in contrast + * to another expression, if that is possible syntactically. */ - virtual BSONObj getSerializedRightHandSide() const = 0; + virtual BSONObj getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs = boost::none) const = 0; private: // ElementPath holds a FieldRef, which owns the underlying path string. diff --git a/src/mongo/db/matcher/expression_serialization_test.cpp b/src/mongo/db/matcher/expression_serialization_test.cpp index 78517a52634..214c0d31ffb 100644 --- a/src/mongo/db/matcher/expression_serialization_test.cpp +++ b/src/mongo/db/matcher/expression_serialization_test.cpp @@ -49,9 +49,7 @@ using std::string; using std::unique_ptr; BSONObj serialize(MatchExpression* match) { - BSONObjBuilder bob; - match->serialize(&bob, true); - return bob.obj(); + return match->serialize(); } TEST(SerializeBasic, AndExpressionWithOneChildSerializesCorrectly) { diff --git a/src/mongo/db/matcher/expression_text_base.cpp b/src/mongo/db/matcher/expression_text_base.cpp index 56ad05af3d3..68af83a2932 100644 --- a/src/mongo/db/matcher/expression_text_base.cpp +++ b/src/mongo/db/matcher/expression_text_base.cpp @@ -56,7 +56,8 @@ void TextMatchExpressionBase::debugString(StringBuilder& debug, int indentationL debug << "\n"; } -void TextMatchExpressionBase::serialize(BSONObjBuilder* out, bool includePath) const { +void TextMatchExpressionBase::serialize(BSONObjBuilder* out, SerializationOptions opts) const { + // TODO support 'opts' const fts::FTSQuery& ftsQuery = getFTSQuery(); out->append("$text", BSON("$search" << ftsQuery.getQuery() << "$language" << ftsQuery.getLanguage() diff --git a/src/mongo/db/matcher/expression_text_base.h b/src/mongo/db/matcher/expression_text_base.h index 22fc581a2e2..22f15b23bea 100644 --- a/src/mongo/db/matcher/expression_text_base.h +++ b/src/mongo/db/matcher/expression_text_base.h @@ -60,7 +60,9 @@ public: */ virtual const fts::FTSQuery& getFTSQuery() const = 0; - BSONObj getSerializedRightHandSide() const final { + BSONObj getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const final { + // TODO SERVER-73676 is this going to be a problem? I don't see how this is unreachable... MONGO_UNREACHABLE; } @@ -70,7 +72,7 @@ public: void debugString(StringBuilder& debug, int indentationLevel = 0) const final; - void serialize(BSONObjBuilder* out, bool includePath) const final; + void serialize(BSONObjBuilder* out, SerializationOptions opts) 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 df70ad278c3..33f8ac9f07a 100644 --- a/src/mongo/db/matcher/expression_tree.cpp +++ b/src/mongo/db/matcher/expression_tree.cpp @@ -46,10 +46,10 @@ void ListOfMatchExpression::_debugList(StringBuilder& debug, int indentationLeve _expressions[i]->debugString(debug, indentationLevel + 1); } -void ListOfMatchExpression::_listToBSON(BSONArrayBuilder* out, bool includePath) const { +void ListOfMatchExpression::_listToBSON(BSONArrayBuilder* out, SerializationOptions opts) const { for (unsigned i = 0; i < _expressions.size(); i++) { BSONObjBuilder childBob(out->subobjStart()); - _expressions[i]->serialize(&childBob, includePath); + _expressions[i]->serialize(&childBob, opts); } out->doneFast(); } @@ -341,15 +341,16 @@ void AndMatchExpression::debugString(StringBuilder& debug, int indentationLevel) _debugList(debug, indentationLevel); } -void AndMatchExpression::serialize(BSONObjBuilder* out, bool includePath) const { +void AndMatchExpression::serialize(BSONObjBuilder* out, SerializationOptions opts) 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. return; } + // TODO SERVER-73673 this looks correct - just need to test. BSONArrayBuilder arrBob(out->subarrayStart("$and")); - _listToBSON(&arrBob, includePath); + _listToBSON(&arrBob, opts); arrBob.doneFast(); } @@ -385,7 +386,7 @@ void OrMatchExpression::debugString(StringBuilder& debug, int indentationLevel) _debugList(debug, indentationLevel); } -void OrMatchExpression::serialize(BSONObjBuilder* out, bool includePath) const { +void OrMatchExpression::serialize(BSONObjBuilder* out, SerializationOptions opts) 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 @@ -394,7 +395,7 @@ void OrMatchExpression::serialize(BSONObjBuilder* out, bool includePath) const { return; } BSONArrayBuilder arrBob(out->subarrayStart("$or")); - _listToBSON(&arrBob, includePath); + _listToBSON(&arrBob, opts); } bool OrMatchExpression::isTriviallyFalse() const { @@ -427,9 +428,9 @@ void NorMatchExpression::debugString(StringBuilder& debug, int indentationLevel) _debugList(debug, indentationLevel); } -void NorMatchExpression::serialize(BSONObjBuilder* out, bool includePath) const { +void NorMatchExpression::serialize(BSONObjBuilder* out, SerializationOptions opts) const { BSONArrayBuilder arrBob(out->subarrayStart("$nor")); - _listToBSON(&arrBob, includePath); + _listToBSON(&arrBob, opts); } // ------- @@ -442,9 +443,9 @@ void NotMatchExpression::debugString(StringBuilder& debug, int indentationLevel) void NotMatchExpression::serializeNotExpressionToNor(MatchExpression* exp, BSONObjBuilder* out, - bool includePath) { + SerializationOptions opts) { BSONObjBuilder childBob; - exp->serialize(&childBob, includePath); + exp->serialize(&childBob, opts); BSONObj tempObj = childBob.obj(); BSONArrayBuilder tBob(out->subarrayStart("$nor")); @@ -452,23 +453,24 @@ void NotMatchExpression::serializeNotExpressionToNor(MatchExpression* exp, tBob.doneFast(); } -void NotMatchExpression::serialize(BSONObjBuilder* out, bool includePath) const { +void NotMatchExpression::serialize(BSONObjBuilder* out, SerializationOptions opts) const { + // TODO SERVER-73676 respect 'opts.' if (_exp->matchType() == MatchType::AND && _exp->numChildren() == 0) { out->append("$alwaysFalse", 1); return; } - if (!includePath) { + if (!opts.includePath) { BSONObjBuilder notBob(out->subobjStart("$not")); // Our parser does not accept a $and directly within a $not, instead expecting the direct // notation like {x: {$not: {$gt: 5, $lt: 0}}}. We represent such an expression with an AND // 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, includePath); + _exp->getChild(x)->serialize(¬Bob, opts); } } else { - _exp->serialize(¬Bob, includePath); + _exp->serialize(¬Bob, opts); } return; } @@ -491,10 +493,11 @@ void NotMatchExpression::serialize(BSONObjBuilder* out, bool includePath) const pathMatch && !dynamic_cast<TextMatchExpressionBase*>(expressionToNegate)) { const auto path = pathMatch->path(); BSONObjBuilder pathBob(out->subobjStart(path)); - pathBob.append("$not", pathMatch->getSerializedRightHandSide()); + pathBob.append("$not", + pathMatch->getSerializedRightHandSide(opts.replacementForLiteralArgs)); return; } - return serializeNotExpressionToNor(expressionToNegate, out, includePath); + return serializeNotExpressionToNor(expressionToNegate, out, opts); } 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 f5ab4bb5a8e..252538b0ea6 100644 --- a/src/mongo/db/matcher/expression_tree.h +++ b/src/mongo/db/matcher/expression_tree.h @@ -99,7 +99,7 @@ public: protected: void _debugList(StringBuilder& debug, int indentationLevel) const; - void _listToBSON(BSONArrayBuilder* out, bool includePath) const; + void _listToBSON(BSONArrayBuilder* out, SerializationOptions opts) const; private: ExpressionOptimizerFunc getOptimizer() const final; @@ -138,7 +138,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel = 0) const; - virtual void serialize(BSONObjBuilder* out, bool includePath) const; + virtual void serialize(BSONObjBuilder* out, SerializationOptions opts) const; bool isTriviallyTrue() const final; @@ -182,7 +182,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel = 0) const; - virtual void serialize(BSONObjBuilder* out, bool includePath) const; + virtual void serialize(BSONObjBuilder* out, SerializationOptions opts) const; bool isTriviallyFalse() const final; @@ -226,7 +226,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel = 0) const; - virtual void serialize(BSONObjBuilder* out, bool includePath) const; + virtual void serialize(BSONObjBuilder* out, SerializationOptions opts) const; void acceptVisitor(MatchExpressionMutableVisitor* visitor) final { visitor->visit(this); @@ -267,7 +267,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel = 0) const; - virtual void serialize(BSONObjBuilder* out, bool includePath) const; + virtual void serialize(BSONObjBuilder* out, SerializationOptions opts) const; bool equivalent(const MatchExpression* other) const final; @@ -309,7 +309,7 @@ public: private: static void serializeNotExpressionToNor(MatchExpression* exp, BSONObjBuilder* out, - bool includePath); + SerializationOptions opts); ExpressionOptimizerFunc getOptimizer() const final; diff --git a/src/mongo/db/matcher/expression_type.h b/src/mongo/db/matcher/expression_type.h index 95e1416486e..51b3d4ae507 100644 --- a/src/mongo/db/matcher/expression_type.h +++ b/src/mongo/db/matcher/expression_type.h @@ -82,7 +82,9 @@ public: debug << "\n"; } - BSONObj getSerializedRightHandSide() const final { + BSONObj getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const final { + // TODO SERVER-73678 respect 'replacementForLiteralArgs'. BSONObjBuilder subBuilder; BSONArrayBuilder arrBuilder(subBuilder.subarrayStart(name())); _typeSet.toBSONArray(&arrBuilder); @@ -252,7 +254,9 @@ public: debug << "\n"; } - BSONObj getSerializedRightHandSide() const final { + BSONObj getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const final { + // TODO SERVER-73678 respect 'replacementForLiteralArgs'. BSONObjBuilder bob; bob.append(name(), _binDataSubType); return bob.obj(); diff --git a/src/mongo/db/matcher/expression_where_base.cpp b/src/mongo/db/matcher/expression_where_base.cpp index 30148f9a42b..17568f76321 100644 --- a/src/mongo/db/matcher/expression_where_base.cpp +++ b/src/mongo/db/matcher/expression_where_base.cpp @@ -46,7 +46,8 @@ void WhereMatchExpressionBase::debugString(StringBuilder& debug, int indentation debug << "code: " << getCode() << "\n"; } -void WhereMatchExpressionBase::serialize(BSONObjBuilder* out, bool includePath) const { +void WhereMatchExpressionBase::serialize(BSONObjBuilder* out, SerializationOptions opts) const { + // TODO SERVER-73676 respect 'opts.' out->appendCode("$where", getCode()); } diff --git a/src/mongo/db/matcher/expression_where_base.h b/src/mongo/db/matcher/expression_where_base.h index 3f5099d7180..6e029c6bab6 100644 --- a/src/mongo/db/matcher/expression_where_base.h +++ b/src/mongo/db/matcher/expression_where_base.h @@ -69,7 +69,7 @@ public: void debugString(StringBuilder& debug, int indentationLevel = 0) const final; - void serialize(BSONObjBuilder* out, bool includePath) const final; + void serialize(BSONObjBuilder* out, SerializationOptions opts) 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 new file mode 100644 index 00000000000..89e965a45ca --- /dev/null +++ b/src/mongo/db/matcher/parsed_match_expression_for_test.h @@ -0,0 +1,75 @@ +/** + * 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); + 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_test.cpp b/src/mongo/db/matcher/rewrite_expr_test.cpp index 948add02c26..c4f1a4b7619 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, true); + result.matchExpression()->serialize(&bob, {}); 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 e62b5e62e1d..7f9b3d14292 100644 --- a/src/mongo/db/matcher/schema/assert_serializes_to.h +++ b/src/mongo/db/matcher/schema/assert_serializes_to.h @@ -34,11 +34,9 @@ namespace mongo { /** * Asserts that the given MatchExpression 'match' serializes to the BSONObj 'expected'. */ -#define ASSERT_SERIALIZES_TO(match, expected) \ - do { \ - BSONObjBuilder bob; \ - match->serialize(&bob, true); \ - ASSERT_BSONOBJ_EQ(bob.obj(), expected); \ +#define ASSERT_SERIALIZES_TO(match, expected) \ + do { \ + ASSERT_BSONOBJ_EQ(match->serialize(), 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 52a225a2ee7..6fbeb59be73 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,13 +76,15 @@ void InternalSchemaAllElemMatchFromIndexMatchExpression::debugString(StringBuild _expression->getFilter()->debugString(debug, indentationLevel + 1); } -BSONObj InternalSchemaAllElemMatchFromIndexMatchExpression::getSerializedRightHandSide() const { +BSONObj InternalSchemaAllElemMatchFromIndexMatchExpression::getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const { + // TODO SERVER-73678 respect 'replacementForLiteralArgs'. BSONObjBuilder allElemMatchBob; BSONArrayBuilder subArray(allElemMatchBob.subarrayStart(kName)); subArray.append(_index); { BSONObjBuilder eBuilder(subArray.subobjStart()); - _expression->getFilter()->serialize(&eBuilder, true); + _expression->getFilter()->serialize(&eBuilder, {}); eBuilder.doneFast(); } subArray.doneFast(); 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 a73c5a255cb..9ca495448f9 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 @@ -78,7 +78,8 @@ public: void debugString(StringBuilder& debug, int indentationLevel) const final; - BSONObj getSerializedRightHandSide() const final; + BSONObj getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const final; bool equivalent(const MatchExpression* other) const final; 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 861bdc2d989..5d86e5571d2 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 @@ -60,7 +60,7 @@ void InternalSchemaAllowedPropertiesMatchExpression::debugString(StringBuilder& _debugAddSpace(debug, indentationLevel); BSONObjBuilder builder; - serialize(&builder, true); + serialize(&builder, {}); debug << builder.obj().toString() << "\n"; const auto* tag = getTag(); @@ -129,7 +129,8 @@ bool InternalSchemaAllowedPropertiesMatchExpression::_matchesBSONObj(const BSONO } void InternalSchemaAllowedPropertiesMatchExpression::serialize(BSONObjBuilder* builder, - bool includePath) const { + SerializationOptions opts) const { + // TODO SERVER-73678 respect 'opts'. BSONObjBuilder expressionBuilder( builder->subobjStart(InternalSchemaAllowedPropertiesMatchExpression::kName)); @@ -145,13 +146,13 @@ void InternalSchemaAllowedPropertiesMatchExpression::serialize(BSONObjBuilder* b itemBuilder.appendRegex("regex", item.first.rawRegex); BSONObjBuilder subexpressionBuilder(itemBuilder.subobjStart("expression")); - item.second->getFilter()->serialize(&subexpressionBuilder, includePath); + item.second->getFilter()->serialize(&subexpressionBuilder, opts); subexpressionBuilder.doneFast(); } patternPropertiesBuilder.doneFast(); BSONObjBuilder otherwiseBuilder(expressionBuilder.subobjStart("otherwise")); - _otherwise->getFilter()->serialize(&otherwiseBuilder, includePath); + _otherwise->getFilter()->serialize(&otherwiseBuilder, opts); 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 6c23028eac6..052b10a4b78 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,7 +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, bool includePath) const final; + void serialize(BSONObjBuilder* builder, SerializationOptions opts) 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 eda67973bed..496dac6a29d 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_eq.cpp +++ b/src/mongo/db/matcher/schema/expression_internal_schema_eq.cpp @@ -69,7 +69,9 @@ void InternalSchemaEqMatchExpression::debugString(StringBuilder& debug, debug << "\n"; } -BSONObj InternalSchemaEqMatchExpression::getSerializedRightHandSide() const { +BSONObj InternalSchemaEqMatchExpression::getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const { + // TODO SERVER-73678 respect 'replacementForLiteralArgs.' BSONObjBuilder eqObj; eqObj.appendAs(_rhsElem, kName); return eqObj.obj(); 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 cc30f428c8c..9accba917d0 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_eq.h +++ b/src/mongo/db/matcher/schema/expression_internal_schema_eq.h @@ -59,7 +59,8 @@ public: void debugString(StringBuilder& debug, int indentationLevel) const final; - BSONObj getSerializedRightHandSide() const final; + BSONObj getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) 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 8ac81ead25f..887f382a0ad 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_fmod.cpp +++ b/src/mongo/db/matcher/schema/expression_internal_schema_fmod.cpp @@ -76,7 +76,9 @@ void InternalSchemaFmodMatchExpression::debugString(StringBuilder& debug, debug << "\n"; } -BSONObj InternalSchemaFmodMatchExpression::getSerializedRightHandSide() const { +BSONObj InternalSchemaFmodMatchExpression::getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const { + // TODO SERVER-73678 respect 'replacementForLiteralArgs'. BSONObjBuilder objMatchBob; BSONArrayBuilder arrBuilder(objMatchBob.subarrayStart("$_internalSchemaFmod")); arrBuilder.append(_divisor); 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 29edd030372..cc426c810a2 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_fmod.h +++ b/src/mongo/db/matcher/schema/expression_internal_schema_fmod.h @@ -58,7 +58,8 @@ public: void debugString(StringBuilder& debug, int indentationLevel) const final; - BSONObj getSerializedRightHandSide() const final; + BSONObj getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) 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 3e44ce00c01..a3b53e6890f 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, true); + serialize(&builder, {}); debug << builder.obj().toString() << "\n"; const auto* tag = getTag(); @@ -72,7 +72,9 @@ bool InternalSchemaMatchArrayIndexMatchExpression::equivalent(const MatchExpress _expression->equivalent(other->_expression.get()); } -BSONObj InternalSchemaMatchArrayIndexMatchExpression::getSerializedRightHandSide() const { +BSONObj InternalSchemaMatchArrayIndexMatchExpression::getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const { + // TODO SERVER-73678 respect 'replacementForLiteralArgs'. BSONObjBuilder objBuilder; { BSONObjBuilder matchArrayElemSubobj(objBuilder.subobjStart(kName)); @@ -80,7 +82,7 @@ BSONObj InternalSchemaMatchArrayIndexMatchExpression::getSerializedRightHandSide matchArrayElemSubobj.append("namePlaceholder", _expression->getPlaceholder().value_or("")); { BSONObjBuilder subexprSubObj(matchArrayElemSubobj.subobjStart("expression")); - _expression->getFilter()->serialize(&subexprSubObj, true); + _expression->getFilter()->serialize(&subexprSubObj, {}); subexprSubObj.doneFast(); } matchArrayElemSubobj.doneFast(); 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 623e32a7e1e..56d115cc5ab 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 @@ -74,7 +74,8 @@ public: return _expression->matchesBSONElement(element, details); } - BSONObj getSerializedRightHandSide() const final; + BSONObj getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) 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 4e87b7fece1..705f726e447 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,7 +56,9 @@ void InternalSchemaNumArrayItemsMatchExpression::debugString(StringBuilder& debu debug << "\n"; } -BSONObj InternalSchemaNumArrayItemsMatchExpression::getSerializedRightHandSide() const { +BSONObj InternalSchemaNumArrayItemsMatchExpression::getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const { + // TODO SERVER-73678 respect 'replacementForLiteralArgs'. BSONObjBuilder objBuilder; objBuilder.append(_name, _numItems); return objBuilder.obj(); 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 d651a006dbd..dc046bda15d 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,7 +52,8 @@ public: void debugString(StringBuilder& debug, int indentationLevel) const final; - BSONObj getSerializedRightHandSide() const final; + BSONObj getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) 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 e640a58ec71..3dd54f0f94f 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,12 +37,13 @@ void InternalSchemaNumPropertiesMatchExpression::debugString(StringBuilder& debu int indentationLevel) const { _debugAddSpace(debug, indentationLevel); BSONObjBuilder builder; - serialize(&builder, true); + serialize(&builder, {}); debug << builder.obj().toString() << "\n"; } void InternalSchemaNumPropertiesMatchExpression::serialize(BSONObjBuilder* out, - bool includePath) const { + SerializationOptions opts) const { + // TODO SERVER-73678 respect 'opts'. out->append(_name, _numProperties); } 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 260cbea7812..845e8822476 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,7 +71,7 @@ public: void debugString(StringBuilder& debug, int indentationLevel) const final; - void serialize(BSONObjBuilder* out, bool includePath) const final; + void serialize(BSONObjBuilder* out, SerializationOptions opts) 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 ca33ea3025e..e057d5c2d26 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,10 +61,12 @@ void InternalSchemaObjectMatchExpression::debugString(StringBuilder& debug, _sub->debugString(debug, indentationLevel + 1); } -BSONObj InternalSchemaObjectMatchExpression::getSerializedRightHandSide() const { +BSONObj InternalSchemaObjectMatchExpression::getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const { + // TODO SERVER-73678 respect 'replacementForLiteralArgs'. BSONObjBuilder objMatchBob; BSONObjBuilder subBob(objMatchBob.subobjStart(kName)); - _sub->serialize(&subBob, true); + _sub->serialize(&subBob, {}); subBob.doneFast(); return objMatchBob.obj(); } 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 8d131e85d6c..44e74fdd8bc 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 @@ -50,7 +50,8 @@ public: void debugString(StringBuilder& debug, int indentationLevel = 0) const final; - BSONObj getSerializedRightHandSide() const final; + BSONObj getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) 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 c4b1e3f7aa2..c9d607b6c0a 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,7 +55,8 @@ void InternalSchemaRootDocEqMatchExpression::debugString(StringBuilder& debug, } void InternalSchemaRootDocEqMatchExpression::serialize(BSONObjBuilder* out, - bool includePath) const { + SerializationOptions opts) const { + // TODO SERVER-73678 respect 'opts.' BSONObjBuilder subObj(out->subobjStart(kName)); 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 c9ff8c20fe3..571bf32d3e6 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,7 +72,7 @@ public: void debugString(StringBuilder& debug, int indentationLevel = 0) const final; - void serialize(BSONObjBuilder* out, bool includePath) const final; + void serialize(BSONObjBuilder* out, SerializationOptions opts) 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 9bba7d99b43..b643393b548 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,7 +56,9 @@ void InternalSchemaStrLengthMatchExpression::debugString(StringBuilder& debug, debug << "\n"; } -BSONObj InternalSchemaStrLengthMatchExpression::getSerializedRightHandSide() const { +BSONObj InternalSchemaStrLengthMatchExpression::getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const { + // TODO SERVER-73678 respect 'replacementForLiteralArgs'. BSONObjBuilder objBuilder; objBuilder.append(_name, _strLen); return objBuilder.obj(); 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 027d5254be7..8c10f3f5253 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,7 +62,8 @@ public: void debugString(StringBuilder& debug, int indentationLevel) const final; - BSONObj getSerializedRightHandSide() const final; + BSONObj getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) 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 f78dca0c37f..a5dcfd9c9cd 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, true); + serialize(&builder, {}); debug << builder.obj().toString() << "\n"; const auto* tag = getTag(); @@ -59,7 +59,9 @@ bool InternalSchemaUniqueItemsMatchExpression::equivalent(const MatchExpression* return path() == other->path(); } -BSONObj InternalSchemaUniqueItemsMatchExpression::getSerializedRightHandSide() const { +BSONObj InternalSchemaUniqueItemsMatchExpression::getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) const { + // TODO SERVER-73678 respect 'replacementForLiteralArgs.' BSONObjBuilder bob; bob.append(kName, true); return bob.obj(); 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 77fce7be0d0..20f78d85bc4 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,7 +86,8 @@ public: bool equivalent(const MatchExpression* other) const final; - BSONObj getSerializedRightHandSide() const final; + BSONObj getSerializedRightHandSide( + boost::optional<StringData> replacementForLiteralArgs) 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 ad265266f52..50b4b813470 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_xor.cpp +++ b/src/mongo/db/matcher/schema/expression_internal_schema_xor.cpp @@ -73,8 +73,9 @@ void InternalSchemaXorMatchExpression::debugString(StringBuilder& debug, _debugList(debug, indentationLevel); } -void InternalSchemaXorMatchExpression::serialize(BSONObjBuilder* out, bool includePath) const { +void InternalSchemaXorMatchExpression::serialize(BSONObjBuilder* out, + SerializationOptions opts) const { BSONArrayBuilder arrBob(out->subarrayStart(kName)); - _listToBSON(&arrBob, includePath); + _listToBSON(&arrBob, opts); } } // 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 928a13413f1..2755098390c 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_xor.h +++ b/src/mongo/db/matcher/schema/expression_internal_schema_xor.h @@ -69,7 +69,7 @@ public: void debugString(StringBuilder& debug, int indentationLevel = 0) const final; - void serialize(BSONObjBuilder* out, bool includePath) const final; + void serialize(BSONObjBuilder* out, SerializationOptions opts) 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 73bb4b7d7dd..468c0bb8cc9 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, true); + MatchExpression::optimize(std::move(typeResult.getValue()))->serialize(&typeBuilder, {}); BSONObjBuilder bsonTypeBuilder; MatchExpression::optimize(std::move(bsonTypeResult.getValue())) - ->serialize(&bsonTypeBuilder, true); + ->serialize(&bsonTypeBuilder, {}); ASSERT_BSONOBJ_EQ(typeBuilder.obj(), bsonTypeBuilder.obj()); } diff --git a/src/mongo/db/pipeline/document_source_internal_unpack_bucket.cpp b/src/mongo/db/pipeline/document_source_internal_unpack_bucket.cpp index 256804b978f..5a36703db13 100644 --- a/src/mongo/db/pipeline/document_source_internal_unpack_bucket.cpp +++ b/src/mongo/db/pipeline/document_source_internal_unpack_bucket.cpp @@ -1373,9 +1373,8 @@ Pipeline::SourceContainer::iterator DocumentSourceInternalUnpackBucket::doOptimi // Create a loose bucket predicate and push it before the unpacking stage. if (predicates.loosePredicate) { - BSONObjBuilder bob; - predicates.loosePredicate->serialize(&bob); - container->insert(itr, DocumentSourceMatch::create(bob.obj(), pExpCtx)); + container->insert( + itr, DocumentSourceMatch::create(predicates.loosePredicate->serialize(), pExpCtx)); // Give other stages a chance to optimize with the new $match. return std::prev(itr) == container->begin() ? std::prev(itr) diff --git a/src/mongo/db/pipeline/document_source_internal_unpack_bucket_test/create_predicates_on_bucket_level_field_test.cpp b/src/mongo/db/pipeline/document_source_internal_unpack_bucket_test/create_predicates_on_bucket_level_field_test.cpp index 63d3b4a0b23..a054bee04dc 100644 --- a/src/mongo/db/pipeline/document_source_internal_unpack_bucket_test/create_predicates_on_bucket_level_field_test.cpp +++ b/src/mongo/db/pipeline/document_source_internal_unpack_bucket_test/create_predicates_on_bucket_level_field_test.cpp @@ -55,7 +55,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) ->createPredicatesOnBucketLevelField(original->getMatchExpression()); - ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(true), + ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(), fromjson("{$or: [ {'control.max.a': {$_internalExprGt: 1}}," "{$expr: {$ne: [ {$type: [ \"$control.min.a\" ]}," "{$type: [ \"$control.max.a\" ]} ]}} ]}")); @@ -77,7 +77,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) ->createPredicatesOnBucketLevelField(original->getMatchExpression()); - ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(true), + ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(), fromjson("{$or: [ {'control.max.a': {$_internalExprGte: 1}}," "{$expr: {$ne: [ {$type: [ \"$control.min.a\" ]}," "{$type: [ \"$control.max.a\" ]} ]}} ]}")); @@ -99,7 +99,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) ->createPredicatesOnBucketLevelField(original->getMatchExpression()); - ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(true), + ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(), fromjson("{$or: [ {'control.min.a': {$_internalExprLt: 1}}," "{$expr: {$ne: [ {$type: [ \"$control.min.a\" ]}," "{$type: [ \"$control.max.a\" ]} ]}} ]}")); @@ -121,7 +121,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) ->createPredicatesOnBucketLevelField(original->getMatchExpression()); - ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(true), + ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(), fromjson("{$or: [ {'control.min.a': {$_internalExprLte: 1}}," "{$expr: {$ne: [ {$type: [ \"$control.min.a\" ]}," "{$type: [ \"$control.max.a\" ]} ]}} ]}")); @@ -143,7 +143,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) ->createPredicatesOnBucketLevelField(original->getMatchExpression()); - ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(true), + ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(), fromjson("{$or: [ {$and:[{'control.min.a': {$_internalExprLte: 1}}," "{'control.max.a': {$_internalExprGte: 1}}]}," "{$expr: {$ne: [ {$type: [ \"$control.min.a\" ]}," @@ -190,7 +190,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, " ]}}" " ]}" "]}"); - ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(true), expected); + ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(), expected); ASSERT_FALSE(predicate.tightPredicate); } @@ -214,7 +214,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) ->createPredicatesOnBucketLevelField(original->getMatchExpression()); - ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(true), + ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(), fromjson("{$or: [ {'control.max.a': {$_internalExprGt: 1}}," "{$expr: {$ne: [ {$type: [ \"$control.min.a\" ]}," "{$type: [ \"$control.max.a\" ]} ]}} ]}")); @@ -241,7 +241,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) ->createPredicatesOnBucketLevelField(original->getMatchExpression()); - ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(true), + ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(), fromjson("{$or: [ {'control.max.a': {$_internalExprGte: 1}}," "{$expr: {$ne: [ {$type: [ \"$control.min.a\" ]}," "{$type: [ \"$control.max.a\" ]} ]}} ]}")); @@ -268,7 +268,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) ->createPredicatesOnBucketLevelField(original->getMatchExpression()); - ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(true), + ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(), fromjson("{$or: [ {'control.min.a': {$_internalExprLt: 1}}," "{$expr: {$ne: [ {$type: [ \"$control.min.a\" ]}," "{$type: [ \"$control.max.a\" ]} ]}} ]}")); @@ -295,7 +295,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) ->createPredicatesOnBucketLevelField(original->getMatchExpression()); - ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(true), + ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(), fromjson("{$or: [ {'control.min.a': {$_internalExprLte: 1}}," "{$expr: {$ne: [ {$type: [ \"$control.min.a\" ]}," "{$type: [ \"$control.max.a\" ]} ]}} ]}")); @@ -322,7 +322,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) ->createPredicatesOnBucketLevelField(original->getMatchExpression()); - ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(true), + ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(), fromjson("{$or: [ {$and:[{'control.min.a': {$_internalExprLte: 1}}," "{'control.max.a': {$_internalExprGte: 1}}]}," "{$expr: {$ne: [ {$type: [ \"$control.min.a\" ]}," @@ -345,7 +345,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) ->createPredicatesOnBucketLevelField(original->getMatchExpression()); - ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(true), + ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(), fromjson("{$and: [ {$or: [ {'control.max.b': {$_internalExprGt: 1}}," "{$expr: {$ne: [ {$type: [ \"$control.min.b\" ]}," "{$type: [ \"$control.max.b\" ]} ]}} ]}," @@ -389,7 +389,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) ->createPredicatesOnBucketLevelField(original->getMatchExpression()); - ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(true), + ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(), fromjson("{$or: [" " {'control.max.b': {$_internalExprGt: 1}}," " {$expr: {$ne: [" @@ -416,7 +416,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) ->createPredicatesOnBucketLevelField(original->getMatchExpression()); - ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(true), + ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(), fromjson("{$and: [ {$or: [ {'control.max.b': {$_internalExprGte: 2}}," "{$expr: {$ne: [ {$type: [ \"$control.min.b\" ]}," "{$type: [ \"$control.max.b\" ]} ]}} ]}," @@ -445,7 +445,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, ->createPredicatesOnBucketLevelField(original->getMatchExpression()); ASSERT(predicate.loosePredicate); - ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(true), + ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(), fromjson("{$or: [" " {$or: [" " {'control.max.b': {$_internalExprGt: 1}}," @@ -522,7 +522,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) ->createPredicatesOnBucketLevelField(original->getMatchExpression()); - ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(true), + ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(), fromjson("{$or: [" " {$or: [" " {'control.max.b': {$_internalExprGte: 2}}," @@ -683,8 +683,8 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, ->createPredicatesOnBucketLevelField(original->getMatchExpression()); // Meta predicates are mapped to the meta field, not the control min/max fields. - ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(true), fromjson("{meta: {$gt: 5}}")); - ASSERT_BSONOBJ_EQ(predicate.tightPredicate->serialize(true), fromjson("{meta: {$gt: 5}}")); + ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(), fromjson("{meta: {$gt: 5}}")); + ASSERT_BSONOBJ_EQ(predicate.tightPredicate->serialize(), fromjson("{meta: {$gt: 5}}")); } TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, @@ -703,10 +703,8 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, ->createPredicatesOnBucketLevelField(original->getMatchExpression()); // Meta predicates are mapped to the meta field, not the control min/max fields. - ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(true), - fromjson("{'meta.foo': {$gt: 5}}")); - ASSERT_BSONOBJ_EQ(predicate.tightPredicate->serialize(true), - fromjson("{'meta.foo': {$gt: 5}}")); + ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(), fromjson("{'meta.foo': {$gt: 5}}")); + ASSERT_BSONOBJ_EQ(predicate.tightPredicate->serialize(), fromjson("{'meta.foo': {$gt: 5}}")); } TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, @@ -724,7 +722,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) ->createPredicatesOnBucketLevelField(original->getMatchExpression()); - ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(true), + ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(), fromjson("{$and: [" " {$or: [" " {'control.max.a': {$_internalExprGt: 1}}," @@ -776,10 +774,10 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, OptimizeMapsTimePre auto children = andExpr->getChildVector(); ASSERT_EQ(children->size(), 3); - ASSERT_BSONOBJ_EQ((*children)[0]->serialize(true), + ASSERT_BSONOBJ_EQ((*children)[0]->serialize(), BSON("control.min.time" << BSON("$_internalExprLt" << date))); ASSERT_BSONOBJ_EQ( - (*children)[1]->serialize(true), + (*children)[1]->serialize(), BSON("control.max.time" << BSON("$_internalExprLt" << datePlusBucketSpan))); auto idPred = dynamic_cast<ComparisonMatchExpressionBase*>((*children)[2].get()); @@ -833,10 +831,10 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, OptimizeMapsTimePre auto children = andExpr->getChildVector(); ASSERT_EQ(children->size(), 3); - ASSERT_BSONOBJ_EQ((*children)[0]->serialize(true), + ASSERT_BSONOBJ_EQ((*children)[0]->serialize(), BSON("control.min.time" << BSON("$_internalExprLte" << date))); ASSERT_BSONOBJ_EQ( - (*children)[1]->serialize(true), + (*children)[1]->serialize(), BSON("control.max.time" << BSON("$_internalExprLte" << datePlusBucketSpan))); auto idPred = dynamic_cast<ComparisonMatchExpressionBase*>((*children)[2].get()); @@ -882,15 +880,15 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, OptimizeMapsTimePre auto children = andExpr->getChildVector(); ASSERT_EQ(children->size(), 6); - ASSERT_BSONOBJ_EQ((*children)[0]->serialize(true), + ASSERT_BSONOBJ_EQ((*children)[0]->serialize(), BSON("control.min.time" << BSON("$_internalExprLte" << date))); ASSERT_BSONOBJ_EQ( - (*children)[1]->serialize(true), + (*children)[1]->serialize(), BSON("control.min.time" << BSON("$_internalExprGte" << dateMinusBucketSpan))); - ASSERT_BSONOBJ_EQ((*children)[2]->serialize(true), + ASSERT_BSONOBJ_EQ((*children)[2]->serialize(), BSON("control.max.time" << BSON("$_internalExprGte" << date))); ASSERT_BSONOBJ_EQ( - (*children)[3]->serialize(true), + (*children)[3]->serialize(), BSON("control.max.time" << BSON("$_internalExprLte" << datePlusBucketSpan))); auto idPred = dynamic_cast<ComparisonMatchExpressionBase*>((*children)[4].get()); @@ -944,10 +942,10 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, OptimizeMapsTimePre auto children = andExpr->getChildVector(); ASSERT_EQ(children->size(), 3); - ASSERT_BSONOBJ_EQ((*children)[0]->serialize(true), + ASSERT_BSONOBJ_EQ((*children)[0]->serialize(), BSON("control.max.time" << BSON("$_internalExprGt" << date))); ASSERT_BSONOBJ_EQ( - (*children)[1]->serialize(true), + (*children)[1]->serialize(), BSON("control.min.time" << BSON("$_internalExprGt" << dateMinusBucketSpan))); auto idPred = dynamic_cast<ComparisonMatchExpressionBase*>((*children)[2].get()); @@ -993,10 +991,10 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, OptimizeMapsTimePre auto children = andExpr->getChildVector(); ASSERT_EQ(children->size(), 3); - ASSERT_BSONOBJ_EQ((*children)[0]->serialize(true), + ASSERT_BSONOBJ_EQ((*children)[0]->serialize(), BSON("control.max.time" << BSON("$_internalExprGte" << date))); ASSERT_BSONOBJ_EQ( - (*children)[1]->serialize(true), + (*children)[1]->serialize(), BSON("control.min.time" << BSON("$_internalExprGte" << dateMinusBucketSpan))); auto idPred = dynamic_cast<ComparisonMatchExpressionBase*>((*children)[2].get()); @@ -1144,7 +1142,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) ->createPredicatesOnBucketLevelField(original->getMatchExpression()); - ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(true), + ASSERT_BSONOBJ_EQ(predicate.loosePredicate->serialize(), fromjson("{$_internalBucketGeoWithin: { withinRegion: { $geometry: { type : " "\"Polygon\" ,coordinates: [ [ [ 0, 0 ], [ 3, 6 ], [ 6, 1 ], [ 0, 0 " "] ] ]}},field: \"loc\"}}")); diff --git a/src/mongo/db/pipeline/document_source_lookup_test.cpp b/src/mongo/db/pipeline/document_source_lookup_test.cpp index 1682a375569..e1582b23a5a 100644 --- a/src/mongo/db/pipeline/document_source_lookup_test.cpp +++ b/src/mongo/db/pipeline/document_source_lookup_test.cpp @@ -1139,13 +1139,10 @@ TEST_F(DocumentSourceLookUpTest, ExprEmbeddedInMatchExpressionShouldBeOptimized) auto& matchSource = dynamic_cast<const DocumentSourceMatch&>(*secondSource); // Ensure that the '$$var' in the embedded expression got optimized to ExpressionConstant. - BSONObjBuilder builder; - matchSource.getMatchExpression()->serialize(&builder); - auto serializedMatch = builder.obj(); auto expectedMatch = fromjson("{$and: [{_id: {$_internalExprEq: 5}}, {$expr: {$eq: ['$_id', {$const: 5}]}}]}"); - ASSERT_VALUE_EQ(Value(serializedMatch), Value(expectedMatch)); + ASSERT_VALUE_EQ(Value(matchSource.getMatchExpression()->serialize()), Value(expectedMatch)); } TEST_F(DocumentSourceLookUpTest, diff --git a/src/mongo/db/pipeline/document_source_match.cpp b/src/mongo/db/pipeline/document_source_match.cpp index d0e0f12ebd8..3deaf5ab453 100644 --- a/src/mongo/db/pipeline/document_source_match.cpp +++ b/src/mongo/db/pipeline/document_source_match.cpp @@ -70,9 +70,7 @@ const char* DocumentSourceMatch::getSourceName() const { Value DocumentSourceMatch::serialize(boost::optional<ExplainOptions::Verbosity> explain) const { if (explain) { - BSONObjBuilder builder; - _expression->serialize(&builder); - return Value(DOC(getSourceName() << Document(builder.obj()))); + return Value(DOC(getSourceName() << Document(_expression->serialize()))); } return Value(DOC(getSourceName() << Document(getQuery()))); } @@ -450,15 +448,11 @@ DocumentSourceMatch::splitSourceByFunc(const OrderedPathSet& fields, // the corresponding BSONObj may not exist. Therefore, we take each of these expressions, // serialize them, and then re-parse them, constructing new BSON that is owned by the // DocumentSourceMatch. - BSONObjBuilder firstBob; - newExpr.first->serialize(&firstBob); - auto firstMatch = DocumentSourceMatch::create(firstBob.obj(), pExpCtx); + auto firstMatch = DocumentSourceMatch::create(newExpr.first->serialize(), pExpCtx); intrusive_ptr<DocumentSourceMatch> secondMatch; if (newExpr.second) { - BSONObjBuilder secondBob; - newExpr.second->serialize(&secondBob); - secondMatch = DocumentSourceMatch::create(secondBob.obj(), pExpCtx); + secondMatch = DocumentSourceMatch::create(newExpr.second->serialize(), pExpCtx); } return {std::move(firstMatch), std::move(secondMatch)}; @@ -491,9 +485,7 @@ boost::intrusive_ptr<DocumentSourceMatch> DocumentSourceMatch::descendMatchOnPat } }); - BSONObjBuilder query; - matchExpr->serialize(&query); - return new DocumentSourceMatch(query.obj(), expCtx); + return new DocumentSourceMatch(matchExpr->serialize(), expCtx); } std::pair<boost::intrusive_ptr<DocumentSourceMatch>, boost::intrusive_ptr<DocumentSourceMatch>> diff --git a/src/mongo/db/pipeline/document_source_match_test.cpp b/src/mongo/db/pipeline/document_source_match_test.cpp index 5d3ac170cae..e8dcd2ce2c0 100644 --- a/src/mongo/db/pipeline/document_source_match_test.cpp +++ b/src/mongo/db/pipeline/document_source_match_test.cpp @@ -606,8 +606,6 @@ DEATH_TEST_REGEX_F(DocumentSourceMatchTest, const auto matchSpec = BSON("a" << BSON("$elemMatch" << BSON("a.b" << 1))); const auto matchExpression = unittest::assertGet(MatchExpressionParser::parse(matchSpec, expCtx)); - BSONObjBuilder out; - matchExpression->serialize(&out); DocumentSourceMatch::descendMatchOnPath(matchExpression.get(), "a", expCtx); } diff --git a/src/mongo/db/query/SConscript b/src/mongo/db/query/SConscript index 305038f5e74..6163d2e3fce 100644 --- a/src/mongo/db/query/SConscript +++ b/src/mongo/db/query/SConscript @@ -23,10 +23,7 @@ env.SConscript( env.Library( target='canonical_query', - source=[ - 'canonical_query.cpp', - 'canonical_query_encoder.cpp', - ], + source=['canonical_query.cpp', 'canonical_query_encoder.cpp', 'query_shape.cpp'], LIBDEPS=[ '$BUILD_DIR/mongo/crypto/encrypted_field_config', '$BUILD_DIR/mongo/db/cst/cst', @@ -439,8 +436,6 @@ env.CppUnitTest( "interval_test.cpp", "killcursors_request_test.cpp", "lru_key_value_test.cpp", - 'map_reduce_output_format_test.cpp', - "util/memory_util_test.cpp", "parsed_distinct_test.cpp", "plan_cache_indexability_test.cpp", "plan_cache_key_info_test.cpp", @@ -456,30 +451,33 @@ env.CppUnitTest( "query_planner_collation_test.cpp", "query_planner_columnar_test.cpp", "query_planner_geo_test.cpp", - "query_planner_pipeline_pushdown_test.cpp", "query_planner_hashed_index_test.cpp", - "query_planner_partialidx_test.cpp", "query_planner_index_test.cpp", "query_planner_operator_test.cpp", "query_planner_options_test.cpp", - "query_planner_tree_test.cpp", + "query_planner_partialidx_test.cpp", + "query_planner_pipeline_pushdown_test.cpp", "query_planner_text_test.cpp", + "query_planner_tree_test.cpp", "query_planner_wildcard_index_test.cpp", "query_request_test.cpp", "query_settings_test.cpp", + "query_shape_test.cpp", "query_solution_test.cpp", "rate_limiting_test.cpp", "sbe_and_hash_test.cpp", "sbe_and_sorted_test.cpp", + "sbe_shard_filter_test.cpp", "sbe_stage_builder_accumulator_test.cpp", "sbe_stage_builder_const_eval_test.cpp", "sbe_stage_builder_lookup_test.cpp", "sbe_stage_builder_test.cpp", "sbe_stage_builder_test_fixture.cpp", - "sbe_shard_filter_test.cpp", "shard_filterer_factory_mock.cpp", "telemetry_store_test.cpp", + "util/memory_util_test.cpp", "view_response_formatter_test.cpp", + 'map_reduce_output_format_test.cpp', ], LIBDEPS=[ "$BUILD_DIR/mongo/db/auth/authmocks", diff --git a/src/mongo/db/query/canonical_query.cpp b/src/mongo/db/query/canonical_query.cpp index 45fc3ef9c41..07bcedfc496 100644 --- a/src/mongo/db/query/canonical_query.cpp +++ b/src/mongo/db/query/canonical_query.cpp @@ -153,9 +153,7 @@ StatusWith<std::unique_ptr<CanonicalQuery>> CanonicalQuery::canonicalize( StatusWith<std::unique_ptr<CanonicalQuery>> CanonicalQuery::canonicalize( OperationContext* opCtx, const CanonicalQuery& baseQuery, MatchExpression* root) { auto findCommand = std::make_unique<FindCommandRequest>(baseQuery.nss()); - BSONObjBuilder builder; - root->serialize(&builder, true); - findCommand->setFilter(builder.obj()); + findCommand->setFilter(root->serialize()); findCommand->setProjection(baseQuery.getFindCommandRequest().getProjection().getOwned()); findCommand->setSort(baseQuery.getFindCommandRequest().getSort().getOwned()); findCommand->setCollation(baseQuery.getFindCommandRequest().getCollation().getOwned()); diff --git a/src/mongo/db/query/canonical_query_test.cpp b/src/mongo/db/query/canonical_query_test.cpp index 8fbb4eec5c3..be8b5eb6fc7 100644 --- a/src/mongo/db/query/canonical_query_test.cpp +++ b/src/mongo/db/query/canonical_query_test.cpp @@ -282,9 +282,7 @@ TEST(CanonicalQueryTest, CanonicalizeFromBaseQuery) { MatchExpression* firstClauseExpr = baseCq->root()->getChild(0); auto childCq = assertGet(CanonicalQuery::canonicalize(opCtx.get(), *baseCq, firstClauseExpr)); - BSONObjBuilder expectedFilter; - firstClauseExpr->serialize(&expectedFilter); - ASSERT_BSONOBJ_EQ(childCq->getFindCommandRequest().getFilter(), expectedFilter.obj()); + ASSERT_BSONOBJ_EQ(childCq->getFindCommandRequest().getFilter(), firstClauseExpr->serialize()); ASSERT_BSONOBJ_EQ(childCq->getFindCommandRequest().getProjection(), baseCq->getFindCommandRequest().getProjection()); diff --git a/src/mongo/db/query/explain.cpp b/src/mongo/db/query/explain.cpp index b9b69eac18d..2b7bb335bfb 100644 --- a/src/mongo/db/query/explain.cpp +++ b/src/mongo/db/query/explain.cpp @@ -123,7 +123,7 @@ void generatePlannerInfo(PlanExecutor* exec, auto query = exec->getCanonicalQuery(); if (nullptr != query) { BSONObjBuilder parsedQueryBob(plannerBob.subobjStart("parsedQuery")); - query->root()->serialize(&parsedQueryBob); + query->root()->serialize(&parsedQueryBob, {}); parsedQueryBob.doneFast(); if (query->getCollator()) { diff --git a/src/mongo/db/query/plan_explainer_sbe.cpp b/src/mongo/db/query/plan_explainer_sbe.cpp index f37382a23b6..d1fbe796d2b 100644 --- a/src/mongo/db/query/plan_explainer_sbe.cpp +++ b/src/mongo/db/query/plan_explainer_sbe.cpp @@ -193,7 +193,9 @@ void statsToBSON(const QuerySolutionNode* node, if (!cisn->filtersByPath.empty()) { BSONObjBuilder filtersBob(bob->subobjStart("filtersByPath")); for (const auto& [path, matchExpr] : cisn->filtersByPath) { - filtersBob.append(path, matchExpr->serialize(false /* includePath */)); + SerializationOptions opts; + opts.includePath = false; + filtersBob.append(path, matchExpr->serialize(opts)); } } diff --git a/src/mongo/db/query/planner_access_test.cpp b/src/mongo/db/query/planner_access_test.cpp index 52df32c871e..d30544b43fa 100644 --- a/src/mongo/db/query/planner_access_test.cpp +++ b/src/mongo/db/query/planner_access_test.cpp @@ -38,9 +38,7 @@ namespace mongo { namespace { BSONObj serializeMatcher(Matcher* matcher) { - BSONObjBuilder builder; - matcher->getMatchExpression()->serialize(&builder); - return builder.obj(); + return matcher->getMatchExpression()->serialize(); } TEST(PlannerAccessTest, PrepareForAccessPlanningSortsEqualNodesByTheirChildren) { diff --git a/src/mongo/db/query/projection_ast_util.cpp b/src/mongo/db/query/projection_ast_util.cpp index e5b4cc1a9c4..50649f8a5ed 100644 --- a/src/mongo/db/query/projection_ast_util.cpp +++ b/src/mongo/db/query/projection_ast_util.cpp @@ -47,7 +47,7 @@ public: virtual void visit(const MatchExpressionASTNode* node) { static_cast<const MatchExpressionASTNode*>(node)->matchExpression()->serialize( - &_builders.top(), true); + &_builders.top(), {}); } virtual void visit(const ProjectionPathASTNode* node) { diff --git a/src/mongo/db/query/query_shape.cpp b/src/mongo/db/query/query_shape.cpp new file mode 100644 index 00000000000..cc6829a72f4 --- /dev/null +++ b/src/mongo/db/query/query_shape.cpp @@ -0,0 +1,39 @@ +/** + * 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/query/query_shape.h" + +namespace mongo::query_shape { + +BSONObj predicateShape(const MatchExpression* predicate) { + SerializationOptions opts; + opts.replacementForLiteralArgs = kLiteralArgString; + return predicate->serialize(opts); +} +} // namespace mongo::query_shape diff --git a/src/mongo/db/query/query_shape.h b/src/mongo/db/query/query_shape.h new file mode 100644 index 00000000000..78733ac54b9 --- /dev/null +++ b/src/mongo/db/query/query_shape.h @@ -0,0 +1,54 @@ +/** + * 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" + +namespace mongo::query_shape { + +constexpr StringData kLiteralArgString = "?"_sd; + +/** + * Computes a BSONObj that is meant to be used to classify queries according to their shape, for the + * purposes of collecting telemetry. + * + * For example, if the MatchExpression represents {a: 2}, it will return the same BSONObj as the + * MatchExpression for {a: 1}, {a: 10}, and {a: {$eq: 2}} (identical bits but not sharing memory) + * because they are considered to be the same shape. + * + * Note that the shape of a MatchExpression is only part of the overall query shape - which should + * include other options like the sort and projection. + * + * TODO better consider how this interacts with persistent query settings project, and document it. + * TODO (TODO SERVER ticket) better distinguish this from a plan cache or CQ 'query shape'. + */ +BSONObj predicateShape(const MatchExpression* predicate); + +} // namespace mongo::query_shape diff --git a/src/mongo/db/query/query_shape_test.cpp b/src/mongo/db/query/query_shape_test.cpp new file mode 100644 index 00000000000..5ee00a416dd --- /dev/null +++ b/src/mongo/db/query/query_shape_test.cpp @@ -0,0 +1,135 @@ +/** + * 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/bson/bsonmisc.h" +#include "mongo/bson/bsonobj.h" +#include "mongo/db/matcher/parsed_match_expression_for_test.h" +#include "mongo/db/operation_context.h" +#include "mongo/db/pipeline/expression_context_for_test.h" +#include "mongo/db/query/optimizer/utils/unit_test_utils.h" +#include "mongo/db/query/query_planner_params.h" +#include "mongo/db/query/query_shape.h" +#include "mongo/unittest/unittest.h" + +namespace mongo { +namespace { +void assertShapeIs(std::string filterJson, BSONObj expectedShape) { + ParsedMatchExpressionForTest expr(filterJson); + ASSERT_BSONOBJ_EQ(expectedShape, query_shape::predicateShape(expr.get())); +} + +void assertShapeIs(std::string filterJson, std::string expectedShapeJson) { + return assertShapeIs(filterJson, fromjson(expectedShapeJson)); +} + +} // namespace + +TEST(QueryPredicateShape, Equals) { + assertShapeIs("{a: 5}", "{a: {$eq: '?'}}"); // Implicit equals + assertShapeIs("{a: {$eq: 5}}", "{a: {$eq: '?'}}"); // Explicit equals + assertShapeIs("{a: 5, b: 6}", "{$and: [{a: {$eq: '?'}}, {b: {$eq: '?'}}]}"); // implicit $and +} + +TEST(QueryPredicateShape, Comparisons) { + assertShapeIs("{a: {$lt: 5}, b: {$gt: 6}, c: {$gte: 3, $lte: 10}}", + "{$and: [{a: {$lt: '?'}}, {b: {$gt: '?'}}, {c: {$gte: '?'}}, {c: {$lte: '?'}}]}"); +} + +TEST(QueryPredicateShape, Regex) { + // Note/warning: 'fromjson' will parse $regex into a /regex/. If you want to keep it as-is, + // construct the BSON yourself. + assertShapeIs("{a: /a+/}", BSON("a" << BSON("$regex" << query_shape::kLiteralArgString))); + assertShapeIs("{a: /a+/i}", + BSON("a" << BSON("$regex" << query_shape::kLiteralArgString << "$options" + << query_shape::kLiteralArgString))); +} + +TEST(QueryPredicateShape, Mod) { + assertShapeIs("{a: {$mod: [2, 0]}}", "{a: {$mod: '?'}}"); +} + +TEST(QueryPredicateShape, Exists) { + assertShapeIs("{a: {$exists: true}}", "{a: {$exists: '?'}}"); +} + +TEST(QueryPredicateShape, In) { + // Any number of children is always the same shape + assertShapeIs("{a: {$in: [1]}}", "{a: {$in: ['?']}}"); + assertShapeIs("{a: {$in: [1, 4, 'str', /regex/]}}", "{a: {$in: ['?']}}"); +} + +TEST(QueryPredicateShape, BitTestOperators) { + assertShapeIs("{a: {$bitsAllSet: [1, 5]}}", "{a: {$bitsAllSet: '?'}}"); + assertShapeIs("{a: {$bitsAllSet: 50}}", "{a: {$bitsAllSet: '?'}}"); + + assertShapeIs("{a: {$bitsAnySet: [1, 5]}}", "{a: {$bitsAnySet: '?'}}"); + assertShapeIs("{a: {$bitsAnySet: 50}}", "{a: {$bitsAnySet: '?'}}"); + + assertShapeIs("{a: {$bitsAllClear: [1, 5]}}", "{a: {$bitsAllClear: '?'}}"); + assertShapeIs("{a: {$bitsAllClear: 50}}", "{a: {$bitsAllClear: '?'}}"); + + assertShapeIs("{a: {$bitsAnyClear: [1, 5]}}", "{a: {$bitsAnyClear: '?'}}"); + assertShapeIs("{a: {$bitsAnyClear: 50}}", "{a: {$bitsAnyClear: '?'}}"); +} + +BSONObj queryShapeForOptimizedExprExpression(std::string exprPredicateJson) { + ParsedMatchExpressionForTest expr(exprPredicateJson); + // We need to optimize an $expr expression in order to generate an $_internalExprEq. It's not + // clear we'd want to do optimization before computing the query shape, but we should support + // the computation on any MatchExpression, and this is the easiest way we can create this type + // of MatchExpression node. + auto optimized = MatchExpression::optimize(expr.release()); + return query_shape::predicateShape(optimized.get()); +} + +TEST(QueryPredicateShape, OptimizedExprPredicates) { + // TODO SERVER-73709 $expr should respect the literal redaction and hide the '2' - here and in + // all following assertions. + ASSERT_BSONOBJ_EQ( + queryShapeForOptimizedExprExpression("{$expr: {$eq: ['$a', 2]}}"), + fromjson("{$and: [{a: {$_internalExprEq: '?'}}, {$expr: {$eq: ['$a', {$const: 2}]}}]}")); + + ASSERT_BSONOBJ_EQ( + queryShapeForOptimizedExprExpression("{$expr: {$lt: ['$a', 2]}}"), + fromjson("{$and: [{a: {$_internalExprLt: '?'}}, {$expr: {$lt: ['$a', {$const: 2}]}}]}")); + + ASSERT_BSONOBJ_EQ( + queryShapeForOptimizedExprExpression("{$expr: {$lte: ['$a', 2]}}"), + fromjson("{$and: [{a: {$_internalExprLte: '?'}}, {$expr: {$lte: ['$a', {$const: 2}]}}]}")); + + ASSERT_BSONOBJ_EQ( + queryShapeForOptimizedExprExpression("{$expr: {$gt: ['$a', 2]}}"), + fromjson("{$and: [{a: {$_internalExprGt: '?'}}, {$expr: {$gt: ['$a', {$const: 2}]}}]}")); + + ASSERT_BSONOBJ_EQ( + queryShapeForOptimizedExprExpression("{$expr: {$gte: ['$a', 2]}}"), + fromjson("{$and: [{a: {$_internalExprGte: '?'}}, {$expr: {$gte: ['$a', {$const: 2}]}}]}")); +} + +} // namespace mongo diff --git a/src/mongo/db/query/serialization_options.h b/src/mongo/db/query/serialization_options.h new file mode 100644 index 00000000000..301617a8768 --- /dev/null +++ b/src/mongo/db/query/serialization_options.h @@ -0,0 +1,70 @@ +/** + * 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/base/string_data.h" +#include "third_party/boost/boost/optional.hpp" + +namespace mongo { + +/** + * A struct with options for how you want to serialize a match expression. + */ +struct SerializationOptions { + SerializationOptions() {} + + // 'replacementForLiteralArgs' is an independent option to serialize in a genericized format + // with the aim of similar "shaped" queries serializing to the same object. For example, if + // set to '?' then the serialization of {a: {$gt: 2}} will result in {a: {$gt: '?'}}, as + // will the serialization of {a: {$gt: 3}}. + // + // "Literal" here is meant to stand in contrast to expression arguements, as in the $gt + // expressions in {$and: [{a: {$gt: 3}}, {b: {$gt: 4}}]}. There the only literals are 3 and + // 4, so the serialization expected would be {$and: [{a: {$gt: '?'}}, {b: {$lt: '?'}}]}. + boost::optional<StringData> replacementForLiteralArgs = boost::none; + + // TODO SERVER-73663 'redactFieldNames' could be here - a callback function? + + // If set, 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}}. + bool includePath = true; +}; + +} // namespace mongo diff --git a/src/mongo/dbtests/extensions_callback_real_test.cpp b/src/mongo/dbtests/extensions_callback_real_test.cpp index 6cf71af0f8c..bd75718e375 100644 --- a/src/mongo/dbtests/extensions_callback_real_test.cpp +++ b/src/mongo/dbtests/extensions_callback_real_test.cpp @@ -255,13 +255,10 @@ TEST_F(ExtensionsCallbackRealTest, WhereExpressionDesugarsToExprAndInternalJs) { auto expr1 = unittest::assertGet( ExtensionsCallbackReal(&_opCtx, &_nss).parseWhere(expCtx, query1.firstElement())); - BSONObjBuilder gotMatch; - expr1->serialize(&gotMatch); - auto expectedMatch = fromjson( "{$expr: {$function: {'body': 'function() { return this.x == 10; }', 'args': " "['$$CURRENT'], 'lang': 'js', '_internalSetObjToThis': true}}}"); - ASSERT_BSONOBJ_EQ(gotMatch.obj(), expectedMatch); + ASSERT_BSONOBJ_EQ(expr1->serialize(), expectedMatch); } } |