summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
authorKyle Suarez <kyle.suarez@mongodb.com>2017-06-07 09:42:34 -0400
committerKyle Suarez <kyle.suarez@mongodb.com>2017-06-07 09:42:43 -0400
commit1345c0476cf47d691e8db532967238800d0a70c2 (patch)
tree6d9dfd97e5d1cad8605f3572a897da7bb87a0c16 /src/mongo
parent386a788bd9f565c1a4bba46b5b570f7e53e85c7b (diff)
downloadmongo-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.cpp5
-rw-r--r--src/mongo/db/query/query_planner.cpp34
-rw-r--r--src/mongo/db/query/query_planner_test.cpp121
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() ? &params.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