summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
authorBernard Gorman <bernard.gorman@gmail.com>2018-09-07 02:43:48 +0100
committerBernard Gorman <bernard.gorman@gmail.com>2018-09-11 12:08:37 +0100
commita08202469ed08f507f2d6cbb8e45158e4cf10839 (patch)
tree2bb087230044da0498564dc9678741461ab96d42 /src/mongo
parentfe8f517a59d694b7577da564d19e4415e13831e8 (diff)
downloadmongo-a08202469ed08f507f2d6cbb8e45158e4cf10839.tar.gz
SERVER-36521 Prevent allPaths indexes from participating in index intersection
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/db/query/planner_access.cpp10
-rw-r--r--src/mongo/db/query/query_planner_all_paths_index_test.cpp77
2 files changed, 87 insertions, 0 deletions
diff --git a/src/mongo/db/query/planner_access.cpp b/src/mongo/db/query/planner_access.cpp
index d928ef62949..1193aef1772 100644
--- a/src/mongo/db/query/planner_access.cpp
+++ b/src/mongo/db/query/planner_access.cpp
@@ -1015,6 +1015,16 @@ std::unique_ptr<QuerySolutionNode> QueryPlannerAccess::buildIndexedAnd(
if (ixscanNodes.size() == 1) {
andResult = std::move(ixscanNodes[0]);
} else {
+ // $** indexes are prohibited from participating in either AND_SORTED or AND_HASH.
+ const bool allPathsIndexInvolvedInIntersection =
+ std::any_of(ixscanNodes.begin(), ixscanNodes.end(), [](const auto& ixScan) {
+ return ixScan->getType() == StageType::STAGE_IXSCAN &&
+ static_cast<IndexScanNode*>(ixScan.get())->index.type == INDEX_ALLPATHS;
+ });
+ if (allPathsIndexInvolvedInIntersection) {
+ return nullptr;
+ }
+
// Figure out if we want AndHashNode or AndSortedNode.
bool allSortedByDiskLoc = true;
for (size_t i = 0; i < ixscanNodes.size(); ++i) {
diff --git a/src/mongo/db/query/query_planner_all_paths_index_test.cpp b/src/mongo/db/query/query_planner_all_paths_index_test.cpp
index af391965404..0e6374252c1 100644
--- a/src/mongo/db/query/query_planner_all_paths_index_test.cpp
+++ b/src/mongo/db/query/query_planner_all_paths_index_test.cpp
@@ -591,4 +591,81 @@ TEST_F(QueryPlannerAllPathsTest, InBasicOrEquivalent) {
// TODO SERVER-35331: Add testing for hints.
// TODO SERVER-36145: Add testing for non-blocking sort.
+//
+// Index intersection tests.
+//
+
+TEST_F(QueryPlannerAllPathsTest, AllPathsIndexDoesNotParticipateInIndexIntersection) {
+ // Enable both AND_SORTED and AND_HASH index intersection for this test.
+ params.options |= QueryPlannerParams::INDEX_INTERSECTION;
+ internalQueryPlannerEnableHashIntersection.store(true);
+
+ // Add two standard single-field indexes.
+ addIndex(BSON("a" << 1));
+ addIndex(BSON("b" << 1));
+
+ // Run a point query on both fields and confirm that an AND_SORTED plan is generated.
+ runQuery(fromjson("{a:10, b:10}"));
+ // Three plans are generated: one IXSCAN for each index, and an AND_SORTED on both.
+ ASSERT_EQUALS(getNumSolutions(), 3U);
+ assertSolutionExists(
+ "{fetch: {filter: {a:10}, node: {ixscan: {filter: null, pattern: {b:1}}}}}");
+ assertSolutionExists(
+ "{fetch: {filter: {b:10}, node: {ixscan: {filter: null, pattern: {a:1}}}}}");
+ assertSolutionExists(
+ "{fetch: {filter: {a:10, b:10}, node: {andSorted: {nodes: [{ixscan: {filter: null, "
+ "pattern: {a:1}}},{ixscan: {filter: null, pattern: {b:1}}}]}}}}");
+
+ // Run a range query on both fields and confirm that an AND_HASH plan is generated.
+ runQuery(fromjson("{a:{$gt: 10}, b:{$gt: 10}}"));
+ // Three plans are generated: one IXSCAN for each index, and an AND_HASH on both.
+ ASSERT_EQUALS(getNumSolutions(), 3U);
+ assertSolutionExists(
+ "{fetch: {filter: {a:{$gt: 10}}, node: {ixscan: {filter: null, pattern: {b:1}}}}}");
+ assertSolutionExists(
+ "{fetch: {filter: {b:{$gt: 10}}, node: {ixscan: {filter: null, pattern: {a:1}}}}}");
+ assertSolutionExists(
+ "{fetch: {filter: {a:{$gt: 10}, b:{$gt: 10}}, node: {andHash: {nodes: [{ixscan: "
+ "{filter: null, pattern: {a:1}}},{ixscan: {filter: null, pattern: {b:1}}}]}}}}");
+
+ // Now add a $** index and re-run the tests.
+ addAllPathsIndex(BSON("$**" << 1));
+
+ // First re-run the AND_SORTED test.
+ runQuery(fromjson("{a:10, b:10}"));
+ // Solution count has increased from 3 to 5, as $** 'duplicates' the {a:1} and {b:1} IXSCANS.
+ ASSERT_EQUALS(getNumSolutions(), 5U);
+ assertSolutionExists(
+ "{fetch: {filter: {a:10}, node: {ixscan: {filter: null, pattern: {b:1}}}}}");
+ assertSolutionExists(
+ "{fetch: {filter: {b:10}, node: {ixscan: {filter: null, pattern: {a:1}}}}}");
+ // The previous AND_SORTED solution is still present...
+ assertSolutionExists(
+ "{fetch: {filter: {a:10, b:10}, node: {andSorted: {nodes: [{ixscan: {filter: null, "
+ "pattern: {a:1}}},{ixscan: {filter: null, pattern: {b:1}}}]}}}}");
+ // ... but there are no additional AND_SORTED solutions contributed by the $** index.
+ assertSolutionExists(
+ "{fetch: {filter: {a:10}, node: {ixscan: {filter: null, pattern: {$_path:1, b:1}}}}}");
+ assertSolutionExists(
+ "{fetch: {filter: {b:10}, node: {ixscan: {filter: null, pattern: {$_path:1, a:1}}}}}");
+
+ // Now re-run the AND_HASH test.
+ runQuery(fromjson("{a:{$gt: 10}, b:{$gt: 10}}"));
+ // Solution count has increased from 3 to 5, as $** 'duplicates' the {a:1} and {b:1} IXSCANS.
+ ASSERT_EQUALS(getNumSolutions(), 5U);
+ assertSolutionExists(
+ "{fetch: {filter: {a:{$gt:10}}, node: {ixscan: {filter: null, pattern: {b:1}}}}}");
+ assertSolutionExists(
+ "{fetch: {filter: {b:{$gt:10}}, node: {ixscan: {filter: null, pattern: {a:1}}}}}");
+ // The previous AND_HASH solution is still present...
+ assertSolutionExists(
+ "{fetch: {filter: {a:{$gt:10}, b:{$gt:10}}, node: {andHash: {nodes: [{ixscan: "
+ "{filter: null, pattern: {a:1}}},{ixscan: {filter: null, pattern: {b:1}}}]}}}}");
+ // ... but there are no additional AND_HASH solutions contributed by the $** index.
+ assertSolutionExists(
+ "{fetch: {filter:{a:{$gt:10}}, node: {ixscan: {filter: null, pattern: {$_path:1, b:1}}}}}");
+ assertSolutionExists(
+ "{fetch: {filter:{b:{$gt:10}}, node: {ixscan: {filter: null, pattern: {$_path:1, a:1}}}}}");
+}
+
} // namespace mongo