diff options
author | Bernard Gorman <bernard.gorman@gmail.com> | 2018-09-07 02:43:48 +0100 |
---|---|---|
committer | Bernard Gorman <bernard.gorman@gmail.com> | 2018-09-11 12:08:37 +0100 |
commit | a08202469ed08f507f2d6cbb8e45158e4cf10839 (patch) | |
tree | 2bb087230044da0498564dc9678741461ab96d42 /src/mongo | |
parent | fe8f517a59d694b7577da564d19e4415e13831e8 (diff) | |
download | mongo-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.cpp | 10 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner_all_paths_index_test.cpp | 77 |
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 |