diff options
author | Kyle Suarez <kyle.suarez@mongodb.com> | 2017-06-07 09:42:34 -0400 |
---|---|---|
committer | Kyle Suarez <kyle.suarez@mongodb.com> | 2017-06-07 09:42:43 -0400 |
commit | 1345c0476cf47d691e8db532967238800d0a70c2 (patch) | |
tree | 6d9dfd97e5d1cad8605f3572a897da7bb87a0c16 /src/mongo | |
parent | 386a788bd9f565c1a4bba46b5b570f7e53e85c7b (diff) | |
download | mongo-1345c0476cf47d691e8db532967238800d0a70c2.tar.gz |
SERVER-20066 consider ixscans if query is empty but projection can be covered
Diffstat (limited to 'src/mongo')
-rw-r--r-- | src/mongo/db/query/get_executor.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner.cpp | 34 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner_test.cpp | 121 |
3 files changed, 160 insertions, 0 deletions
diff --git a/src/mongo/db/query/get_executor.cpp b/src/mongo/db/query/get_executor.cpp index e99c855c768..c631ff9d8b3 100644 --- a/src/mongo/db/query/get_executor.cpp +++ b/src/mongo/db/query/get_executor.cpp @@ -1311,6 +1311,11 @@ StatusWith<unique_ptr<PlanExecutor, PlanExecutor::Deleter>> getExecutorCount( bool turnIxscanIntoDistinctIxscan(QuerySolution* soln, const string& field) { QuerySolutionNode* root = soln->root.get(); + // Solution must have a filter. + if (soln->filterData.isEmpty()) { + return false; + } + // Root stage must be a project. if (STAGE_PROJECTION != root->getType()) { return false; diff --git a/src/mongo/db/query/query_planner.cpp b/src/mongo/db/query/query_planner.cpp index 0477dba4980..07ba13c47e4 100644 --- a/src/mongo/db/query/query_planner.cpp +++ b/src/mongo/db/query/query_planner.cpp @@ -1002,6 +1002,40 @@ Status QueryPlanner::plan(const CanonicalQuery& query, } } + // If a projection exists, there may be an index that allows for a covered plan, even if none + // were considered earlier. + const auto projection = query.getProj(); + if (out->size() == 0 && query.getQueryObj().isEmpty() && projection && + !projection->requiresDocument()) { + + const auto* indicesToConsider = hintIndex.isEmpty() ? ¶ms.indices : &relevantIndices; + for (auto&& index : *indicesToConsider) { + if (index.type != INDEX_BTREE || index.multikey || index.sparse || index.filterExpr || + !CollatorInterface::collatorsMatch(index.collator, query.getCollator())) { + continue; + } + + QueryPlannerParams paramsForCoveredIxScan; + paramsForCoveredIxScan.options = + params.options | QueryPlannerParams::NO_UNCOVERED_PROJECTIONS; + auto soln = buildWholeIXSoln(index, query, paramsForCoveredIxScan); + if (soln) { + LOG(5) << "Planner: outputting soln that uses index to provide projection."; + PlanCacheIndexTree* indexTree = new PlanCacheIndexTree(); + indexTree->setIndexEntry(index); + + SolutionCacheData* scd = new SolutionCacheData(); + scd->tree.reset(indexTree); + scd->solnType = SolutionCacheData::WHOLE_IXSCAN_SOLN; + scd->wholeIXSolnDir = 1; + soln->cacheData.reset(scd); + + out->push_back(soln); + break; + } + } + } + // geoNear and text queries *require* an index. // Also, if a hint is specified it indicates that we MUST use it. bool possibleToCollscan = diff --git a/src/mongo/db/query/query_planner_test.cpp b/src/mongo/db/query/query_planner_test.cpp index 2f1102e9e21..39f1a2c5cb2 100644 --- a/src/mongo/db/query/query_planner_test.cpp +++ b/src/mongo/db/query/query_planner_test.cpp @@ -5269,4 +5269,125 @@ TEST_F(QueryPlannerTest, ContainedOrPathLevelMultikeyCannotCompoundTrailingField assertSolutionExists("{cscan: {dir: 1}}}}"); } +TEST_F(QueryPlannerTest, EmptyQueryWithoutProjectionUsesCollscan) { + addIndex(BSON("a" << 1)); + runQuery(BSONObj()); + assertNumSolutions(1); + assertSolutionExists("{cscan: {dir: 1}}}"); +} + +TEST_F(QueryPlannerTest, EmptyQueryWithProjectionUsesCoveredIxscan) { + params.options &= ~QueryPlannerParams::INCLUDE_COLLSCAN; + addIndex(BSON("a" << 1)); + runQueryAsCommand(fromjson("{find: 'testns', projection: {_id: 0, a: 1}}")); + assertNumSolutions(1); + assertSolutionExists( + "{proj: {spec: {_id: 0, a: 1}, node: " + "{ixscan: {filter: null, pattern: {a: 1}," + "bounds: {a: [['MinKey', 'MaxKey', true, true]]}}}}}"); +} + +TEST_F(QueryPlannerTest, EmptyQueryWithProjectionUsesCoveredIxscanOnCompoundIndex) { + params.options &= ~QueryPlannerParams::INCLUDE_COLLSCAN; + addIndex(BSON("a" << 1 << "b" << 1 << "c" << 1)); + runQueryAsCommand(fromjson("{find: 'testns', projection: {_id: 0, a: 1, c: 1}}")); + assertNumSolutions(1); + assertSolutionExists( + "{proj: {spec: {_id: 0, a: 1, c: 1}, node: " + "{ixscan: {filter: null, pattern: {a: 1, b: 1, c: 1}, bounds:" + "{a: [['MinKey', 'MaxKey', true, true]], b: [['MinKey', 'MaxKey', true, true]]," + "c: [['MinKey', 'MaxKey', true, true]]}}}}}"); +} + +TEST_F(QueryPlannerTest, PlannerDoesNotConsiderCoveredIxscanForProjIfItWouldNotRespectTheHint) { + addIndex(BSON("a" << 1)); + runQueryAsCommand(fromjson("{find: 'testns', projection: {_id: 0, a: 1}, hint: {_id: 1}}")); + assertNumSolutions(1); + assertSolutionExists( + "{proj: {spec: {_id: 0, a: 1}, node: " + "{fetch: {filter: null, node: " + "{ixscan: {filter: null, pattern: {_id: 1}, " + "bounds: {_id: [['MinKey', 'MaxKey', true, true]]}}}}}}}"); +} + +TEST_F(QueryPlannerTest, EmptyQueryWithProjectionUsesCollscanIfNoCoveredIxscans) { + addIndex(BSON("a" << 1)); + runQueryAsCommand(fromjson("{find: 'testns', projection: {a: 1}}")); + assertNumSolutions(1); + assertSolutionExists( + "{proj: {spec: {a: 1}, node:" + "{cscan: {dir: 1}}}}"); +} + +TEST_F(QueryPlannerTest, EmptyQueryWithProjectionUsesCoveredIxscanOnDotttedNonMultikeyIndex) { + params.options &= ~QueryPlannerParams::INCLUDE_COLLSCAN; + addIndex(BSON("a.b" << 1)); + runQueryAsCommand(fromjson("{find: 'testns', projection: {_id: 0, 'a.b': 1}}")); + assertNumSolutions(1); + assertSolutionExists( + "{proj: {spec: {_id: 0, 'a.b': 1}, node: " + "{ixscan: {filter: null, pattern: {'a.b': 1}," + "bounds: {'a.b': [['MinKey', 'MaxKey', true, true]]}}}}}"); +} + +TEST_F(QueryPlannerTest, EmptyQueryWithProjectionUsesCollscanIfIndexIsMultikey) { + constexpr bool isMultikey = true; + addIndex(BSON("a" << 1 << "b" << 1), isMultikey); + runQueryAsCommand(fromjson("{find: 'testns', projection: {_id: 0, a: 1, b: 1}}")); + assertNumSolutions(1); + assertSolutionExists( + "{proj: {spec: {_id: 0, a: 1, b: 1}, node: " + "{cscan: {dir: 1}}}}"); +} + +TEST_F(QueryPlannerTest, EmptyQueryWithProjectionUsesCollscanIfIndexIsSparse) { + constexpr bool isMultikey = false; + constexpr bool isSparse = true; + addIndex(BSON("a" << 1), isMultikey, isSparse); + runQueryAsCommand(fromjson("{find: 'testns', projection: {_id: 0, a: 1, b: 1}}")); + assertNumSolutions(1); + assertSolutionExists( + "{proj: {spec: {_id: 0, a: 1, b: 1}, node: " + "{cscan: {dir: 1}}}}"); +} + +TEST_F(QueryPlannerTest, EmptyQueryWithProjectionUsesCollscanIfIndexIsPartial) { + FalseMatchExpression matchExpr("foo"_sd); + addIndex(BSON("a" << 1), &matchExpr); + runQueryAsCommand(fromjson("{find: 'testns', projection: {_id: 0, a: 1}}")); + assertNumSolutions(1); + assertSolutionExists( + "{proj: {spec: {_id: 0, a: 1}, node: " + "{cscan: {dir: 1}}}}"); +} + +TEST_F(QueryPlannerTest, EmptyQueryWithProjectionUsesCollscanIfIndexIsText) { + addIndex(BSON("a" + << "text")); + runQueryAsCommand(fromjson("{find: 'testns', projection: {_id: 0, a: 1}}")); + assertNumSolutions(1); + assertSolutionExists( + "{proj: {spec: {_id: 0, a: 1}, node: " + "{cscan: {dir: 1}}}}"); +} + +TEST_F(QueryPlannerTest, EmptyQueryWithProjectionUsesCollscanIfIndexIsGeo) { + addIndex(BSON("a" + << "2dsphere")); + runQueryAsCommand(fromjson("{find: 'testns', projection: {_id: 0, a: 1}}")); + assertNumSolutions(1); + assertSolutionExists( + "{proj: {spec: {_id: 0, a: 1}, node: " + "{cscan: {dir: 1}}}}"); +} + +TEST_F(QueryPlannerTest, EmptyQueryWithProjectionUsesCollscanIfIndexCollationDiffers) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + addIndex(BSON("a" << 1), &collator); + runQueryAsCommand(fromjson("{find: 'testns', projection: {_id: 0, a: 1}}")); + assertNumSolutions(1); + assertSolutionExists( + "{proj: {spec: {_id: 0, a: 1}, node: " + "{cscan: {dir: 1}}}}"); +} } // namespace |