diff options
author | jannaerin <golden.janna@gmail.com> | 2017-10-25 10:31:59 -0400 |
---|---|---|
committer | jannaerin <golden.janna@gmail.com> | 2017-10-30 23:00:02 -0400 |
commit | efd028178dbf938a9f52abf6e9434dea6aa508e3 (patch) | |
tree | fbac67cb064389ef85778627bb200eabf065c8f7 | |
parent | b49b20887cf13720c0e863d24da95cc0239889f8 (diff) | |
download | mongo-efd028178dbf938a9f52abf6e9434dea6aa508e3.tar.gz |
SERVER-31292: Make explain reflect optimizations
-rw-r--r-- | jstests/core/optimized_match_explain.js | 23 | ||||
-rw-r--r-- | jstests/sharding/aggregation_currentop.js | 2 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_lookup_test.cpp | 72 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_match.cpp | 6 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_match_test.cpp | 14 | ||||
-rw-r--r-- | src/mongo/db/pipeline/pipeline_test.cpp | 335 |
6 files changed, 346 insertions, 106 deletions
diff --git a/jstests/core/optimized_match_explain.js b/jstests/core/optimized_match_explain.js new file mode 100644 index 00000000000..8e688302753 --- /dev/null +++ b/jstests/core/optimized_match_explain.js @@ -0,0 +1,23 @@ +/** + * Tests that the explain output for $match reflects any optimizations. + */ +(function() { + "use strict"; + load("jstests/libs/analyze_plan.js"); + + const coll = db.match_explain; + coll.drop(); + + assert.writeOK(coll.insert({a: 1, b: 1})); + assert.writeOK(coll.insert({a: 2, b: 3})); + assert.writeOK(coll.insert({a: 1, b: 2})); + assert.writeOK(coll.insert({a: 1, b: 4})); + + // Explain output should reflect optimizations. + // $and should not be in the explain output because it is optimized out. + let explain = coll.explain().aggregate( + [{$sort: {b: -1}}, {$addFields: {c: {$mod: ["$a", 4]}}}, {$match: {$and: [{c: 1}]}}]); + + assert.commandWorked(explain); + assert.eq(getAggPlanStage(explain, "$match"), {$match: {c: {$eq: 1}}}); +}()); diff --git a/jstests/sharding/aggregation_currentop.js b/jstests/sharding/aggregation_currentop.js index 689e57ea20c..395888f7e0c 100644 --- a/jstests/sharding/aggregation_currentop.js +++ b/jstests/sharding/aggregation_currentop.js @@ -361,7 +361,7 @@ const expectedStages = [ {$currentOp: {idleConnections: true, allUsers: false, truncateOps: false}}, - {$match: {desc: "test"}} + {$match: {desc: {$eq: "test"}}} ]; if (isMongos) { diff --git a/src/mongo/db/pipeline/document_source_lookup_test.cpp b/src/mongo/db/pipeline/document_source_lookup_test.cpp index 3ad105a4414..3b25bb8407b 100644 --- a/src/mongo/db/pipeline/document_source_lookup_test.cpp +++ b/src/mongo/db/pipeline/document_source_lookup_test.cpp @@ -718,10 +718,10 @@ TEST_F(DocumentSourceLookUpTest, ShouldCacheNonCorrelatedSubPipelinePrefix) { auto subPipeline = lookupStage->getSubPipeline_forTest(DOC("_id" << 5)); ASSERT(subPipeline); - auto expectedPipe = - fromjson(str::stream() << "[{mock: {}}, {$match: {x:1}}, {$sort: {sortKey: {x: 1}}}, " - << sequentialCacheStageObj() - << ", {$addFields: {varField: {$const: 5} }}]"); + auto expectedPipe = fromjson( + str::stream() << "[{mock: {}}, {$match: {x:{$eq: 1}}}, {$sort: {sortKey: {x: 1}}}, " + << sequentialCacheStageObj() + << ", {$addFields: {varField: {$const: 5} }}]"); ASSERT_VALUE_EQ(Value(subPipeline->writeExplainOps(kExplain)), Value(BSONArray(expectedPipe))); } @@ -752,15 +752,11 @@ TEST_F(DocumentSourceLookUpTest, auto subPipeline = lookupStage->getSubPipeline_forTest(DOC("_id" << 5)); ASSERT(subPipeline); - // TODO: The '$$var1' in this expression actually gets optimized to a constant expression with - // value 5, but the explain output does not reflect that change. SERVER-31292 will make that - // optimization visible here, so we will need to replace '$$var1' with a $const expression for - // this test to pass. - auto expectedPipe = - fromjson(str::stream() << "[{mock: {}}, {$match: {x:1}}, {$sort: {sortKey: {x: 1}}}, " - << sequentialCacheStageObj() - << ", {$facet: {facetPipe: [{$group: {_id: '$_id'}}, {$match: " - "{$expr: {$eq: ['$_id', '$$var1']}}}]}}]"); + auto expectedPipe = fromjson( + str::stream() << "[{mock: {}}, {$match: {x:{$eq: 1}}}, {$sort: {sortKey: {x: 1}}}, " + << sequentialCacheStageObj() + << ", {$facet: {facetPipe: [{$group: {_id: '$_id'}}, {$match: " + "{$and: [{_id: {$eq: 5}}, {$expr: {$eq: ['$_id', {$const: 5}]}}]}}]}}]"); ASSERT_VALUE_EQ(Value(subPipeline->writeExplainOps(kExplain)), Value(BSONArray(expectedPipe))); } @@ -829,11 +825,11 @@ TEST_F(DocumentSourceLookUpTest, ASSERT(subPipeline); auto expectedPipe = fromjson( - str::stream() - << "[{mock: {}}, {$match: {x: 1}}, {$sort: {sortKey: {x: 1}}}, {$project: {_id: false, " - "projectedField: {$let: {vars: {var1: {$const: 'abc'}}, in: '$$var1'}}}}," - << sequentialCacheStageObj() - << ", {$addFields: {varField: {$sum: ['$x', {$const: 5}]}}}]"); + str::stream() << "[{mock: {}}, {$match: {x: {$eq: 1}}}, {$sort: {sortKey: {x: 1}}}, " + "{$project: {_id: false, " + "projectedField: {$let: {vars: {var1: {$const: 'abc'}}, in: '$$var1'}}}}," + << sequentialCacheStageObj() + << ", {$addFields: {varField: {$sum: ['$x', {$const: 5}]}}}]"); ASSERT_VALUE_EQ(Value(subPipeline->writeExplainOps(kExplain)), Value(BSONArray(expectedPipe))); } @@ -863,13 +859,13 @@ TEST_F(DocumentSourceLookUpTest, ShouldInsertCacheBeforeCorrelatedNestedLookup) auto subPipeline = lookupStage->getSubPipeline_forTest(DOC("_id" << 5)); ASSERT(subPipeline); - auto expectedPipe = - fromjson(str::stream() << "[{mock: {}}, {$match: {x:1}}, {$sort: {sortKey: {x: 1}}}, " - << sequentialCacheStageObj() - << ", {$lookup: {from: 'coll', as: 'subas', let: {}, pipeline: " - "[{$match: {x: 1}}, {$lookup: {from: 'coll', as: 'subsubas', " - "pipeline: [{$match: {$expr: {$eq: ['$y', '$$var1']}}}]}}]}}, " - "{$addFields: {varField: {$const: 5}}}]"); + auto expectedPipe = fromjson( + str::stream() << "[{mock: {}}, {$match: {x:{$eq: 1}}}, {$sort: {sortKey: {x: 1}}}, " + << sequentialCacheStageObj() + << ", {$lookup: {from: 'coll', as: 'subas', let: {}, pipeline: " + "[{$match: {x: 1}}, {$lookup: {from: 'coll', as: 'subsubas', " + "pipeline: [{$match: {$expr: {$eq: ['$y', '$$var1']}}}]}}]}}, " + "{$addFields: {varField: {$const: 5}}}]"); ASSERT_VALUE_EQ(Value(subPipeline->writeExplainOps(kExplain)), Value(BSONArray(expectedPipe))); } @@ -899,12 +895,12 @@ TEST_F(DocumentSourceLookUpTest, auto subPipeline = lookupStage->getSubPipeline_forTest(DOC("_id" << 5)); ASSERT(subPipeline); - auto expectedPipe = - fromjson(str::stream() << "[{mock: {}}, {$match: {x:1}}, {$sort: {sortKey: {x: 1}}}, " - "{$lookup: {from: 'coll', as: 'subas', let: {var1: '$y'}, " - "pipeline: [{$match: {$expr: { $eq: ['$z', '$$var1']}}}]}}, " - << sequentialCacheStageObj() - << ", {$addFields: {varField: {$const: 5} }}]"); + auto expectedPipe = fromjson( + str::stream() << "[{mock: {}}, {$match: {x:{$eq: 1}}}, {$sort: {sortKey: {x: 1}}}, " + "{$lookup: {from: 'coll', as: 'subas', let: {var1: '$y'}, " + "pipeline: [{$match: {$expr: { $eq: ['$z', '$$var1']}}}]}}, " + << sequentialCacheStageObj() + << ", {$addFields: {varField: {$const: 5} }}]"); ASSERT_VALUE_EQ(Value(subPipeline->writeExplainOps(kExplain)), Value(BSONArray(expectedPipe))); } @@ -930,13 +926,13 @@ TEST_F(DocumentSourceLookUpTest, ShouldCacheEntirePipelineIfNonCorrelated) { auto subPipeline = lookupStage->getSubPipeline_forTest(DOC("_id" << 5)); ASSERT(subPipeline); - auto expectedPipe = - fromjson(str::stream() - << "[{mock: {}}, {$match: {x:1}}, {$sort: {sortKey: {x: 1}}}, {$lookup: {from: " - "'coll', as: 'subas', let: {}, pipeline: [{$match: {y: 5}}]}}, {$addFields: " - "{constField: {$const: 5}}}, " - << sequentialCacheStageObj() - << "]"); + auto expectedPipe = fromjson( + str::stream() + << "[{mock: {}}, {$match: {x:{$eq: 1}}}, {$sort: {sortKey: {x: 1}}}, {$lookup: {from: " + "'coll', as: 'subas', let: {}, pipeline: [{$match: {y: 5}}]}}, {$addFields: " + "{constField: {$const: 5}}}, " + << sequentialCacheStageObj() + << "]"); ASSERT_VALUE_EQ(Value(subPipeline->writeExplainOps(kExplain)), Value(BSONArray(expectedPipe))); } diff --git a/src/mongo/db/pipeline/document_source_match.cpp b/src/mongo/db/pipeline/document_source_match.cpp index f65aa8db073..cbdf165b504 100644 --- a/src/mongo/db/pipeline/document_source_match.cpp +++ b/src/mongo/db/pipeline/document_source_match.cpp @@ -60,6 +60,11 @@ 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(getQuery()))); } @@ -495,7 +500,6 @@ DocumentSourceMatch::DocumentSourceMatch(const BSONObj& query, : DepsTracker::MetadataAvailable::kNoMetadata) { StatusWithMatchExpression status = uassertStatusOK(MatchExpressionParser::parse( _predicate, pExpCtx, ExtensionsCallbackNoop(), Pipeline::kAllowedMatcherFeatures)); - _expression = std::move(status.getValue()); getDependencies(&_dependencies); } diff --git a/src/mongo/db/pipeline/document_source_match_test.cpp b/src/mongo/db/pipeline/document_source_match_test.cpp index a22eabc993d..84a3ac6c01c 100644 --- a/src/mongo/db/pipeline/document_source_match_test.cpp +++ b/src/mongo/db/pipeline/document_source_match_test.cpp @@ -49,6 +49,8 @@ using std::string; // This provides access to getExpCtx(), but we'll use a different name for this test suite. using DocumentSourceMatchTest = AggregationContextFixture; +constexpr auto kExplain = ExplainOptions::Verbosity::kQueryPlanner; + TEST_F(DocumentSourceMatchTest, RedactSafePortion) { auto expCtx = getExpCtx(); auto assertExpectedRedactSafePortion = [&expCtx](string input, string safePortion) { @@ -617,5 +619,17 @@ TEST_F(DocumentSourceMatchTest, ShouldCorrectlyEvaluateJSONSchemaPredicate) { ASSERT_TRUE(match->getNext().isEOF()); } +TEST_F(DocumentSourceMatchTest, ShouldShowOptimizationsInExplainOutputWhenOptimized) { + const auto match = DocumentSourceMatch::create(fromjson("{$and: [{a: 1}]}"), getExpCtx()); + + auto optimizedMatch = match->optimize(); + + auto expectedMatch = fromjson("{$match: {a:{$eq: 1}}}"); + + ASSERT_VALUE_EQ( + Value((static_cast<DocumentSourceMatch*>(optimizedMatch.get()))->serialize(kExplain)), + Value(expectedMatch)); +} + } // namespace } // namespace mongo diff --git a/src/mongo/db/pipeline/pipeline_test.cpp b/src/mongo/db/pipeline/pipeline_test.cpp index cf90fd4e77e..33bbeddc97f 100644 --- a/src/mongo/db/pipeline/pipeline_test.cpp +++ b/src/mongo/db/pipeline/pipeline_test.cpp @@ -131,33 +131,63 @@ TEST(PipelineOptimizationTest, MoveMultipleSkipsAndLimitsBeforeProject) { } TEST(PipelineOptimizationTest, MoveMatchBeforeAddFieldsIfInvolvedFieldsNotRelated) { - assertPipelineOptimizesTo("[{$addFields : {a : 1}}, {$match : {b : 1}}]", - "[{$match : {b : 1}}, {$addFields : {a : {$const : 1}}}]"); + string inputPipe = "[{$addFields : {a : 1}}, {$match : {b : 1}}]"; + + string outputPipe = "[{$match : {b : {$eq : 1}}}, {$addFields : {a : {$const : 1}}}]"; + + string serializedPipe = "[{$match: {b : 1}}, {$addFields: {a : {$const : 1}}}]"; + + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); } TEST(PipelineOptimizationTest, MatchDoesNotMoveBeforeAddFieldsIfInvolvedFieldsAreRelated) { - assertPipelineOptimizesTo("[{$addFields : {a : 1}}, {$match : {a : 1}}]", - "[{$addFields : {a : {$const : 1}}}, {$match : {a : 1}}]"); + string inputPipe = "[{$addFields : {a : 1}}, {$match : {a : 1}}]"; + + string outputPipe = "[{$addFields : {a : {$const : 1}}}, {$match : {a : {$eq : 1}}}]"; + + string serializedPipe = "[{$addFields : {a : {$const : 1}}}, {$match: {a : 1}}]"; + + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); } TEST(PipelineOptimizationTest, MatchOnTopLevelFieldDoesNotMoveBeforeAddFieldsOfNestedPath) { - assertPipelineOptimizesTo("[{$addFields : {'a.b' : 1}}, {$match : {a : 1}}]", - "[{$addFields : {a : {b : {$const : 1}}}}, {$match : {a : 1}}]"); + string inputPipe = "[{$addFields : {'a.b' : 1}}, {$match : {a : 1}}]"; + + string outputPipe = "[{$addFields : {a : {b : {$const : 1}}}}, {$match : {a : {$eq : 1}}}]"; + + string serializedPipe = "[{$addFields: {a: {b: {$const: 1}}}}, {$match: {a: 1}}]"; + + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); } TEST(PipelineOptimizationTest, MatchOnNestedFieldDoesNotMoveBeforeAddFieldsOfPrefixOfPath) { - assertPipelineOptimizesTo("[{$addFields : {a : 1}}, {$match : {'a.b' : 1}}]", - "[{$addFields : {a : {$const : 1}}}, {$match : {'a.b' : 1}}]"); + string inputPipe = "[{$addFields : {a : 1}}, {$match : {'a.b' : 1}}]"; + + string outputPipe = "[{$addFields : {a : {$const : 1}}}, {$match : {'a.b' : {$eq : 1}}}]"; + + string serializedPipe = "[{$addFields : {a : {$const : 1}}}, {$match : {'a.b' : 1}}]"; + + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); } TEST(PipelineOptimizationTest, MoveMatchOnNestedFieldBeforeAddFieldsOfDifferentNestedField) { - assertPipelineOptimizesTo("[{$addFields : {'a.b' : 1}}, {$match : {'a.c' : 1}}]", - "[{$match : {'a.c' : 1}}, {$addFields : {a : {b : {$const : 1}}}}]"); + string inputPipe = "[{$addFields : {'a.b' : 1}}, {$match : {'a.c' : 1}}]"; + + string outputPipe = "[{$match : {'a.c' : {$eq : 1}}}, {$addFields : {a : {b : {$const : 1}}}}]"; + + string serializedPipe = "[{$match : {'a.c' : 1}}, {$addFields : {a : {b: {$const : 1}}}}]"; + + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); } TEST(PipelineOptimizationTest, MoveMatchBeforeAddFieldsWhenMatchedFieldIsPrefixOfAddedFieldName) { - assertPipelineOptimizesTo("[{$addFields : {abcd : 1}}, {$match : {abc : 1}}]", - "[{$match : {abc : 1}}, {$addFields : {abcd: {$const: 1}}}]"); + string inputPipe = "[{$addFields : {abcd : 1}}, {$match : {abc : 1}}]"; + + string outputPipe = "[{$match : {abc : {$eq : 1}}}, {$addFields : {abcd: {$const: 1}}}]"; + + string serializedPipe = "[{$match : {abc : 1}}, {$addFields : {abcd : {$const : 1}}}]"; + + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); } TEST(PipelineOptimizationTest, SkipSkipLimitBecomesLimitSkip) { @@ -183,7 +213,7 @@ TEST(PipelineOptimizationTest, SortMatchProjSkipLimBecomesMatchTopKSortSkipProj) "]"; std::string outputPipe = - "[{$match: {a: 1}}" + "[{$match: {a: {$eq: 1}}}" ",{$sort: {sortKey: {a: 1}, limit: 8}}" ",{$skip: 3}" ",{$project: {_id: true, a: true}}" @@ -213,16 +243,28 @@ TEST(PipelineOptimizationTest, RemoveEmptyMatch) { } TEST(PipelineOptimizationTest, RemoveMultipleEmptyMatches) { - assertPipelineOptimizesTo("[{$match: {}}, {$match: {}}]", "[{$match: {$and: [{}, {}]}}]"); + string inputPipe = "[{$match: {}}, {$match: {}}]"; + + string outputPipe = "[{$match: {}}]"; + + string serializedPipe = "[{$match: {$and: [{}, {}]}}]"; + + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); } TEST(PipelineOptimizationTest, DoNotRemoveNonEmptyMatch) { - assertPipelineOptimizesTo("[{$match: {_id: 1}}]", "[{$match: {_id: 1}}]"); + string inputPipe = "[{$match: {_id: 1}}]"; + + string outputPipe = "[{$match: {_id: {$eq : 1}}}]"; + + string serializedPipe = "[{$match: {_id: 1}}]"; + + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); } TEST(PipelineOptimizationTest, MoveMatchBeforeSort) { std::string inputPipe = "[{$sort: {b: 1}}, {$match: {a: 2}}]"; - std::string outputPipe = "[{$match: {a: 2}}, {$sort: {sortKey: {b: 1}}}]"; + std::string outputPipe = "[{$match: {a: {$eq : 2}}}, {$sort: {sortKey: {b: 1}}}]"; std::string serializedPipe = "[{$match: {a: 2}}, {$sort: {b: 1}}]"; assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); } @@ -326,10 +368,14 @@ TEST(PipelineOptimizationTest, LookupShouldSwapWithMatch) { "'z'}}, " " {$match: {'independent': 0}}]"; string outputPipe = - "[{$match: {independent: 0}}, " + "[{$match: {independent: {$eq : 0}}}, " " {$lookup: {from: 'lookupColl', as: 'asField', localField: 'y', foreignField: " "'z'}}]"; - assertPipelineOptimizesTo(inputPipe, outputPipe); + string serializedPipe = + "[{$match: {independent: 0}}, " + "{$lookup: {from: 'lookupColl', as: 'asField', localField: 'y', foreignField: 'z'}}]"; + + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); } TEST(PipelineOptimizationTest, LookupWithPipelineSyntaxShouldSwapWithMatch) { @@ -337,9 +383,13 @@ TEST(PipelineOptimizationTest, LookupWithPipelineSyntaxShouldSwapWithMatch) { "[{$lookup: {from: 'lookupColl', as: 'asField', pipeline: []}}, " " {$match: {'independent': 0}}]"; string outputPipe = - "[{$match: {independent: 0}}, " + "[{$match: {independent: {$eq : 0}}}, " " {$lookup: {from: 'lookupColl', as: 'asField', let: {}, pipeline: []}}]"; - assertPipelineOptimizesTo(inputPipe, outputPipe); + string serializedPipe = + "[{$match: {independent: 0}}, " + "{$lookup: {from: 'lookupColl', as: 'asField', let: {}, pipeline: []}}]"; + + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); } TEST(PipelineOptimizationTest, LookupShouldSplitMatch) { @@ -363,8 +413,13 @@ TEST(PipelineOptimizationTest, LookupShouldNotAbsorbMatchOnAs) { string outputPipe = "[{$lookup: {from: 'lookupColl', as: 'asField', localField: 'y', foreignField: " "'z'}}, " + " {$match: {'asField.subfield': {$eq : 0}}}]"; + string serializedPipe = + "[{$lookup: {from: 'lookupColl', as: 'asField', localField: 'y', foreignField: " + "'z'}}, " " {$match: {'asField.subfield': 0}}]"; - assertPipelineOptimizesTo(inputPipe, outputPipe); + + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); } TEST(PipelineOptimizationTest, LookupShouldAbsorbUnwindMatch) { @@ -525,7 +580,7 @@ TEST(PipelineOptimizationTest, LookupDoesNotAbsorbElemMatch) { " } " " } " " }, " - " {$match: {x: {$elemMatch: {a: 1}}}}]"; + " {$match: {x: {$elemMatch: {a: {$eq: 1}}}}}]"; string serializedPipe = "[{$lookup: {from: 'lookupColl', as: 'x', localField: 'y', foreignField: 'z'}}, " " {$unwind: {path: '$x'}}, " @@ -569,8 +624,11 @@ TEST(PipelineOptimizationTest, LookupDoesNotAbsorbUnwindOnSubfieldOfAsButStillMo TEST(PipelineOptimizationTest, MatchShouldDuplicateItselfBeforeRedact) { string inputPipe = "[{$redact: '$$PRUNE'}, {$match: {a: 1, b:12}}]"; string outputPipe = - "[{$match: {a: 1, b:12}}, {$redact: {$const: 'prune'}}, {$match: {a: 1, b:12}}]"; - assertPipelineOptimizesTo(inputPipe, outputPipe); + "[{$match: {$and: [{a: {$eq: 1}}, {b: {$eq: 12}}]}}, {$redact: {$const: 'prune'}}, " + "{$match: {$and: [{a: {$eq: 1}}, {b: {$eq: 12}}]}}]"; + string serializedPipe = + "[{$match: {a: 1, b: 12}}, {$redact: {$const: 'prune'}}, {$match: {a: 1, b: 12}}]"; + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); } TEST(PipelineOptimizationTest, MatchShouldSwapWithUnwind) { @@ -578,9 +636,10 @@ TEST(PipelineOptimizationTest, MatchShouldSwapWithUnwind) { "[{$unwind: '$a.b.c'}, " "{$match: {'b': 1}}]"; string outputPipe = - "[{$match: {'b': 1}}, " + "[{$match: {'b': {$eq : 1}}}, " "{$unwind: {path: '$a.b.c'}}]"; - assertPipelineOptimizesTo(inputPipe, outputPipe); + string serializedPipe = "[{$match: {b: 1}}, {$unwind: {path: '$a.b.c'}}]"; + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); } TEST(PipelineOptimizationTest, MatchOnPrefixShouldNotSwapOnUnwind) { @@ -589,8 +648,9 @@ TEST(PipelineOptimizationTest, MatchOnPrefixShouldNotSwapOnUnwind) { "{$match: {'a.b': 1}}]"; string outputPipe = "[{$unwind: {path: '$a.b.c'}}, " - "{$match: {'a.b': 1}}]"; - assertPipelineOptimizesTo(inputPipe, outputPipe); + "{$match: {'a.b': {$eq : 1}}}]"; + string serializedPipe = "[{$unwind: {path: '$a.b.c'}}, {$match: {'a.b': 1}}]"; + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); } TEST(PipelineOptimizationTest, MatchShouldSplitOnUnwind) { @@ -612,8 +672,10 @@ TEST(PipelineOptimizationTest, MatchShouldNotOptimizeWithElemMatch) { "{$match: {a: {$elemMatch: {b: {d: 1}}}}}]"; string outputPipe = "[{$unwind: {path: '$a.b'}}, " - "{$match: {a: {$elemMatch: {b: {d: 1}}}}}]"; - assertPipelineOptimizesTo(inputPipe, outputPipe); + "{$match: {a: {$elemMatch: {b: {$eq : {d: 1}}}}}}]"; + string serializedPipe = + "[{$unwind : {path : '$a.b'}}, {$match : {a : {$elemMatch : {b : {d : 1}}}}}]"; + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); } TEST(PipelineOptimizationTest, MatchShouldNotOptimizeWhenMatchingOnIndexField) { @@ -741,7 +803,7 @@ TEST(PipelineOptimizationTest, GraphLookupShouldSwapWithMatch) { " {$match: {independent: 'x'}}" "]"; string outputPipe = - "[{$match: {independent: 'x'}}," + "[{$match: {independent: {$eq : 'x'}}}," " {$graphLookup: {" " from: 'lookupColl'," " as: 'results'," @@ -749,18 +811,28 @@ TEST(PipelineOptimizationTest, GraphLookupShouldSwapWithMatch) { " connectFromField: 'from'," " startWith: '$startVal'" " }}]"; - assertPipelineOptimizesTo(inputPipe, outputPipe); + string serializedPipe = + "[{$match: {independent: 'x'}}, " + " {$graphLookup: {" + " from: 'lookupColl'," + " as: 'results'," + " connectToField: 'to'," + " connectFromField: 'from'," + " startWith: '$startVal'" + " }}]"; + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); } TEST(PipelineOptimizationTest, ExclusionProjectShouldSwapWithIndependentMatch) { string inputPipe = "[{$project: {redacted: 0}}, {$match: {unrelated: 4}}]"; - string outputPipe = "[{$match: {unrelated: 4}}, {$project: {redacted: false}}]"; - assertPipelineOptimizesTo(inputPipe, outputPipe); + string outputPipe = "[{$match: {unrelated: {$eq : 4}}}, {$project: {redacted: false}}]"; + string serializedPipe = "[{$match : {unrelated : 4}}, {$project : {redacted : false}}]"; + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); } TEST(PipelineOptimizationTest, ExclusionProjectShouldNotSwapWithMatchOnExcludedFields) { std::string pipeline = - "[{$project: {subdoc: {redacted: false}}}, {$match: {'subdoc.redacted': 4}}]"; + "[{$project: {subdoc: {redacted: false}}}, {$match: {'subdoc.redacted': {$eq : 4}}}]"; assertPipelineOptimizesTo(pipeline, pipeline); } @@ -777,15 +849,24 @@ TEST(PipelineOptimizationTest, MatchShouldSplitIfPartIsIndependentOfExclusionPro TEST(PipelineOptimizationTest, InclusionProjectShouldSwapWithIndependentMatch) { string inputPipe = "[{$project: {included: 1}}, {$match: {included: 4}}]"; - string outputPipe = "[{$match: {included: 4}}, {$project: {_id: true, included: true}}]"; - assertPipelineOptimizesTo(inputPipe, outputPipe); + string outputPipe = + "[{$match: {included: {$eq : 4}}}, {$project: {_id: true, included: true}}]"; + string serializedPipe = + "[{$match : {included : 4}}, {$project : {_id: true, included : true}}]"; + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); } TEST(PipelineOptimizationTest, InclusionProjectShouldNotSwapWithMatchOnFieldsNotIncluded) { - string pipeline = + string inputPipe = "[{$project: {_id: true, included: true, subdoc: {included: true}}}," " {$match: {notIncluded: 'x', unrelated: 4}}]"; - assertPipelineOptimizesTo(pipeline, pipeline); + string outputPipe = + "[{$project: {_id: true, included: true, subdoc: {included: true}}}," + " {$match: {$and: [{notIncluded: {$eq: 'x'}}, {unrelated: {$eq: 4}}]}}]"; + string serializedPipe = + "[{$project: {_id: true, included: true, subdoc: {included: true}}}," + " {$match: {notIncluded: 'x', unrelated: 4}}]"; + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); } TEST(PipelineOptimizationTest, MatchShouldSplitIfPartIsIndependentOfInclusionProjection) { @@ -815,18 +896,23 @@ TEST(PipelineOptimizationTest, NeighboringMatchesShouldCoalesce) { string inputPipe = "[{$match: {x: 'x'}}," " {$match: {y: 'y'}}]"; - string outputPipe = "[{$match: {$and: [{x: 'x'}, {y: 'y'}]}}]"; - assertPipelineOptimizesTo(inputPipe, outputPipe); + string outputPipe = "[{$match: {$and: [{x: {$eq: 'x'}}, {y: {$eq : 'y'}}]}}]"; + string serializedPipe = "[{$match: {$and: [{x: 'x'}, {y: 'y'}]}}]"; + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); } TEST(PipelineOptimizationTest, MatchShouldNotSwapBeforeLimit) { - string pipeline = "[{$limit: 3}, {$match: {y: 'y'}}]"; - assertPipelineOptimizesTo(pipeline, pipeline); + string inputPipe = "[{$limit: 3}, {$match: {y: 'y'}}]"; + string outputPipe = "[{$limit: 3}, {$match: {y: {$eq : 'y'}}}]"; + string serializedPipe = "[{$limit: 3}, {$match: {y: 'y'}}]"; + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); } TEST(PipelineOptimizationTest, MatchShouldNotSwapBeforeSkip) { - string pipeline = "[{$skip: 3}, {$match: {y: 'y'}}]"; - assertPipelineOptimizesTo(pipeline, pipeline); + string inputPipe = "[{$skip: 3}, {$match: {y: 'y'}}]"; + string outputPipe = "[{$skip: 3}, {$match: {y: {$eq : 'y'}}}]"; + string serializedPipe = "[{$skip: 3}, {$match: {y: 'y'}}]"; + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); } TEST(PipelineOptimizationTest, MatchShouldMoveAcrossProjectRename) { @@ -1051,17 +1137,38 @@ TEST(PipelineOptimizationTest, MatchOnObjectMatchShouldNotSwapSinceCategoryIsOth string inputPipe = "[{$project: {_id: true, a: '$b'}}, " "{$match: {a: {$_internalSchemaObjectMatch: {b: 1}}}}]"; - assertPipelineOptimizesTo(inputPipe, inputPipe); + string outputPipe = + "[{$project: {_id: true, a: '$b'}}, " + "{$match: {a: {$_internalSchemaObjectMatch: {b: {$eq: 1}}}}}]"; + string serializedPipe = + "[{$project: {_id: true, a: '$b'}}," + "{$match: {a: {$_internalSchemaObjectMatch: {b: 1}}}}]"; + + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); inputPipe = "[{$project: {redacted: false}}, " "{$match: {a: {$_internalSchemaObjectMatch: {b: 1}}}}]"; - assertPipelineOptimizesTo(inputPipe, inputPipe); + outputPipe = + "[{$project: {redacted: false}}," + "{$match: {a: {$_internalSchemaObjectMatch: {b: {$eq: 1}}}}}]"; + serializedPipe = + "[{$project: {redacted: false}}," + "{$match: {a: {$_internalSchemaObjectMatch: {b: 1}}}}]"; + + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); inputPipe = "[{$addFields : {a : {$const: 1}}}, " "{$match: {a: {$_internalSchemaObjectMatch: {b: 1}}}}]"; - assertPipelineOptimizesTo(inputPipe, inputPipe); + outputPipe = + "[{$addFields : {a : {$const: 1}}}, " + "{$match: {a: {$_internalSchemaObjectMatch: {b: {$eq: 1}}}}}]"; + serializedPipe = + "[{$addFields : {a : {$const: 1}}}, " + "{$match: {a: {$_internalSchemaObjectMatch: {b: 1}}}}]"; + + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); } // Descriptive test. The following internal match expression *could* participate in pipeline @@ -1113,7 +1220,23 @@ TEST(PipelineOptimizationTest, MatchOnAllowedPropertiesShouldNotSwapSinceCategor patternProperties: [], otherwise: {i: 1} }}}])"; - assertPipelineOptimizesTo(inputPipe, inputPipe); + string outputPipe = R"( + [{$project: {_id: true, a: '$b'}}, + {$match: {$_internalSchemaAllowedProperties: { + properties: ['b'], + namePlaceholder: 'i', + patternProperties: [], + otherwise: {i: {$eq : 1}} + }}}])"; + string serializedPipe = R"( + [{$project: {_id: true, a: '$b'}}, + {$match: {$_internalSchemaAllowedProperties: { + properties: ['b'], + namePlaceholder: 'i', + patternProperties: [], + otherwise: {i : 1}} + }}])"; + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); inputPipe = R"( [{$project: {redacted: false}}, @@ -1123,7 +1246,23 @@ TEST(PipelineOptimizationTest, MatchOnAllowedPropertiesShouldNotSwapSinceCategor patternProperties: [], otherwise: {i: 1} }}}])"; - assertPipelineOptimizesTo(inputPipe, inputPipe); + outputPipe = R"( + [{$project: {redacted: false}}, + {$match: {$_internalSchemaAllowedProperties: { + properties: ['b'], + namePlaceholder: 'i', + patternProperties: [], + otherwise: {i: {$eq: 1} + }}}}])"; + serializedPipe = R"( + [{$project: {redacted: false}}, + {$match: {$_internalSchemaAllowedProperties: { + properties: ['b'], + namePlaceholder: 'i', + patternProperties: [], + otherwise: {i: 1} + }}}])"; + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); inputPipe = R"( [{$addFields : {a : {$const: 1}}}, @@ -1133,7 +1272,23 @@ TEST(PipelineOptimizationTest, MatchOnAllowedPropertiesShouldNotSwapSinceCategor patternProperties: [], otherwise: {i: 1} }}}])"; - assertPipelineOptimizesTo(inputPipe, inputPipe); + outputPipe = R"( + [{$addFields: {a: {$const: 1}}}, + {$match: {$_internalSchemaAllowedProperties: { + properties: ["b"], + namePlaceholder: "i", + patternProperties: [], + otherwise: {i: {$eq: 1} + }}}}])"; + serializedPipe = R"( + [{$addFields : {a : {$const: 1}}}, + {$match: {$_internalSchemaAllowedProperties: { + properties: ['b'], + namePlaceholder: 'i', + patternProperties: [], + otherwise: {i: 1} + }}}])"; + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); } // Descriptive test. The following internal match expression *could* participate in pipeline @@ -1142,17 +1297,35 @@ TEST(PipelineOptimizationTest, MatchOnCondShouldNotSwapSinceCategoryIsOther) { string inputPipe = "[{$project: {_id: true, a: '$b'}}, " "{$match: {$_internalSchemaCond: [{a: 1}, {b: 1}, {c: 1}]}}]"; - assertPipelineOptimizesTo(inputPipe, inputPipe); + string outputPipe = + "[{$project: {_id: true, a: '$b'}}, " + "{$match: {$_internalSchemaCond: [{a: {$eq : 1}}, {b: {$eq : 1}}, {c: {$eq : 1}}]}}]"; + string serializedPipe = + "[{$project: {_id: true, a: '$b'}}, " + "{$match: {$_internalSchemaCond: [{a: 1}, {b: 1}, {c: 1}]}}]"; + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); inputPipe = "[{$project: {redacted: false}}, " "{$match: {$_internalSchemaCond: [{a: 1}, {b: 1}, {c: 1}]}}]"; - assertPipelineOptimizesTo(inputPipe, inputPipe); + outputPipe = + "[{$project: {redacted: false}}, " + "{$match: {$_internalSchemaCond: [{a: {$eq : 1}}, {b: {$eq: 1}}, {c: {$eq: 1}}]}}]"; + serializedPipe = + "[{$project: {redacted: false}}, " + "{$match: {$_internalSchemaCond: [{a: 1}, {b: 1}, {c: 1}]}}]"; + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); inputPipe = "[{$addFields : {a : {$const: 1}}}, " "{$match: {$_internalSchemaCond: [{a: 1}, {b: 1}, {c: 1}]}}]"; - assertPipelineOptimizesTo(inputPipe, inputPipe); + outputPipe = + "[{$addFields : {a : {$const: 1}}}, " + "{$match: {$_internalSchemaCond: [{a: {$eq : 1}}, {b: {$eq: 1}}, {c: {$eq : 1}}]}}]"; + serializedPipe = + "[{$addFields : {a : {$const: 1}}}, " + "{$match: {$_internalSchemaCond: [{a: 1}, {b: 1}, {c: 1}]}}]"; + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); } // Descriptive test. The following internal match expression *could* participate in pipeline @@ -1180,17 +1353,35 @@ TEST(PipelineOptimizationTest, MatchOnInternalSchemaTypeShouldNotSwapSinceCatego string inputPipe = "[{$project: {_id: true, a: '$b'}}, " "{$match: {a: {$_internalSchemaType: 1}}}]"; - assertPipelineOptimizesTo(inputPipe, inputPipe); + string outputPipe = + "[{$project: {_id: true, a: '$b'}}, " + " {$match: {a: {$_internalSchemaType: [1]}}}]"; + string serializedPipe = + "[{$project: {_id: true, a: '$b'}}, " + "{$match: {a: {$_internalSchemaType: 1}}}]"; + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); inputPipe = "[{$project: {redacted: false}}, " "{$match: {a: {$_internalSchemaType: 1}}}]"; - assertPipelineOptimizesTo(inputPipe, inputPipe); + outputPipe = + "[{$project: {redacted: false}}, " + " {$match: {a: {$_internalSchemaType: [1]}}}]"; + serializedPipe = + "[{$project: {redacted: false}}, " + "{$match: {a: {$_internalSchemaType: 1}}}]"; + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); inputPipe = "[{$addFields : {a : {$const: 1}}}, " "{$match: {b: {$_internalSchemaType: 1}}}]"; - assertPipelineOptimizesTo(inputPipe, inputPipe); + outputPipe = + "[{$addFields : {a : {$const: 1}}}, " + " {$match: {b: {$_internalSchemaType: [1]}}}]"; + serializedPipe = + "[{$addFields : {a : {$const: 1}}}, " + "{$match: {b: {$_internalSchemaType: 1}}}]"; + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); } TEST(PipelineOptimizationTest, MatchOnMinLengthShouldSwapWithAdjacentStage) { @@ -1278,28 +1469,40 @@ TEST(PipelineOptimizationTest, MatchOnXorShouldSwapIfEverySubExpressionIsEligibl string outputPipe = "[{$match: {$_internalSchemaXor: [{b: {$eq: 1}}, {d: {$eq: 1}}]}}, " "{$project: {_id: true, a: '$b', c: '$d'}}]"; - assertPipelineOptimizesTo(inputPipe, outputPipe); + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, outputPipe); inputPipe = "[{$project: {redacted: false}}, " "{$match: {$_internalSchemaXor: [{a: 1}, {b: 1}]}}]"; outputPipe = - "[{$match: {$_internalSchemaXor: [{a: 1}, {b: 1}]}}, " + "[{$match: {$_internalSchemaXor: [{a: {$eq : 1}}, {b: {$eq : 1}}]}}, " "{$project: {redacted: false}}]"; - assertPipelineOptimizesTo(inputPipe, outputPipe); + string serializedPipe = + "[{$match: {$_internalSchemaXor: [{a: 1}, {b: 1}]}}, " + " {$project: {redacted: false}}]"; + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); inputPipe = "[{$addFields : {a : {$const: 1}}}, " "{$match: {$_internalSchemaXor: [{b: 1}, {c: 1}]}}]"; outputPipe = - "[{$match: {$_internalSchemaXor: [{b: 1}, {c: 1}]}}, " + "[{$match: {$_internalSchemaXor: [{b: {$eq: 1}}, {c: {$eq: 1}}]}}, " "{$addFields: {a: {$const: 1}}}]"; - assertPipelineOptimizesTo(inputPipe, outputPipe); + serializedPipe = + "[{$match: {$_internalSchemaXor: [{b: 1}, {c: 1}]}}, " + "{$addFields : {a : {$const: 1}}}]"; + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); inputPipe = "[{$addFields : {a : {$const: 1}}}, " "{$match: {$_internalSchemaXor: [{b: 1}, {a: 1}]}}]"; - assertPipelineOptimizesTo(inputPipe, inputPipe); + outputPipe = + "[{$addFields: {a: {$const: 1}}}, " + "{$match: {$_internalSchemaXor: [{b: {$eq: 1}}, {a: {$eq: 1}}]}}]"; + serializedPipe = + "[{$addFields: {a: {$const: 1}}}, " + "{$match: {$_internalSchemaXor: [{b: 1}, {a: 1}]}}]"; + assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe); } TEST(PipelineOptimizationTest, MatchOnFmodShouldSwapWithAdjacentStage) { @@ -1491,7 +1694,7 @@ class UnwindNotFinal : public Base { return "[{$unwind: {path: '$a'}}, {$match: {a:1}}]}"; } string shardPipeJson() { - return "[{$unwind: {path: '$a'}}, {$match: {a:1}}]}"; + return "[{$unwind: {path: '$a'}}, {$match: {a:{$eq:1}}}]}"; } string mergePipeJson() { return "[]}"; @@ -1503,7 +1706,7 @@ class UnwindWithOther : public Base { return "[{$match: {a:1}}, {$unwind: {path: '$a'}}]}"; } string shardPipeJson() { - return "[{$match: {a:1}}]}"; + return "[{$match: {a:{$eq:1}}}]}"; } string mergePipeJson() { return "[{$unwind: {path: '$a'}}]}"; @@ -1601,7 +1804,7 @@ class ShardedSortMatchProjSkipLimBecomesMatchTopKSortSkipProj : public Base { "]"; } string shardPipeJson() { - return "[{$match: {a: 1}}" + return "[{$match: {a: {$eq : 1}}}" ",{$sort: {sortKey: {a: 1}, limit: 8}}" ",{$project: {_id: true, a: true}}" "]"; |