summaryrefslogtreecommitdiff
path: root/src/mongo/db/query/query_planner_test.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/query/query_planner_test.cpp')
-rw-r--r--src/mongo/db/query/query_planner_test.cpp392
1 files changed, 391 insertions, 1 deletions
diff --git a/src/mongo/db/query/query_planner_test.cpp b/src/mongo/db/query/query_planner_test.cpp
index e07a5a37b51..6f69c90e520 100644
--- a/src/mongo/db/query/query_planner_test.cpp
+++ b/src/mongo/db/query/query_planner_test.cpp
@@ -1521,7 +1521,8 @@ TEST_F(QueryPlannerTest, CantUseHashedIndexToProvideSortWithIndexablePred) {
TEST_F(QueryPlannerTest, CantUseTextIndexToProvideSort) {
addIndex(BSON("x" << 1 << "_fts"
<< "text"
- << "_ftsx" << 1));
+ << "_ftsx"
+ << 1));
runQuerySortProj(BSONObj(), BSON("x" << 1), BSONObj());
ASSERT_EQUALS(getNumSolutions(), 1U);
@@ -5686,4 +5687,393 @@ TEST_F(QueryPlannerTest, SolutionSetStableWhenOrEnumerationLimitIsReached) {
"1}}}}}]}}");
}
+// Test that we enumerate the expected plans with the special parameter set. In this test we have
+// two branches of an $or, each with two possible indexed solutions.
+TEST_F(QueryPlannerTest, LockstepOrEnumerationSanityCheckTwoChildrenTwoIndexesEach) {
+ params.options =
+ QueryPlannerParams::NO_TABLE_SCAN | QueryPlannerParams::ENUMERATE_OR_CHILDREN_LOCKSTEP;
+ addIndex(BSON("a" << 1 << "b" << 1));
+ addIndex(BSON("a" << 1 << "c" << 1));
+
+ runQueryAsCommand(
+ fromjson("{find: 'testns', filter: {a: 1, $or: [{b: 1, c: 1}, {b: 2, c: 2}]}}"));
+
+ assertNumSolutions(6U);
+
+ assertSolutionExists(
+ "{or: {nodes: [{fetch: {filter: {c: {$eq: 1} }, node: {ixscan: {pattern: {a: 1, b: 1}}}}}, "
+ "{fetch: {filter: {c: {$eq: 2}}, node: {ixscan: {pattern: {a: 1, b: 1}}}}}]}}");
+ assertSolutionExists(
+ "{or: {nodes: [{fetch: {filter: {b: {$eq: 1} }, node: {ixscan: {pattern: {a: 1, c: 1}}}}}, "
+ "{fetch: {filter: {b: {$eq: 2}}, node: {ixscan: {pattern: {a: 1, c: 1}}}}}]}}");
+ assertSolutionExists(
+ "{or: {nodes: [{fetch: {filter: {c: {$eq: 1} }, node: {ixscan: {pattern: {a: 1, b: 1}}}}}, "
+ "{fetch: {filter: {b: {$eq: 2}}, node: {ixscan: {pattern: {a: 1, c: 1}}}}}]}}");
+ assertSolutionExists(
+ "{or: {nodes: [{fetch: {filter: {b: {$eq: 1} }, node: {ixscan: {pattern: {a: 1, c: 1}}}}}, "
+ "{fetch: {filter: {c: {$eq: 2}}, node: {ixscan: {pattern: {a: 1, b: 1}}}}}]}}");
+ assertSolutionExists(
+ "{fetch: {filter: {$or: [{b: {$eq: 1}, c: {$eq: 1}}, {b: {$eq: 2}, c: {$eq: 2}}]}, node: "
+ "{ixscan: {pattern: {a: 1, b: 1}}}}}}}");
+ assertSolutionExists(
+ "{fetch: {filter: {$or: [{b: {$eq: 1}, c: {$eq: 1}}, {b: {$eq: 2}, c: {$eq: 2}}]}, node: "
+ "{ixscan: {pattern: {a: 1, c: 1}}}}}}}");
+}
+
+// Test that we enumerate the expected plans with the special parameter set. In this test we have
+// two branches of an $or, each with one possible indexed solution.
+TEST_F(QueryPlannerTest, LockstepOrEnumerationSanityCheckTwoChildrenOneIndexEach) {
+ params.options =
+ QueryPlannerParams::NO_TABLE_SCAN | QueryPlannerParams::ENUMERATE_OR_CHILDREN_LOCKSTEP;
+ addIndex(BSON("a" << 1 << "b" << 1));
+ addIndex(BSON("a" << 1 << "c" << 1));
+
+ runQueryAsCommand(fromjson("{find: 'testns', filter: {a: 1, $or: [{b: 1}, {c: 2}]}}"));
+
+ assertNumSolutions(3U);
+
+ assertSolutionExists(
+ "{fetch: {filter: null, node: {or: {nodes: [{ixscan: {pattern: {a: 1, b: 1}}}, {ixscan: "
+ "{pattern: {a: 1, c: 1}}}]}}}}");
+ assertSolutionExists(
+ "{fetch: {filter: {$or: [{b: {$eq: 1}}, {c: {$eq: 2}}]}, node: {ixscan: {pattern: {a: 1, "
+ "b: 1}}}}}}}");
+ assertSolutionExists(
+ "{fetch: {filter: {$or: [{b: {$eq: 1}}, {c: {$eq: 2}}]}, node: {ixscan: {pattern: {a: 1, "
+ "c: 1}}}}}}}");
+}
+
+// Test that we enumerate the expected plans with the special parameter set. In this test we have
+// two branches of an $or, one with one possible indexed solution, the other with two possible
+// indexed solutions.
+TEST_F(QueryPlannerTest, LockstepOrEnumerationSanityCheckTwoChildrenDifferentNumSolutions) {
+ params.options =
+ QueryPlannerParams::NO_TABLE_SCAN | QueryPlannerParams::ENUMERATE_OR_CHILDREN_LOCKSTEP;
+ addIndex(BSON("a" << 1 << "b" << 1));
+ addIndex(BSON("a" << 1 << "c" << 1));
+
+ runQueryAsCommand(fromjson("{find: 'testns', filter: {a: 1, $or: [{b: 1}, {b: 2, c: 2}]}}"));
+
+ assertNumSolutions(4U);
+
+ assertSolutionExists(
+ "{fetch: {filter: null, node: {or: {nodes: [{ixscan: {pattern: {a: 1, b: 1}}}, {fetch: "
+ "{filter: {c: {$eq: 2}}, node: {ixscan: {pattern: {a: 1, b: 1}}}}}]}}}}");
+ assertSolutionExists(
+ "{fetch: {filter: null, node: {or: {nodes: [{ixscan: {pattern: {a: 1, b: 1}}}, {fetch: "
+ "{filter: {b: {$eq: 2}}, node: {ixscan: {pattern: {a: 1, c: 1}}}}}]}}}}");
+ assertSolutionExists(
+ "{fetch: {filter: {$or: [{b: {$eq: 1}}, {b: {$eq: 2}, c: {$eq: 2}}]}, node: {ixscan: "
+ "{pattern: {a: 1, b: 1}}}}}}}");
+ assertSolutionExists(
+ "{fetch: {filter: {$or: [{b: {$eq: 1}}, {b: {$eq: 2}, c: {$eq: 2}}]}, node: {ixscan: "
+ "{pattern: {a: 1, c: 1}}}}}}}");
+}
+
+// Test that the special parameter does in fact impact the order of enumeration. Here we rely on the
+// cap of number of or enumerations to prove that the plans we're interested in are enumerated
+// before we hit the limit.
+TEST_F(QueryPlannerTest, NormalOrEnumerationDoesNotPrioritizeLockstepIteration) {
+ params.options = QueryPlannerParams::NO_TABLE_SCAN;
+ ASSERT_EQ(internalQueryEnumerationMaxOrSolutions.load(), 10);
+ addIndex(BSON("a" << 1 << "b" << 1));
+ addIndex(BSON("a" << 1 << "c" << 1));
+ addIndex(BSON("a" << 1 << "d" << 1));
+
+ // For this query and the above indexes, each clause of the $or has three options to choose
+ // from, for a total of 3 * 3 * 3 = 27 possible enumerations for just that $or sub-branch.
+ runQueryAsCommand(
+ fromjson("{find: 'testns', filter: {a: 1, $or: [{b: 1, c: 1, d: 1}, {b: 2, c: 2, d: 2}, "
+ "{b: 3, c: 3, d: 3}]}}"));
+
+ // The $or enumeration is limited to 10, and then we have three plans where just the {a: 1}
+ // predicate is indexed.
+ assertNumSolutions(13U);
+
+ assertSolutionExists(
+ "{or: {nodes: ["
+ "{fetch: {filter: {c: {$eq: 1}, d: {$eq: 1}}, node: {ixscan: {pattern: {a: 1, b: 1}}}}}, "
+ "{fetch: {filter: {c: {$eq: 2}, d: {$eq: 2}}, node: {ixscan: {pattern: {a: 1, b: 1}}}}}, "
+ "{fetch: {filter: {c: {$eq: 3}, d: {$eq: 3}}, node: {ixscan: {pattern: {a: 1, b: 1}}}}} "
+ "]}}");
+ // Because we did not set the 'ENUMERATE_OR_CHILDREN_LOCKSTEP' flag, we don't expect this
+ // solution to be generated. This is in contrast to the next test case.
+ ASSERT_THROWS(
+ assertSolutionExists(
+ "{or: {nodes: ["
+ "{fetch: {filter: {b: {$eq: 1}, c: {$eq: 1}}, node: {ixscan: {pattern: {a: 1, d: "
+ "1}}}}}, "
+ "{fetch: {filter: {b: {$eq: 2}, c: {$eq: 2}}, node: {ixscan: {pattern: {a: 1, d: "
+ "1}}}}}, "
+ "{fetch: {filter: {b: {$eq: 3}, c: {$eq: 3}}, node: {ixscan: {pattern: {a: 1, d: "
+ "1}}}}} "
+ "]}}"),
+ unittest::TestAssertionFailureException);
+
+ // We still expect to generate the solutions which don't index the $or.
+ assertSolutionExists(
+ "{fetch: {filter: {$or: ["
+ "{b: {$eq: 1}, c: {$eq: 1}, d: {$eq: 1}}, "
+ "{b: {$eq: 2}, c: {$eq: 2}, d: {$eq: 2}}, "
+ "{b: {$eq: 3}, c: {$eq: 3}, d: {$eq: 3}} "
+ "]}, node: {ixscan: {pattern: {a: 1, b: 1}}}}}}");
+}
+
+TEST_F(QueryPlannerTest, LockstepOrEnumerationDoesPrioritizeLockstepIteration) {
+ params.options =
+ QueryPlannerParams::NO_TABLE_SCAN | QueryPlannerParams::ENUMERATE_OR_CHILDREN_LOCKSTEP;
+ ASSERT_EQ(internalQueryEnumerationMaxOrSolutions.load(), 10);
+ addIndex(BSON("a" << 1 << "b" << 1));
+ addIndex(BSON("a" << 1 << "c" << 1));
+ addIndex(BSON("a" << 1 << "d" << 1));
+
+ // For this query and the above indexes, each clause of the $or has three options to choose
+ // from, for a total of 3 * 3 * 3 = 27 possible enumerations for just that $or sub-branch.
+ runQueryAsCommand(
+ fromjson("{find: 'testns', filter: {a: 1, $or: [{b: 1, c: 1, d: 1}, {b: 2, c: 2, d: 2}, "
+ "{b: 3, c: 3, d: 3}]}}"));
+
+ // The $or enumeration is limited to 10, and then we have three plans where just the {a: 1}
+ // predicate is indexed.
+ assertNumSolutions(13U);
+
+ assertSolutionExists(
+ "{or: {nodes: ["
+ "{fetch: {filter: {c: {$eq: 1}, d: {$eq: 1}}, node: {ixscan: {pattern: {a: 1, b: 1}}}}}, "
+ "{fetch: {filter: {c: {$eq: 2}, d: {$eq: 2}}, node: {ixscan: {pattern: {a: 1, b: 1}}}}}, "
+ "{fetch: {filter: {c: {$eq: 3}, d: {$eq: 3}}, node: {ixscan: {pattern: {a: 1, b: 1}}}}} "
+ "]}}");
+ assertSolutionExists(
+ "{or: {nodes: ["
+ "{fetch: {filter: {b: {$eq: 1}, d: {$eq: 1}}, node: {ixscan: {pattern: {a: 1, c: 1}}}}}, "
+ "{fetch: {filter: {b: {$eq: 2}, d: {$eq: 2}}, node: {ixscan: {pattern: {a: 1, c: 1}}}}}, "
+ "{fetch: {filter: {b: {$eq: 3}, d: {$eq: 3}}, node: {ixscan: {pattern: {a: 1, c: 1}}}}} "
+ "]}}");
+ assertSolutionExists(
+ "{or: {nodes: ["
+ "{fetch: {filter: {b: {$eq: 1}, c: {$eq: 1}}, node: {ixscan: {pattern: {a: 1, d: 1}}}}}, "
+ "{fetch: {filter: {b: {$eq: 2}, c: {$eq: 2}}, node: {ixscan: {pattern: {a: 1, d: 1}}}}}, "
+ "{fetch: {filter: {b: {$eq: 3}, c: {$eq: 3}}, node: {ixscan: {pattern: {a: 1, d: 1}}}}} "
+ "]}}");
+ assertSolutionExists(
+ "{fetch: {filter: {$or: ["
+ "{b: {$eq: 1}, c: {$eq: 1}, d: {$eq: 1}}, "
+ "{b: {$eq: 2}, c: {$eq: 2}, d: {$eq: 2}}, "
+ "{b: {$eq: 3}, c: {$eq: 3}, d: {$eq: 3}} "
+ "]}, node: {ixscan: {pattern: {a: 1, b: 1}}}}}}");
+}
+
+TEST_F(QueryPlannerTest, LockstepOrEnumerationDoesPrioritizeLockstepIterationMixedChildren) {
+ params.options =
+ QueryPlannerParams::NO_TABLE_SCAN | QueryPlannerParams::ENUMERATE_OR_CHILDREN_LOCKSTEP;
+ ASSERT_EQ(internalQueryEnumerationMaxOrSolutions.load(), 10);
+ addIndex(BSON("a" << 1 << "b" << 1));
+ addIndex(BSON("a" << 1 << "c" << 1));
+ addIndex(BSON("a" << 1 << "d" << 1));
+ addIndex(BSON("a" << 1 << "e" << 1));
+
+ // For this query and the above indexes, each clause of the $or has a varying number options to
+ // choose from, for a total of 2 * 3 * 4 * 2 = 48 possible enumerations for just that $or
+ // sub-branch.
+ runQueryAsCommand(
+ fromjson("{find: 'testns', filter: {"
+ " a: 1,"
+ " $or: ["
+ " {b: 2.1, c: 2.1},"
+ " {b: 3, c: 3, d: 3},"
+ " {b: 4, c: 4, d: 4, e: 4},"
+ " {b: 2.2, c: 2.2}"
+ "]}}"));
+
+ // The $or enumeration is limited to 10, and then we have four plans where just the {a: 1}
+ // predicate is indexed.
+ assertNumSolutions(14U);
+
+ // Lockstep enumerations. Definitely expected.
+ assertSolutionExists(
+ "{or: {nodes: ["
+ "{fetch: {filter: {c: {$eq: 2.1}}, node: {ixscan: {pattern: {a: 1, b: 1}}}}}, "
+ "{fetch: {filter: {c: {$eq: 3}, d: {$eq: 3}}, node: {ixscan: {pattern: {a: 1, b: 1}}}}}, "
+ "{fetch: {filter: {c: {$eq: 4}, d: {$eq: 4}, e: {$eq: 4}},"
+ " node: {ixscan: {pattern: {a: 1, b: 1}}}}}, "
+ "{fetch: {filter: {c: {$eq: 2.2}}, node: {ixscan: {pattern: {a: 1, b: 1}}}}}"
+ "]}}");
+ assertSolutionExists(
+ "{or: {nodes: ["
+ "{fetch: {filter: {b: {$eq: 2.1}}, node: {ixscan: {pattern: {a: 1, c: 1}}}}}, "
+ "{fetch: {filter: {b: {$eq: 3}, d: {$eq: 3}}, node: {ixscan: {pattern: {a: 1, c: 1}}}}}, "
+ "{fetch: {filter: {b: {$eq: 4}, d: {$eq: 4}, e: {$eq: 4}},"
+ " node: {ixscan: {pattern: {a: 1, c: 1}}}}}, "
+ "{fetch: {filter: {b: {$eq: 2.2}}, node: {ixscan: {pattern: {a: 1, c: 1}}}}}"
+ "]}}");
+ // Everyone advances one more time, no longer lock step.
+ assertSolutionExists(
+ "{or: {nodes: ["
+ "{fetch: {filter: {c: {$eq: 2.1}}, node: {ixscan: {pattern: {a: 1, b: 1}}}}}, "
+ "{fetch: {filter: {b: {$eq: 3}, c: {$eq: 3}}, node: {ixscan: {pattern: {a: 1, d: 1}}}}}, "
+ "{fetch: {filter: {b: {$eq: 4}, c: {$eq: 4}, e: {$eq: 4}},"
+ " node: {ixscan: {pattern: {a: 1, d: 1}}}}}, "
+ "{fetch: {filter: {c: {$eq: 2.2}}, node: {ixscan: {pattern: {a: 1, b: 1}}}}}"
+ "]}}");
+ // Normal enumeration. Here we observe an interesting phenomena. Before we get into plan
+ // enumeration, the query is parsed and "normalized". This process involves putting the query in
+ // a canonical order, in part so that similar queries can be recognized as such for caching. In
+ // this case, it orders the $or children by their respective number of children. So our original
+ // query will be enumerated as if it were typed in this order:
+ // {a: 1,
+ // $or: [
+ // {b: 2.1, c: 2.1},
+ // {b: 2.2, c: 2.2},
+ // {b: 3, c: 3, d: 3},
+ // {b: 4, c: 4, d: 4, e: 4}
+ // ]
+ // }
+ // Here are the exact plans:
+ assertSolutionExists(
+ "{or: {nodes: ["
+ "{fetch: {filter: {b: {$eq: 2.1}}, node: {ixscan: {pattern: {a: 1, c: 1}}}}}, "
+ "{fetch: {filter: {c: {$eq: 2.2}}, node: {ixscan: {pattern: {a: 1, b: 1}}}}}, "
+ "{fetch: {filter: {b: {$eq: 3}, c: {$eq: 3}}, node: {ixscan: {pattern: {a: 1, d: 1}}}}}, "
+ "{fetch: {filter: {b: {$eq: 4}, c: {$eq: 4}, e: {$eq: 4}},"
+ " node: {ixscan: {pattern: {a: 1, d: 1}}}}}"
+ "]}}");
+ assertSolutionExists(
+ "{or: {nodes: ["
+ "{fetch: {filter: {c: {$eq: 2.1}}, node: {ixscan: {pattern: {a: 1, b: 1}}}}}, "
+ "{fetch: {filter: {b: {$eq: 2.2}}, node: {ixscan: {pattern: {a: 1, c: 1}}}}}, "
+ "{fetch: {filter: {b: {$eq: 3}, c: {$eq: 3}}, node: {ixscan: {pattern: {a: 1, d: 1}}}}}, "
+ "{fetch: {filter: {b: {$eq: 4}, c: {$eq: 4}, e: {$eq: 4}},"
+ " node: {ixscan: {pattern: {a: 1, d: 1}}}}}"
+ "]}}");
+ assertSolutionExists(
+ "{or: {nodes: ["
+ "{fetch: {filter: {b: {$eq: 2.1}}, node: {ixscan: {pattern: {a: 1, c: 1}}}}}, "
+ "{fetch: {filter: {b: {$eq: 2.2}}, node: {ixscan: {pattern: {a: 1, c: 1}}}}}, "
+ "{fetch: {filter: {b: {$eq: 3}, c: {$eq: 3}}, node: {ixscan: {pattern: {a: 1, d: 1}}}}}, "
+ "{fetch: {filter: {b: {$eq: 4}, c: {$eq: 4}, e: {$eq: 4}},"
+ " node: {ixscan: {pattern: {a: 1, d: 1}}}}}"
+ "]}}");
+ assertSolutionExists(
+ "{or: {nodes: ["
+ "{fetch: {filter: {c: {$eq: 2.1}}, node: {ixscan: {pattern: {a: 1, b: 1}}}}}, "
+ "{fetch: {filter: {c: {$eq: 2.2}}, node: {ixscan: {pattern: {a: 1, b: 1}}}}}, "
+ "{fetch: {filter: {c: {$eq: 3}, d: {$eq: 3}}, node: {ixscan: {pattern: {a: 1, b: 1}}}}}, "
+ "{fetch: {filter: {b: {$eq: 4}, c: {$eq: 4}, d: {$eq: 4}},"
+ " node: {ixscan: {pattern: {a: 1, e: 1}}}}}"
+ "]}}");
+ assertSolutionExists(
+ "{or: {nodes: ["
+ "{fetch: {filter: {b: {$eq: 2.1}}, node: {ixscan: {pattern: {a: 1, c: 1}}}}}, "
+ "{fetch: {filter: {c: {$eq: 2.2}}, node: {ixscan: {pattern: {a: 1, b: 1}}}}}, "
+ "{fetch: {filter: {c: {$eq: 3}, d: {$eq: 3}}, node: {ixscan: {pattern: {a: 1, b: 1}}}}}, "
+ "{fetch: {filter: {b: {$eq: 4}, c: {$eq: 4}, d: {$eq: 4}},"
+ " node: {ixscan: {pattern: {a: 1, e: 1}}}}}"
+ "]}}");
+ assertSolutionExists(
+ "{or: {nodes: ["
+ "{fetch: {filter: {c: {$eq: 2.1}}, node: {ixscan: {pattern: {a: 1, b: 1}}}}}, "
+ "{fetch: {filter: {b: {$eq: 2.2}}, node: {ixscan: {pattern: {a: 1, c: 1}}}}}, "
+ "{fetch: {filter: {c: {$eq: 3}, d: {$eq: 3}}, node: {ixscan: {pattern: {a: 1, b: 1}}}}}, "
+ "{fetch: {filter: {b: {$eq: 4}, c: {$eq: 4}, d: {$eq: 4}},"
+ " node: {ixscan: {pattern: {a: 1, e: 1}}}}}"
+ "]}}");
+ assertSolutionExists(
+ "{or: {nodes: ["
+ "{fetch: {filter: {b: {$eq: 2.1}}, node: {ixscan: {pattern: {a: 1, c: 1}}}}}, "
+ "{fetch: {filter: {b: {$eq: 2.2}}, node: {ixscan: {pattern: {a: 1, c: 1}}}}}, "
+ "{fetch: {filter: {c: {$eq: 3}, d: {$eq: 3}}, node: {ixscan: {pattern: {a: 1, b: 1}}}}}, "
+ "{fetch: {filter: {b: {$eq: 4}, c: {$eq: 4}, d: {$eq: 4}},"
+ " node: {ixscan: {pattern: {a: 1, e: 1}}}}}"
+ "]}}");
+
+ // Now to the solutions which don't index the $or.
+ assertSolutionExists(
+ "{fetch: {filter: {$or: ["
+ "{b: {$eq: 2.1}, c: {$eq: 2.1}}, "
+ "{b: {$eq: 2.2}, c: {$eq: 2.2}}, "
+ "{b: {$eq: 3}, c: {$eq: 3}, d: {$eq: 3}}, "
+ "{b: {$eq: 4}, c: {$eq: 4}, d: {$eq: 4}, e: {$eq: 4}} "
+ "]}, node: {ixscan: {pattern: {a: 1, b: 1}}}}}}");
+ assertSolutionExists(
+ "{fetch: {filter: {$or: ["
+ "{b: {$eq: 2.1}, c: {$eq: 2.1}}, "
+ "{b: {$eq: 2.2}, c: {$eq: 2.2}}, "
+ "{b: {$eq: 3}, c: {$eq: 3}, d: {$eq: 3}}, "
+ "{b: {$eq: 4}, c: {$eq: 4}, d: {$eq: 4}, e: {$eq: 4}} "
+ "]}, node: {ixscan: {pattern: {a: 1, c: 1}}}}}}");
+ assertSolutionExists(
+ "{fetch: {filter: {$or: ["
+ "{b: {$eq: 2.1}, c: {$eq: 2.1}}, "
+ "{b: {$eq: 2.2}, c: {$eq: 2.2}}, "
+ "{b: {$eq: 3}, c: {$eq: 3}, d: {$eq: 3}}, "
+ "{b: {$eq: 4}, c: {$eq: 4}, d: {$eq: 4}, e: {$eq: 4}} "
+ "]}, node: {ixscan: {pattern: {a: 1, d: 1}}}}}}");
+ assertSolutionExists(
+ "{fetch: {filter: {$or: ["
+ "{b: {$eq: 2.1}, c: {$eq: 2.1}}, "
+ "{b: {$eq: 2.2}, c: {$eq: 2.2}}, "
+ "{b: {$eq: 3}, c: {$eq: 3}, d: {$eq: 3}}, "
+ "{b: {$eq: 4}, c: {$eq: 4}, d: {$eq: 4}, e: {$eq: 4}} "
+ "]}, node: {ixscan: {pattern: {a: 1, e: 1}}}}}}");
+}
+
+TEST_F(QueryPlannerTest, LockstepOrEnumerationApplysToEachOrInTree) {
+ params.options =
+ QueryPlannerParams::NO_TABLE_SCAN | QueryPlannerParams::ENUMERATE_OR_CHILDREN_LOCKSTEP;
+ ASSERT_EQ(internalQueryEnumerationMaxOrSolutions.load(), 10);
+ addIndex(BSON("a" << 1 << "b" << 1));
+ addIndex(BSON("a" << 1 << "c" << 1));
+ addIndex(BSON("a" << 1 << "x" << 1));
+ addIndex(BSON("a" << 1 << "y" << 1));
+
+ // For this query and the above indexes, each clause of the $or has 2 indexes to choose from,
+ // for a total of 2 * 2 * 2 * 2 = 16 possible enumerations for just that $or sub-branch.
+ runQueryAsCommand(
+ fromjson("{find: 'testns', filter: {"
+ " a: 1,"
+ " $or: ["
+ " {b: 2.1, c: 2.1},"
+ " {b: 2.2, c: 2.2},"
+ " {$and: ["
+ " {unindexed: 'thisPredicateToEnsureNestedOrsAreNotCombined'},"
+ " {$or: ["
+ " {x: 3.0, y: 3.0},"
+ " {x: 3.1, y: 3.1}"
+ " ]}"
+ " ]}"
+ "]}}"));
+
+ // The $or enumeration is limited to 10, and then we have 4 plans where just the {a: 1}
+ // predicate is indexed.
+ assertNumSolutions(14U);
+
+ // Both lockstep enumerations should be present.
+ assertSolutionExists(
+ "{or: {nodes: ["
+ " {fetch: {filter: {c: {$eq: 2.1}}, node: {ixscan: {pattern: {a: 1, b: 1}}}}}, "
+ " {fetch: {filter: {c: {$eq: 2.2}}, node: {ixscan: {pattern: {a: 1, b: 1}}}}}, "
+ " {fetch: {"
+ " filter: {unindexed: {$eq: 'thisPredicateToEnsureNestedOrsAreNotCombined'}},"
+ " node: {"
+ " or: {nodes: ["
+ " {fetch: {filter: {y: {$eq: 3.0}}, node: {ixscan: {pattern: {a: 1, x: 1}}}}},"
+ " {fetch: {filter: {y: {$eq: 3.1}}, node: {ixscan: {pattern: {a: 1, x: 1}}}}}"
+ " ]}}"
+ " }}"
+ "]}}");
+ assertSolutionExists(
+ "{or: {nodes: ["
+ " {fetch: {filter: {b: {$eq: 2.1}}, node: {ixscan: {pattern: {a: 1, c: 1}}}}}, "
+ " {fetch: {filter: {b: {$eq: 2.2}}, node: {ixscan: {pattern: {a: 1, c: 1}}}}}, "
+ " {fetch: {"
+ " filter: {unindexed: {$eq: 'thisPredicateToEnsureNestedOrsAreNotCombined'}},"
+ " node: {"
+ " or: {nodes: ["
+ " {fetch: {filter: {x: {$eq: 3.0}}, node: {ixscan: {pattern: {a: 1, y: 1}}}}},"
+ " {fetch: {filter: {x: {$eq: 3.1}}, node: {ixscan: {pattern: {a: 1, y: 1}}}}}"
+ " ]}}"
+ " }}"
+ "]}}");
+}
+
} // namespace