summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTed Tuckman <ted.tuckman@mongodb.com>2021-01-21 14:44:35 -0500
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2021-01-25 15:57:46 +0000
commit2497bf18a4f2959501582f4084ad27212698b574 (patch)
treeaaf8c1f5dc5e0ae6fb0eeaa2796ee1bd468a65a8
parentdc70bfe933ba9f4b01a028eab72e44e2681fa09a (diff)
downloadmongo-2497bf18a4f2959501582f4084ad27212698b574.tar.gz
SERVER-52814 Serialize projection _id field to always equivalent projection spec
-rwxr-xr-xbuildscripts/resmokeconfig/suites/cst_jscore_passthrough.yml1
-rw-r--r--jstests/core/wildcard_index_projection.js68
-rw-r--r--src/mongo/db/cst/cst_pipeline_translation_test.cpp9
-rw-r--r--src/mongo/db/exec/exclusion_projection_executor.h11
-rw-r--r--src/mongo/db/exec/exclusion_projection_executor_test.cpp37
-rw-r--r--src/mongo/db/exec/inclusion_projection_executor.h3
-rw-r--r--src/mongo/db/exec/inclusion_projection_executor_test.cpp37
-rw-r--r--src/mongo/db/pipeline/document_source_project_test.cpp2
-rw-r--r--src/mongo/db/pipeline/pipeline_test.cpp67
9 files changed, 197 insertions, 38 deletions
diff --git a/buildscripts/resmokeconfig/suites/cst_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/cst_jscore_passthrough.yml
index b53de16836d..8dd8eab5389 100755
--- a/buildscripts/resmokeconfig/suites/cst_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/cst_jscore_passthrough.yml
@@ -26,6 +26,7 @@ selector:
- jstests/core/wildcard_index_collation.js
- jstests/core/wildcard_index_hint.js
- jstests/core/wildcard_index_return_key.js
+ - jstests/core/wildcard_index_projection.js
- jstests/core/update_array_offset_positional.js
- jstests/core/update_arraymatch6.js
- jstests/core/update_arraymatch8.js
diff --git a/jstests/core/wildcard_index_projection.js b/jstests/core/wildcard_index_projection.js
new file mode 100644
index 00000000000..f460852165d
--- /dev/null
+++ b/jstests/core/wildcard_index_projection.js
@@ -0,0 +1,68 @@
+/**
+ * Tests that a wildcard index with an exclusion projection but including _id field gets saved
+ * properly. Exercises the fix for SERVER-52814.
+ * @tags: [
+ * sbe_incompatible,
+ * requires_fcv_49,
+ * ]
+ */
+
+(function() {
+"use strict";
+
+load("jstests/libs/fixture_helpers.js"); // For isMongos.
+
+const collName = jsTestName();
+const coll = db[collName];
+coll.drop();
+coll.createIndex({"$**": 1}, {wildcardProjection: {name: 0, type: 0, _id: 1}});
+
+const sharded = FixtureHelpers.isMongos(db);
+
+// Return the explain object containing the winning plan and rejected plans.
+function getExplainObj(explainRes) {
+ if (sharded) {
+ return explainRes.queryPlanner.winningPlan.shards[0];
+ }
+
+ return explainRes.queryPlanner;
+}
+
+const indexes = coll.getIndexes().filter(idx => idx.name === "$**_1");
+assert.eq(1, indexes.length);
+const indexSpec = indexes[0];
+assert.eq(false, indexSpec.wildcardProjection.name, indexes);
+assert.eq(false, indexSpec.wildcardProjection.type, indexes);
+assert.eq(true, indexSpec.wildcardProjection._id, indexes);
+
+coll.insert({name: "Ted", type: "Person", _id: 1});
+coll.insert({name: "Bernard", type: "Person", _id: 2});
+const explainResFull = coll.find({_id: {$eq: 1}}).explain();
+const plannerRes = getExplainObj(explainResFull);
+// For a query on _id we expect that the IDHACK plan will be selected. However, we should also
+// observe a rejected plan which uses the wildcard index to resolve _id. In a sharded cluster we
+// may also need to skip the _id: hashed index.
+let indexStage = plannerRes.rejectedPlans[0].inputStage;
+if (sharded) {
+ if (indexStage.keyPattern._id === "hashed") {
+ assert.eq(plannerRes.rejectedPlans.length, 2, plannerRes.rejectedPlans);
+ indexStage = plannerRes.rejectedPlans[1].inputStage;
+ }
+} else {
+ assert.eq(plannerRes.rejectedPlans.length, 1, plannerRes.rejectedPlans);
+}
+
+assert.eq(indexStage.stage, "IXSCAN", indexStage);
+assert.eq(indexStage.keyPattern, {"$_path": 1, "_id": 1}, indexStage);
+
+// Ensure we use the index for _id if we supply a hint.
+const hintExplainRes = coll.find({_id: {$eq: 1}}).hint("$**_1").explain();
+const winningPlan = getExplainObj(hintExplainRes).winningPlan;
+assert.eq(winningPlan.inputStage.stage, "IXSCAN", winningPlan.inputStage);
+assert.eq(winningPlan.inputStage.keyPattern, {$_path: 1, _id: 1}, winningPlan.inputStage);
+
+// Test that the results are correct.
+const hintedResults = coll.find({_id: {$eq: 1}}).hint("$**_1").toArray();
+assert.eq(hintedResults.length, 1, hintedResults);
+assert.eq(hintedResults[0]._id, 1, hintedResults);
+})();
diff --git a/src/mongo/db/cst/cst_pipeline_translation_test.cpp b/src/mongo/db/cst/cst_pipeline_translation_test.cpp
index 0bd77dd3846..3d247af1e57 100644
--- a/src/mongo/db/cst/cst_pipeline_translation_test.cpp
+++ b/src/mongo/db/cst/cst_pipeline_translation_test.cpp
@@ -193,7 +193,7 @@ TEST(CstPipelineTranslationTest, TranslatesOneFieldExclusionProjectionStage) {
auto& singleDoc = dynamic_cast<DocumentSourceSingleDocumentTransformation&>(**iter);
// DocumenSourceSingleDocumentTransformation reorders fields so we need to be insensitive.
ASSERT(UnorderedFieldsBSONObjComparator{}.evaluate(
- BSON("a" << false) ==
+ BSON("a" << false << "_id" << true) ==
singleDoc.getTransformer().serializeTransformation(boost::none).toBson()));
}
@@ -244,7 +244,8 @@ TEST(CstPipelineTranslationTest, TranslatesCompoundObjectExclusionProjection) {
auto& singleDoc = dynamic_cast<DocumentSourceSingleDocumentTransformation&>(**iter);
// DocumenSourceSingleDocumentTransformation reorders fields so we need to be insensitive.
ASSERT(UnorderedFieldsBSONObjComparator{}.evaluate(
- BSON("a" << BSON("b" << BSON("c" << false << "d" << false << "e" << BSON("f" << false)))) ==
+ BSON("a" << BSON("b" << BSON("c" << false << "d" << false << "e" << BSON("f" << false)))
+ << "_id" << true) ==
singleDoc.getTransformer().serializeTransformation(boost::none).toBson()));
}
@@ -263,7 +264,7 @@ TEST(CstPipelineTranslationTest, TranslatesMultiComponentPathExclusionProjection
auto& singleDoc = dynamic_cast<DocumentSourceSingleDocumentTransformation&>(**iter);
// DocumenSourceSingleDocumentTransformation reorders fields so we need to be insensitive.
ASSERT(UnorderedFieldsBSONObjComparator{}.evaluate(
- BSON("a" << BSON("b" << BSON("c" << BSON("d" << false)))) ==
+ BSON("a" << BSON("b" << BSON("c" << BSON("d" << false))) << "_id" << true) ==
singleDoc.getTransformer().serializeTransformation(boost::none).toBson()));
}
@@ -399,7 +400,7 @@ TEST(CstPipelineTranslationTest, TranslatesMultipleProjectionStages) {
auto& singleDoc = dynamic_cast<DocumentSourceSingleDocumentTransformation&>(**iter++);
// DocumenSourceSingleDocumentTransformation reorders fields so we need to be insensitive.
ASSERT(UnorderedFieldsBSONObjComparator{}.evaluate(
- BSON("b" << false) ==
+ BSON("b" << false << "_id" << true) ==
singleDoc.getTransformer().serializeTransformation(boost::none).toBson()));
}
{
diff --git a/src/mongo/db/exec/exclusion_projection_executor.h b/src/mongo/db/exec/exclusion_projection_executor.h
index cf21bf57126..dd4854e39ea 100644
--- a/src/mongo/db/exec/exclusion_projection_executor.h
+++ b/src/mongo/db/exec/exclusion_projection_executor.h
@@ -106,7 +106,16 @@ public:
Document serializeTransformation(
boost::optional<ExplainOptions::Verbosity> explain) const final {
- return _root->serialize(explain);
+ MutableDocument output;
+
+ // The ExclusionNode tree in '_root' will always have a top-level _id node if _id is to be
+ // excluded. If the _id node is not present, then explicitly set {_id: true} to avoid
+ // ambiguity in the expected behavior of the serialized projection.
+ _root->serialize(explain, &output);
+ if (output.peek()["_id"].missing()) {
+ output.addField("_id", Value{true});
+ }
+ return output.freeze();
}
/**
diff --git a/src/mongo/db/exec/exclusion_projection_executor_test.cpp b/src/mongo/db/exec/exclusion_projection_executor_test.cpp
index 51893b71838..9ddd696e861 100644
--- a/src/mongo/db/exec/exclusion_projection_executor_test.cpp
+++ b/src/mongo/db/exec/exclusion_projection_executor_test.cpp
@@ -106,6 +106,43 @@ TEST(ExclusionProjectionExecutionTest, ShouldSerializeToEquivalentProjection) {
ASSERT_VALUE_EQ(serialization["x"].getDocument()["y"], Value(false));
}
+TEST(ExclusionProjectionExecutionTest, ShouldSerializeWithTopLevelID) {
+ auto exclusion = makeExclusionProjectionWithDefaultPolicies(BSON("a" << 0 << "b" << 0));
+ auto serialization = exclusion->serializeTransformation(boost::none);
+ ASSERT_VALUE_EQ(serialization["a"], Value(false));
+ ASSERT_VALUE_EQ(serialization["b"], Value(false));
+ ASSERT_VALUE_EQ(serialization["_id"], Value(true));
+
+ exclusion = makeExclusionProjectionWithDefaultPolicies(
+ BSON("a" << 0 << "b" << BSON("c" << 0 << "d" << 0)));
+ serialization = exclusion->serializeTransformation(boost::none);
+ ASSERT_VALUE_EQ(serialization["a"], Value(false));
+ ASSERT_VALUE_EQ(serialization["b"]["c"], Value(false));
+ ASSERT_VALUE_EQ(serialization["b"]["d"], Value(false));
+ ASSERT_VALUE_EQ(serialization["_id"], Value(true));
+ ASSERT_VALUE_EQ(serialization["b"]["_id"], Value());
+
+ exclusion = makeExclusionProjectionWithDefaultIdExclusion(BSON("a" << false << "b" << false));
+ serialization = exclusion->serializeTransformation(boost::none);
+ ASSERT_VALUE_EQ(serialization["a"], Value(false));
+ ASSERT_VALUE_EQ(serialization["b"], Value(false));
+ ASSERT_VALUE_EQ(serialization["_id"], Value(false));
+
+ exclusion = makeExclusionProjectionWithDefaultIdExclusion(
+ BSON("a" << false << "b" << false << "_id" << false));
+ serialization = exclusion->serializeTransformation(boost::none);
+ ASSERT_VALUE_EQ(serialization["a"], Value(false));
+ ASSERT_VALUE_EQ(serialization["b"], Value(false));
+ ASSERT_VALUE_EQ(serialization["_id"], Value(false));
+
+ exclusion = makeExclusionProjectionWithDefaultIdExclusion(
+ BSON("a" << false << "b" << false << "_id" << true));
+ serialization = exclusion->serializeTransformation(boost::none);
+ ASSERT_VALUE_EQ(serialization["a"], Value(false));
+ ASSERT_VALUE_EQ(serialization["b"], Value(false));
+ ASSERT_VALUE_EQ(serialization["_id"], Value(true));
+}
+
TEST(ExclusionProjectionExecutionTest, ShouldNotAddAnyDependencies) {
// An exclusion projection will cause the stage to return DepsTracker::State::SEE_NEXT, meaning
// it doesn't strictly require any fields.
diff --git a/src/mongo/db/exec/inclusion_projection_executor.h b/src/mongo/db/exec/inclusion_projection_executor.h
index b095a423652..c21196d11e5 100644
--- a/src/mongo/db/exec/inclusion_projection_executor.h
+++ b/src/mongo/db/exec/inclusion_projection_executor.h
@@ -169,6 +169,9 @@ public:
boost::optional<ExplainOptions::Verbosity> explain) const final {
MutableDocument output;
+ // The InclusionNode tree in '_root' will always have a top-level _id node if _id is to be
+ // included. If the _id node is not present, then explicitly set {_id: false} to avoid
+ // ambiguity in the expected behavior of the serialized projection.
_root->serialize(explain, &output);
if (output.peek()["_id"].missing()) {
output.addField("_id", Value{false});
diff --git a/src/mongo/db/exec/inclusion_projection_executor_test.cpp b/src/mongo/db/exec/inclusion_projection_executor_test.cpp
index 1b61ab11f9b..d80c741e0c0 100644
--- a/src/mongo/db/exec/inclusion_projection_executor_test.cpp
+++ b/src/mongo/db/exec/inclusion_projection_executor_test.cpp
@@ -305,6 +305,43 @@ TEST_F(InclusionProjectionExecutionTestWithoutFallBackToDefault,
inclusion->serializeTransformation(ExplainOptions::Verbosity::kExecAllPlans));
}
+TEST_F(InclusionProjectionExecutionTestWithoutFallBackToDefault, ShouldSerializeWithTopLevelID) {
+ auto inclusion = makeInclusionProjectionWithDefaultPolicies(BSON("a" << 1 << "b" << 1));
+ auto serialization = inclusion->serializeTransformation(boost::none);
+ ASSERT_VALUE_EQ(serialization["a"], Value(true));
+ ASSERT_VALUE_EQ(serialization["b"], Value(true));
+ ASSERT_VALUE_EQ(serialization["_id"], Value(true));
+
+ inclusion = makeInclusionProjectionWithDefaultPolicies(
+ BSON("a" << 1 << "b" << BSON("c" << 1 << "d" << 1)));
+ serialization = inclusion->serializeTransformation(boost::none);
+ ASSERT_VALUE_EQ(serialization["a"], Value(true));
+ ASSERT_VALUE_EQ(serialization["b"]["c"], Value(true));
+ ASSERT_VALUE_EQ(serialization["b"]["d"], Value(true));
+ ASSERT_VALUE_EQ(serialization["_id"], Value(true));
+ ASSERT_VALUE_EQ(serialization["b"]["_id"], Value());
+
+ inclusion = makeInclusionProjectionWithDefaultIdExclusion(BSON("a" << true << "b" << true));
+ serialization = inclusion->serializeTransformation(boost::none);
+ ASSERT_VALUE_EQ(serialization["a"], Value(true));
+ ASSERT_VALUE_EQ(serialization["b"], Value(true));
+ ASSERT_VALUE_EQ(serialization["_id"], Value(false));
+
+ inclusion = makeInclusionProjectionWithDefaultIdExclusion(
+ BSON("a" << true << "b" << true << "_id" << false));
+ serialization = inclusion->serializeTransformation(boost::none);
+ ASSERT_VALUE_EQ(serialization["a"], Value(true));
+ ASSERT_VALUE_EQ(serialization["b"], Value(true));
+ ASSERT_VALUE_EQ(serialization["_id"], Value(false));
+
+ inclusion = makeInclusionProjectionWithDefaultIdExclusion(
+ BSON("a" << true << "b" << true << "_id" << true));
+ serialization = inclusion->serializeTransformation(boost::none);
+ ASSERT_VALUE_EQ(serialization["a"], Value(true));
+ ASSERT_VALUE_EQ(serialization["b"], Value(true));
+ ASSERT_VALUE_EQ(serialization["_id"], Value(true));
+}
+
TEST_F(InclusionProjectionExecutionTestWithFallBackToDefault, ShouldOptimizeTopLevelExpressions) {
auto inclusion =
makeInclusionProjectionWithDefaultPolicies(BSON("a" << BSON("$add" << BSON_ARRAY(1 << 2))));
diff --git a/src/mongo/db/pipeline/document_source_project_test.cpp b/src/mongo/db/pipeline/document_source_project_test.cpp
index 685de1e3603..ced99ce0200 100644
--- a/src/mongo/db/pipeline/document_source_project_test.cpp
+++ b/src/mongo/db/pipeline/document_source_project_test.cpp
@@ -430,7 +430,7 @@ TEST_F(UnsetTest, UnsetSerializesToProject) {
vector<Value> serializedArray;
unsetStage->serializeToArray(serializedArray);
auto serializedUnsetStage = serializedArray[0].getDocument().toBson();
- ASSERT_BSONOBJ_EQ(serializedUnsetStage, fromjson("{$project: {b: {c: false}}}"));
+ ASSERT_BSONOBJ_EQ(serializedUnsetStage, fromjson("{$project: {b: {c: false}, _id: true}}"));
auto projectStage =
DocumentSourceProject::createFromBson(serializedUnsetStage.firstElement(), getExpCtx());
projectStage->serializeToArray(serializedArray);
diff --git a/src/mongo/db/pipeline/pipeline_test.cpp b/src/mongo/db/pipeline/pipeline_test.cpp
index 93813da0e66..62ec234c501 100644
--- a/src/mongo/db/pipeline/pipeline_test.cpp
+++ b/src/mongo/db/pipeline/pipeline_test.cpp
@@ -1225,14 +1225,17 @@ TEST(PipelineOptimizationTest, GraphLookupShouldSwapWithMatch) {
TEST(PipelineOptimizationTest, ExclusionProjectShouldSwapWithIndependentMatch) {
string inputPipe = "[{$project: {redacted: 0}}, {$match: {unrelated: 4}}]";
- string outputPipe = "[{$match: {unrelated: {$eq : 4}}}, {$project: {redacted: false}}]";
- string serializedPipe = "[{$match : {unrelated : 4}}, {$project : {redacted : false}}]";
+ string outputPipe =
+ "[{$match: {unrelated: {$eq : 4}}}, {$project: {redacted: false, _id: true}}]";
+ string serializedPipe =
+ "[{$match : {unrelated : 4}}, {$project : {redacted : false, _id: true}}]";
assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe);
}
TEST(PipelineOptimizationTest, ExclusionProjectShouldNotSwapWithMatchOnExcludedFields) {
std::string pipeline =
- "[{$project: {subdoc: {redacted: false}}}, {$match: {'subdoc.redacted': {$eq : 4}}}]";
+ "[{$project: {subdoc: {redacted: false}, _id: true}}, {$match: {'subdoc.redacted': {$eq : "
+ "4}}}]";
assertPipelineOptimizesTo(pipeline, pipeline);
}
@@ -1242,7 +1245,7 @@ TEST(PipelineOptimizationTest, MatchShouldSplitIfPartIsIndependentOfExclusionPro
" {$match: {redacted: 'x', unrelated: 4}}]";
string outputPipe =
"[{$match: {unrelated: {$eq: 4}}},"
- " {$project: {redacted: false}},"
+ " {$project: {redacted: false, _id: true}},"
" {$match: {redacted: {$eq: 'x'}}}]";
assertPipelineOptimizesTo(inputPipe, outputPipe);
}
@@ -1452,7 +1455,7 @@ TEST(PipelineOptimizationTest, MatchOnMinItemsShouldNotSwapSinceCategoryIsArrayM
assertPipelineOptimizesTo(inputPipe, inputPipe);
inputPipe =
- "[{$project: {redacted: false}}, "
+ "[{$project: {redacted: false, _id: true}}, "
"{$match: {a: {$_internalSchemaMinItems: 1}}}]";
assertPipelineOptimizesTo(inputPipe, inputPipe);
@@ -1471,7 +1474,7 @@ TEST(PipelineOptimizationTest, MatchOnMaxItemsShouldNotSwapSinceCategoryIsArrayM
assertPipelineOptimizesTo(inputPipe, inputPipe);
inputPipe =
- "[{$project: {redacted: false}}, "
+ "[{$project: {redacted: false, _id: true}}, "
"{$match: {a: {$_internalSchemaMaxItems: 1}}}]";
assertPipelineOptimizesTo(inputPipe, inputPipe);
@@ -1491,7 +1494,7 @@ TEST(PipelineOptimizationTest,
assertPipelineOptimizesTo(inputPipe, inputPipe);
inputPipe =
- "[{$project: {redacted: false}}, "
+ "[{$project: {redacted: false, _id: true}}, "
"{$match: {a: {$_internalSchemaAllElemMatchFromIndex: [1, {b: {$gt: 0}}]}}}]";
assertPipelineOptimizesTo(inputPipe, inputPipe);
@@ -1511,7 +1514,7 @@ TEST(PipelineOptimizationTest, MatchOnArrayIndexShouldNotSwapSinceCategoryIsArra
assertPipelineOptimizesTo(inputPipe, inputPipe);
inputPipe = R"(
- [{$project: {redacted: false}},
+ [{$project: {redacted: false, _id: true}},
{$match: {a: {$_internalSchemaMatchArrayIndex:
{index: 0, namePlaceholder: 'i', expression: {i: {$lt: 0}}}}}}])";
assertPipelineOptimizesTo(inputPipe, inputPipe);
@@ -1532,7 +1535,7 @@ TEST(PipelineOptimizationTest, MatchOnUniqueItemsShouldNotSwapSinceCategoryIsArr
assertPipelineOptimizesTo(inputPipe, inputPipe);
inputPipe =
- "[{$project: {redacted: false}}, "
+ "[{$project: {redacted: false, _id: true}}, "
"{$match: {a: {$_internalSchemaUniqueItems: true}}}]";
assertPipelineOptimizesTo(inputPipe, inputPipe);
@@ -1561,10 +1564,10 @@ TEST(PipelineOptimizationTest, MatchOnObjectMatchShouldNotSwapSinceCategoryIsOth
"[{$project: {redacted: false}}, "
"{$match: {a: {$_internalSchemaObjectMatch: {b: 1}}}}]";
outputPipe =
- "[{$project: {redacted: false}},"
+ "[{$project: {redacted: false, _id: true}},"
"{$match: {a: {$_internalSchemaObjectMatch: {b: {$eq: 1}}}}}]";
serializedPipe =
- "[{$project: {redacted: false}},"
+ "[{$project: {redacted: false, _id: true}},"
"{$match: {a: {$_internalSchemaObjectMatch: {b: 1}}}}]";
assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe);
@@ -1591,7 +1594,7 @@ TEST(PipelineOptimizationTest, MatchOnMinPropertiesShouldNotSwapSinceCategoryIsO
assertPipelineOptimizesTo(inputPipe, inputPipe);
inputPipe =
- "[{$project: {redacted: false}}, "
+ "[{$project: {redacted: false, _id: true}}, "
"{$match: {$_internalSchemaMinProperties: 2}}]";
assertPipelineOptimizesTo(inputPipe, inputPipe);
@@ -1610,7 +1613,7 @@ TEST(PipelineOptimizationTest, MatchOnMaxPropertiesShouldNotSwapSinceCategoryIsO
assertPipelineOptimizesTo(inputPipe, inputPipe);
inputPipe =
- "[{$project: {redacted: false}}, "
+ "[{$project: {redacted: false, _id: true}}, "
"{$match: {$_internalSchemaMaxProperties: 2}}]";
assertPipelineOptimizesTo(inputPipe, inputPipe);
@@ -1658,7 +1661,7 @@ TEST(PipelineOptimizationTest, MatchOnAllowedPropertiesShouldNotSwapSinceCategor
otherwise: {i: 1}
}}}])";
outputPipe = R"(
- [{$project: {redacted: false}},
+ [{$project: {redacted: false, _id: true}},
{$match: {$_internalSchemaAllowedProperties: {
properties: ['b'],
namePlaceholder: 'i',
@@ -1666,7 +1669,7 @@ TEST(PipelineOptimizationTest, MatchOnAllowedPropertiesShouldNotSwapSinceCategor
otherwise: {i: {$eq: 1}
}}}}])";
serializedPipe = R"(
- [{$project: {redacted: false}},
+ [{$project: {redacted: false, _id: true}},
{$match: {$_internalSchemaAllowedProperties: {
properties: ['b'],
namePlaceholder: 'i',
@@ -1720,10 +1723,10 @@ TEST(PipelineOptimizationTest, MatchOnCondShouldNotSwapSinceCategoryIsOther) {
"[{$project: {redacted: false}}, "
"{$match: {$_internalSchemaCond: [{a: 1}, {b: 1}, {c: 1}]}}]";
outputPipe =
- "[{$project: {redacted: false}}, "
+ "[{$project: {redacted: false, _id: true}}, "
"{$match: {$_internalSchemaCond: [{a: {$eq : 1}}, {b: {$eq: 1}}, {c: {$eq: 1}}]}}]";
serializedPipe =
- "[{$project: {redacted: false}}, "
+ "[{$project: {redacted: false, _id: true}}, "
"{$match: {$_internalSchemaCond: [{a: 1}, {b: 1}, {c: 1}]}}]";
assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe);
@@ -1748,7 +1751,7 @@ TEST(PipelineOptimizationTest, MatchOnRootDocEqShouldNotSwapSinceCategoryIsOther
assertPipelineOptimizesTo(inputPipe, inputPipe);
inputPipe =
- "[{$project: {redacted: false}}, "
+ "[{$project: {redacted: false, _id: true}}, "
"{$match: {$_internalSchemaRootDocEq: {a: 1}}}]";
assertPipelineOptimizesTo(inputPipe, inputPipe);
@@ -1776,10 +1779,10 @@ TEST(PipelineOptimizationTest, MatchOnInternalSchemaTypeShouldNotSwapSinceCatego
"[{$project: {redacted: false}}, "
"{$match: {a: {$_internalSchemaType: 1}}}]";
outputPipe =
- "[{$project: {redacted: false}}, "
+ "[{$project: {redacted: false, _id: true}}, "
" {$match: {a: {$_internalSchemaType: [1]}}}]";
serializedPipe =
- "[{$project: {redacted: false}}, "
+ "[{$project: {redacted: false, _id: true}}, "
"{$match: {a: {$_internalSchemaType: 1}}}]";
assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe);
@@ -1809,7 +1812,7 @@ TEST(PipelineOptimizationTest, MatchOnMinLengthShouldSwapWithAdjacentStage) {
"{$match: {a: {$_internalSchemaMinLength: 1}}}]";
outputPipe =
"[{$match: {a: {$_internalSchemaMinLength: 1}}},"
- "{$project: {redacted: false}}]";
+ "{$project: {redacted: false, _id: true}}]";
assertPipelineOptimizesTo(inputPipe, outputPipe);
inputPipe =
@@ -1835,7 +1838,7 @@ TEST(PipelineOptimizationTest, MatchOnMaxLengthShouldSwapWithAdjacentStage) {
"{$match: {a: {$_internalSchemaMaxLength: 1}}}]";
outputPipe =
"[{$match: {a: {$_internalSchemaMaxLength: 1}}}, "
- "{$project: {redacted: false}}]";
+ "{$project: {redacted: false, _id: true}}]";
assertPipelineOptimizesTo(inputPipe, outputPipe);
inputPipe =
@@ -1857,11 +1860,11 @@ TEST(PipelineOptimizationTest, MatchOnInternalEqShouldSwapWithAdjacentStage) {
assertPipelineOptimizesTo(inputPipe, outputPipe);
inputPipe =
- "[{$project: {redacted: false}}, "
+ "[{$project: {redacted: false, _id: true}}, "
"{$match: {a: {$_internalSchemaEq: {c: 1}}}}]";
outputPipe =
"[{$match: {a: {$_internalSchemaEq: {c: 1}}}}, "
- "{$project: {redacted: false}}]";
+ "{$project: {redacted: false, _id: true}}]";
assertPipelineOptimizesTo(inputPipe, outputPipe);
inputPipe =
@@ -1887,10 +1890,10 @@ TEST(PipelineOptimizationTest, MatchOnXorShouldSwapIfEverySubExpressionIsEligibl
"{$match: {$_internalSchemaXor: [{a: 1}, {b: 1}]}}]";
outputPipe =
"[{$match: {$_internalSchemaXor: [{a: {$eq : 1}}, {b: {$eq : 1}}]}}, "
- "{$project: {redacted: false}}]";
+ "{$project: {redacted: false, _id: true}}]";
string serializedPipe =
"[{$match: {$_internalSchemaXor: [{a: 1}, {b: 1}]}}, "
- " {$project: {redacted: false}}]";
+ " {$project: {redacted: false, _id: true}}]";
assertPipelineOptimizesAndSerializesTo(inputPipe, outputPipe, serializedPipe);
inputPipe =
@@ -1926,11 +1929,11 @@ TEST(PipelineOptimizationTest, MatchOnFmodShouldSwapWithAdjacentStage) {
assertPipelineOptimizesTo(inputPipe, outputPipe);
inputPipe =
- "[{$project: {redacted: false}}, "
+ "[{$project: {redacted: false, _id: true}}, "
"{$match: {a: {$_internalSchemaFmod: [5, 0]}}}]";
outputPipe =
"[{$match: {a: {$_internalSchemaFmod: [5, 0]}}}, "
- "{$project: {redacted: false}}]";
+ "{$project: {redacted: false, _id: true}}]";
assertPipelineOptimizesTo(inputPipe, outputPipe);
inputPipe =
@@ -2248,7 +2251,7 @@ TEST(PipelineOptimizationTest, MatchGetsPushedIntoBothChildrenOfUnion) {
" coll: 'unionColl',"
" pipeline: ["
" {$match: {x: {$eq: 2}}},"
- " {$project: {y: false}},"
+ " {$project: {y: false, _id: true}},"
" {$sort: {sortKey: {score: 1}}}"
" ]"
" }}"
@@ -2259,7 +2262,7 @@ TEST(PipelineOptimizationTest, MatchGetsPushedIntoBothChildrenOfUnion) {
" coll: 'unionColl',"
" pipeline: ["
" {$match: {x: {$eq: 2}}},"
- " {$project: {y: false}},"
+ " {$project: {y: false, _id: true}},"
" {$sort: {score: 1}}"
" ]"
" }}"
@@ -2272,10 +2275,10 @@ TEST(PipelineOptimizationTest, ProjectGetsPushedIntoBothChildrenOfUnion) {
" {$unionWith: 'unionColl'},"
" {$project: {x: false}}"
"]",
- "[{$project: {x: false}},"
+ "[{$project: {x: false, _id: true}},"
" {$unionWith: {"
" coll: 'unionColl',"
- " pipeline: [{$project: {x: false}}]"
+ " pipeline: [{$project: {x: false, _id: true}}]"
" }}]");
// Test an inclusion projection.