summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjannaerin <golden.janna@gmail.com>2017-10-25 10:31:59 -0400
committerjannaerin <golden.janna@gmail.com>2017-10-30 23:00:02 -0400
commitefd028178dbf938a9f52abf6e9434dea6aa508e3 (patch)
treefbac67cb064389ef85778627bb200eabf065c8f7
parentb49b20887cf13720c0e863d24da95cc0239889f8 (diff)
downloadmongo-efd028178dbf938a9f52abf6e9434dea6aa508e3.tar.gz
SERVER-31292: Make explain reflect optimizations
-rw-r--r--jstests/core/optimized_match_explain.js23
-rw-r--r--jstests/sharding/aggregation_currentop.js2
-rw-r--r--src/mongo/db/pipeline/document_source_lookup_test.cpp72
-rw-r--r--src/mongo/db/pipeline/document_source_match.cpp6
-rw-r--r--src/mongo/db/pipeline/document_source_match_test.cpp14
-rw-r--r--src/mongo/db/pipeline/pipeline_test.cpp335
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}}"
"]";