summaryrefslogtreecommitdiff
path: root/src/mongo/db/query
diff options
context:
space:
mode:
authorDavid Percy <david.percy@mongodb.com>2019-11-11 18:50:37 +0000
committerevergreen <evergreen@mongodb.com>2019-11-11 18:50:37 +0000
commit2411853a626e2c28e8bc54c82490cbb2ab9947a0 (patch)
tree1fde60264e4ccfecaded6cbfa8aa15cd26e3951e /src/mongo/db/query
parent2b64ed2d9049a10cf105f3fb1258062678bfd682 (diff)
downloadmongo-2411853a626e2c28e8bc54c82490cbb2ab9947a0.tar.gz
SERVER-14643 Return informative Status from QueryPlanner::plan
Diffstat (limited to 'src/mongo/db/query')
-rw-r--r--src/mongo/db/query/get_executor.cpp10
-rw-r--r--src/mongo/db/query/query_planner.cpp104
-rw-r--r--src/mongo/db/query/query_planner_array_test.cpp5
-rw-r--r--src/mongo/db/query/query_planner_geo_test.cpp30
-rw-r--r--src/mongo/db/query/query_planner_index_test.cpp9
-rw-r--r--src/mongo/db/query/query_planner_partialidx_test.cpp120
-rw-r--r--src/mongo/db/query/query_planner_test_fixture.cpp11
-rw-r--r--src/mongo/db/query/query_planner_test_fixture.h6
-rw-r--r--src/mongo/db/query/query_planner_text_test.cpp9
-rw-r--r--src/mongo/db/query/query_planner_wildcard_index_test.cpp12
10 files changed, 189 insertions, 127 deletions
diff --git a/src/mongo/db/query/get_executor.cpp b/src/mongo/db/query/get_executor.cpp
index b236de9e8ea..070218d9efd 100644
--- a/src/mongo/db/query/get_executor.cpp
+++ b/src/mongo/db/query/get_executor.cpp
@@ -508,14 +508,8 @@ StatusWith<PrepareExecutionResult> prepareExecution(OperationContext* opCtx,
<< " planner returned error");
}
auto solutions = std::move(statusWithSolutions.getValue());
-
- // We cannot figure out how to answer the query. Perhaps it requires an index
- // we do not have?
- if (0 == solutions.size()) {
- return Status(ErrorCodes::NoQueryExecutionPlans,
- str::stream() << "error processing query: " << canonicalQuery->toString()
- << " No query solutions");
- }
+ // The planner should have returned an error status if there are no solutions.
+ invariant(solutions.size() > 0);
// See if one of our solutions is a fast count hack in disguise.
if (plannerParams.options & QueryPlannerParams::IS_COUNT) {
diff --git a/src/mongo/db/query/query_planner.cpp b/src/mongo/db/query/query_planner.cpp
index 682e27de35b..de86cf3fc32 100644
--- a/src/mongo/db/query/query_planner.cpp
+++ b/src/mongo/db/query/query_planner.cpp
@@ -532,8 +532,6 @@ StatusWith<std::vector<std::unique_ptr<QuerySolution>>> QueryPlanner::plan(
<< "Canonical query:" << endl
<< redact(query.toString()) << "=============================";
- std::vector<std::unique_ptr<QuerySolution>> out;
-
for (size_t i = 0; i < params.indices.size(); ++i) {
LOG(5) << "Index " << i << " is " << params.indices[i].toString();
}
@@ -544,12 +542,23 @@ StatusWith<std::vector<std::unique_ptr<QuerySolution>>> QueryPlanner::plan(
// If the query requests a tailable cursor, the only solution is a collscan + filter with
// tailable set on the collscan.
if (isTailable) {
- if (!QueryPlannerCommon::hasNode(query.root(), MatchExpression::GEO_NEAR) && canTableScan) {
- auto soln = buildCollscanSoln(query, isTailable, params);
- if (soln) {
- out.push_back(std::move(soln));
- }
+ if (!canTableScan) {
+ return Status(
+ ErrorCodes::NoQueryExecutionPlans,
+ "Running with 'notablescan', so tailable cursors (which always do a table "
+ "scan) are not allowed");
}
+ if (QueryPlannerCommon::hasNode(query.root(), MatchExpression::GEO_NEAR)) {
+ return Status(ErrorCodes::NoQueryExecutionPlans,
+ "Tailable cursors and geo $near cannot be used together");
+ }
+ auto soln = buildCollscanSoln(query, isTailable, params);
+ if (!soln) {
+ return Status(ErrorCodes::NoQueryExecutionPlans,
+ "Failed to build collection scan soln");
+ }
+ std::vector<std::unique_ptr<QuerySolution>> out;
+ out.push_back(std::move(soln));
return {std::move(out)};
}
@@ -567,14 +576,22 @@ StatusWith<std::vector<std::unique_ptr<QuerySolution>>> QueryPlanner::plan(
// scan if there is a $natural sort with a non-$natural hint.
if (!naturalHint.eoo() || (!naturalSort.eoo() && hintObj.isEmpty())) {
LOG(5) << "Forcing a table scan due to hinted $natural";
- // min/max are incompatible with $natural.
- if (canTableScan && query.getQueryRequest().getMin().isEmpty() &&
- query.getQueryRequest().getMax().isEmpty()) {
- auto soln = buildCollscanSoln(query, isTailable, params);
- if (soln) {
- out.push_back(std::move(soln));
- }
+ if (!canTableScan) {
+ return Status(ErrorCodes::NoQueryExecutionPlans,
+ "hint $natural is not allowed, because 'notablescan' is enabled");
+ }
+ if (!query.getQueryRequest().getMin().isEmpty() ||
+ !query.getQueryRequest().getMax().isEmpty()) {
+ return Status(ErrorCodes::NoQueryExecutionPlans,
+ "min and max are incompatible with $natural");
+ }
+ auto soln = buildCollscanSoln(query, isTailable, params);
+ if (!soln) {
+ return Status(ErrorCodes::NoQueryExecutionPlans,
+ "Failed to build collection scan soln");
}
+ std::vector<std::unique_ptr<QuerySolution>> out;
+ out.push_back(std::move(soln));
return {std::move(out)};
}
}
@@ -682,10 +699,12 @@ StatusWith<std::vector<std::unique_ptr<QuerySolution>>> QueryPlanner::plan(
invariant(solnRoot);
auto soln = QueryPlannerAnalysis::analyzeDataAccess(query, params, std::move(solnRoot));
- if (soln) {
- out.push_back(std::move(soln));
+ if (!soln) {
+ return Status(ErrorCodes::NoQueryExecutionPlans,
+ "Sort and covering analysis failed while planning hint/min/max query");
}
-
+ std::vector<std::unique_ptr<QuerySolution>> out;
+ out.push_back(std::move(soln));
return {std::move(out)};
}
@@ -767,6 +786,8 @@ StatusWith<std::vector<std::unique_ptr<QuerySolution>>> QueryPlanner::plan(
LOG(5) << "Rated tree after text processing:" << redact(query.root()->debugString());
}
+ std::vector<std::unique_ptr<QuerySolution>> out;
+
// If we have any relevant indices, we try to create indexed plans.
if (0 < relevantIndices.size()) {
// The enumerator spits out trees tagged with IndexTag(s).
@@ -845,14 +866,23 @@ StatusWith<std::vector<std::unique_ptr<QuerySolution>>> QueryPlanner::plan(
// desired behavior when an index is hinted that is not relevant to the query. In the case that
// $** index is hinted, we do not want this behavior.
if (!hintedIndex.isEmpty() && relevantIndices.size() == 1) {
- if (0 == out.size() && relevantIndices.front().type != IndexType::INDEX_WILDCARD) {
- // Push hinted index solution to output list if found.
- auto soln = buildWholeIXSoln(relevantIndices.front(), query, params);
- if (soln) {
- LOG(5) << "Planner: outputting soln that uses hinted index as scan.";
- out.push_back(std::move(soln));
- }
+ if (out.size() > 0) {
+ return {std::move(out)};
+ }
+ if (relevantIndices.front().type == IndexType::INDEX_WILDCARD) {
+ return Status(
+ ErrorCodes::NoQueryExecutionPlans,
+ "$hint: refusing to build whole-index solution, because it's a wildcard index");
}
+ // Return hinted index solution if found.
+ auto soln = buildWholeIXSoln(relevantIndices.front(), query, params);
+ if (!soln) {
+ return Status(ErrorCodes::NoQueryExecutionPlans,
+ "Failed to build whole-index solution for $hint");
+ }
+ LOG(5) << "Planner: outputting soln that uses hinted index as scan.";
+ std::vector<std::unique_ptr<QuerySolution>> out;
+ out.push_back(std::move(soln));
return {std::move(out)};
}
@@ -979,20 +1009,31 @@ StatusWith<std::vector<std::unique_ptr<QuerySolution>>> QueryPlanner::plan(
}
}
+ // The caller can explicitly ask for a collscan.
+ bool collscanRequested = (params.options & QueryPlannerParams::INCLUDE_COLLSCAN);
+
+ // No indexed plans? We must provide a collscan if possible or else we can't run the query.
+ bool collScanRequired = 0 == out.size();
+ if (collScanRequired && !canTableScan) {
+ return Status(ErrorCodes::NoQueryExecutionPlans,
+ "No indexed plans available, and running with 'notablescan'");
+ }
+
// geoNear and text queries *require* an index.
// Also, if a hint is specified it indicates that we MUST use it.
bool possibleToCollscan =
!QueryPlannerCommon::hasNode(query.root(), MatchExpression::GEO_NEAR) &&
!QueryPlannerCommon::hasNode(query.root(), MatchExpression::TEXT) && hintedIndex.isEmpty();
+ if (collScanRequired && !possibleToCollscan) {
+ return Status(ErrorCodes::NoQueryExecutionPlans, "No query solutions");
+ }
- // The caller can explicitly ask for a collscan.
- bool collscanRequested = (params.options & QueryPlannerParams::INCLUDE_COLLSCAN);
-
- // No indexed plans? We must provide a collscan if possible or else we can't run the query.
- bool collscanNeeded = (0 == out.size() && canTableScan);
-
- if (possibleToCollscan && (collscanRequested || collscanNeeded)) {
+ if (possibleToCollscan && (collscanRequested || collScanRequired)) {
auto collscan = buildCollscanSoln(query, isTailable, params);
+ if (!collscan && collScanRequired) {
+ return Status(ErrorCodes::NoQueryExecutionPlans,
+ "Failed to build collection scan soln");
+ }
if (collscan) {
LOG(5) << "Planner: outputting a collscan:" << endl << redact(collscan->toString());
SolutionCacheData* scd = new SolutionCacheData();
@@ -1002,6 +1043,7 @@ StatusWith<std::vector<std::unique_ptr<QuerySolution>>> QueryPlanner::plan(
}
}
+ invariant(out.size() > 0);
return {std::move(out)};
}
diff --git a/src/mongo/db/query/query_planner_array_test.cpp b/src/mongo/db/query/query_planner_array_test.cpp
index 6d0d4b0a11f..857ef98613b 100644
--- a/src/mongo/db/query/query_planner_array_test.cpp
+++ b/src/mongo/db/query/query_planner_array_test.cpp
@@ -1002,10 +1002,9 @@ TEST_F(QueryPlannerTest, CantIndexNegationBelowElemMatchValue) {
// true means multikey
addIndex(BSON("a" << 1), true);
- runQuery(fromjson("{a: {$elemMatch: {$not: {$mod: [2, 0]}}}}"));
-
// There are no indexed solutions, because negations of $mod are not indexable.
- assertNumSolutions(0);
+ runInvalidQuery(fromjson("{a: {$elemMatch: {$not: {$mod: [2, 0]}}}}"));
+ assertNoSolutions();
}
/**
diff --git a/src/mongo/db/query/query_planner_geo_test.cpp b/src/mongo/db/query/query_planner_geo_test.cpp
index b23c40a64fe..914cd613da5 100644
--- a/src/mongo/db/query/query_planner_geo_test.cpp
+++ b/src/mongo/db/query/query_planner_geo_test.cpp
@@ -947,9 +947,9 @@ TEST_F(QueryPlannerGeo2dsphereTest,
addIndex(BSON("a" << 1 << "b" << 1 << "geo"
<< "2dsphere"),
multikeyPaths);
- runQuery(fromjson("{a: {$ne: 3}, b: 2, geo: {$nearSphere: [0, 0]}}"));
+ runInvalidQuery(fromjson("{a: {$ne: 3}, b: 2, geo: {$nearSphere: [0, 0]}}"));
- assertNumSolutions(0U);
+ assertNoSolutions();
}
TEST_F(QueryPlannerGeo2dsphereTest,
@@ -1303,6 +1303,23 @@ TEST_F(QueryPlannerGeo2dsphereTest, CannotIntersectBoundsOn2dsphereFieldWhenItIs
// A fixture to help run tests for multiple 2dsphere index versions.
class QueryPlanner2dsphereVersionTest : public QueryPlannerTest {
public:
+ // For each 2dsphere index version in 'versions', verifies the planner returns
+ // 'errorCode' for 'predicate' given 'keyPatterns'.
+ void assertExpectedFailureFor2dsphereIndexVersions(std::vector<int> versions,
+ std::vector<BSONObj> keyPatterns,
+ BSONObj predicate,
+ ErrorCodes::Error errorCode) {
+ for (auto version : versions) {
+ params.indices.clear();
+ for (auto keyPattern : keyPatterns) {
+ addIndex(keyPattern, BSON("2dsphereIndexVersion" << version));
+ }
+
+ runInvalidQuery(predicate);
+ ASSERT_EQUALS(plannerStatus.code(), errorCode);
+ }
+ }
+
// For each 2dsphere index version in 'versions', verifies the planner generates
// 'expectedSolutions' for 'predicate' given 'keyPatterns'.
void testMultiple2dsphereIndexVersions(std::vector<int> versions,
@@ -1564,9 +1581,8 @@ TEST_F(QueryPlanner2dsphereVersionTest, NegationWithoutGeoPredCannotUseGeoIndex)
BSONObj predicate = fromjson("{a: {$ne: 3}}");
// Only a COLLSCAN is possible, but COLLSCANs are prohibited above.
- std::vector<std::string> solutions = {};
-
- testMultiple2dsphereIndexVersions(versions, keyPatterns, predicate, solutions);
+ ErrorCodes::Error err = ErrorCodes::NoQueryExecutionPlans;
+ assertExpectedFailureFor2dsphereIndexVersions(versions, keyPatterns, predicate, err);
}
TEST_F(QueryPlannerTest, 2dInexactFetchPredicateOverTrailingFieldHandledCorrectly) {
@@ -1703,9 +1719,9 @@ TEST_F(QueryPlannerTest, 2dsphereNonNearWithInternalExprEqOverLeadingFieldMultik
<< "2dsphere"),
multikey);
- runQuery(
+ runInvalidQuery(
fromjson("{a: {$_internalExprEq: 0}, b: {$geoWithin: {$centerSphere: [[0, 0], 10]}}}"));
- assertNumSolutions(0U);
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, 2dsphereNonNearWithInternalExprEqOverTrailingField) {
diff --git a/src/mongo/db/query/query_planner_index_test.cpp b/src/mongo/db/query/query_planner_index_test.cpp
index b819a3311f6..1ec1f6e19ca 100644
--- a/src/mongo/db/query/query_planner_index_test.cpp
+++ b/src/mongo/db/query/query_planner_index_test.cpp
@@ -587,13 +587,12 @@ TEST_F(QueryPlannerTest, CompoundMultikeyBoundsNoIntersect) {
TEST_F(QueryPlannerTest, NoTableScanBasic) {
params.options = QueryPlannerParams::NO_TABLE_SCAN;
- runQuery(BSONObj());
- assertNumSolutions(0U);
+ runInvalidQuery(BSONObj());
+ assertNoSolutions();
addIndex(BSON("x" << 1));
-
- runQuery(BSONObj());
- assertNumSolutions(0U);
+ runInvalidQuery(BSONObj());
+ assertNoSolutions();
runQuery(fromjson("{x: {$gte: 0}}"));
assertNumSolutions(1U);
diff --git a/src/mongo/db/query/query_planner_partialidx_test.cpp b/src/mongo/db/query/query_planner_partialidx_test.cpp
index 9cf9b66d00b..124b75c7518 100644
--- a/src/mongo/db/query/query_planner_partialidx_test.cpp
+++ b/src/mongo/db/query/query_planner_partialidx_test.cpp
@@ -49,8 +49,8 @@ TEST_F(QueryPlannerTest, PartialIndexEq) {
"{filter: null, pattern: {a: 1}, "
"bounds: {a: [[1, 1, true, true]]}}}}}");
- runQuery(fromjson("{a: -1}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{a: -1}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexNot) {
@@ -59,14 +59,14 @@ TEST_F(QueryPlannerTest, PartialIndexNot) {
std::unique_ptr<MatchExpression> filterExpr = parseMatchExpression(filterObj);
addIndex(fromjson("{a: 1}"), filterExpr.get());
- runQuery(fromjson("{a: {$ne: 1}}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{a: {$ne: 1}}"));
+ assertNoSolutions();
- runQuery(fromjson("{a: {$ne: -1}}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{a: {$ne: -1}}"));
+ assertNoSolutions();
- runQuery(fromjson("{a: {$not: {$lte: 0}}}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{a: {$not: {$lte: 0}}}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexElemMatchObj) {
@@ -76,11 +76,11 @@ TEST_F(QueryPlannerTest, PartialIndexElemMatchObj) {
addIndex(fromjson("{'a.b': 1}"), filterExpr.get());
// The following query could in theory use the partial index, but we don't support it right now.
- runQuery(fromjson("{a: {$elemMatch: {b: 1}}}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{a: {$elemMatch: {b: 1}}}"));
+ assertNoSolutions();
- runQuery(fromjson("{a: {$elemMatch: {b: -1}}}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{a: {$elemMatch: {b: -1}}}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexElemMatchObjContainingOr) {
@@ -90,8 +90,8 @@ TEST_F(QueryPlannerTest, PartialIndexElemMatchObjContainingOr) {
addIndex(fromjson("{'a.b': 1}"), filterExpr.get());
// The following query could in theory use the partial index, but we don't support it right now.
- runQuery(fromjson("{a: {$elemMatch: {$or: [{b: 1}, {b: 2}]}}}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{a: {$elemMatch: {$or: [{b: 1}, {b: 2}]}}}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexElemMatchObjWithBadSubobjectFilter) {
@@ -100,11 +100,11 @@ TEST_F(QueryPlannerTest, PartialIndexElemMatchObjWithBadSubobjectFilter) {
std::unique_ptr<MatchExpression> filterExpr = parseMatchExpression(filterObj);
addIndex(fromjson("{'a.b': 1}"), filterExpr.get());
- runQuery(fromjson("{a: {$elemMatch: {$or: [{b: 1}, {b: 2}]}}}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{a: {$elemMatch: {$or: [{b: 1}, {b: 2}]}}}"));
+ assertNoSolutions();
- runQuery(fromjson("{a: {$elemMatch: {b: {$in: [1, 2]}}}}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{a: {$elemMatch: {b: {$in: [1, 2]}}}}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexAndSingleAssignment) {
@@ -120,8 +120,8 @@ TEST_F(QueryPlannerTest, PartialIndexAndSingleAssignment) {
"{filter: null, pattern: {a: 1}, "
"bounds: {a: [[1, 1, true, true]]}}}}}");
- runQuery(fromjson("{a: 1, f: -1}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{a: 1, f: -1}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexAndMultipleAssignments) {
@@ -137,8 +137,8 @@ TEST_F(QueryPlannerTest, PartialIndexAndMultipleAssignments) {
"{filter: null, pattern: {a: 1}, "
"bounds: {a: [[0, 10, true, true]]}}}}}");
- runQuery(fromjson("{a: {$gte: 0, $lte: 10}, f: -1}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{a: {$gte: 0, $lte: 10}, f: -1}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexOr) {
@@ -156,8 +156,8 @@ TEST_F(QueryPlannerTest, PartialIndexOr) {
"{ixscan: {filter: null, pattern: {_id: 1}, "
"bounds: {_id: [[0, 0, true, true]]}}}]}}}}");
- runQuery(fromjson("{$or: [{a: -1}, {_id: 0}]}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{$or: [{a: -1}, {_id: 0}]}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexOrContainingAnd) {
@@ -176,8 +176,8 @@ TEST_F(QueryPlannerTest, PartialIndexOrContainingAnd) {
"{ixscan: {filter: null, pattern: {_id: 1}, "
"bounds: {_id: [[0, 0, true, true]]}}}]}}}}");
- runQuery(fromjson("{$or: [{a: 1, f: -1}, {_id: 0}]}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{$or: [{a: 1, f: -1}, {_id: 0}]}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexOrContainingAndMultipleAssignments) {
@@ -213,8 +213,8 @@ TEST_F(QueryPlannerTest, PartialIndexOrContainingAndMultipleAssignments) {
"{ixscan: {filter: null, pattern: {a: 1}, "
"bounds: {a: [[2, 2, true, true]]}}}]}}}}");
- runQuery(fromjson("{$or: [{_id: 0, a: -1}, {a: -2}]}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{$or: [{_id: 0, a: -1}, {a: -2}]}"));
+ assertNoSolutions();
}
@@ -234,8 +234,8 @@ TEST_F(QueryPlannerTest, PartialIndexAndContainingOrContainingAnd) {
"{ixscan: {filter: null, pattern: {_id: 1}, "
"bounds: {_id: [[0, 0, true, true]]}}}]}}}}");
- runQuery(fromjson("{x: 1, $or: [{a: 1, f: -1}, {_id: 0}]}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{x: 1, $or: [{a: 1, f: -1}, {_id: 0}]}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexAndContainingOrContainingAndSatisfyingPredOutside) {
@@ -255,8 +255,8 @@ TEST_F(QueryPlannerTest, PartialIndexAndContainingOrContainingAndSatisfyingPredO
"bounds: {_id: [[0, 0, true, true]]}}}]}}}}");
// The following query could in theory use the partial index, but we don't support it right now.
- runQuery(fromjson("{f: 1, x: 1, $or: [{a: 1, g: 1}, {_id: 0}]}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{f: 1, x: 1, $or: [{a: 1, g: 1}, {_id: 0}]}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexOrContainingAndContainingOr) {
@@ -277,8 +277,8 @@ TEST_F(QueryPlannerTest, PartialIndexOrContainingAndContainingOr) {
"{ixscan: {filter: null, pattern: {_id: 1}, "
"bounds: {_id: [[1, 1, true, true]]}}}]}}}}");
- runQuery(fromjson("{$or: [{x: 1, $or: [{a: -1}, {_id: 2}]}, {_id: 1}]}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{$or: [{x: 1, $or: [{a: -1}, {_id: 2}]}, {_id: 1}]}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexProvidingSort) {
@@ -294,8 +294,8 @@ TEST_F(QueryPlannerTest, PartialIndexProvidingSort) {
"{filter: null, pattern: {a: 1}, "
"bounds: {a: [['MinKey', 'MaxKey', true, true]]}}}}}");
- runQuerySortProj(fromjson("{f: -1}"), fromjson("{a: 1}"), BSONObj());
- assertNumSolutions(0U);
+ runInvalidQuerySortProj(fromjson("{f: -1}"), fromjson("{a: 1}"), BSONObj());
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexOrContainingNot) {
@@ -304,11 +304,11 @@ TEST_F(QueryPlannerTest, PartialIndexOrContainingNot) {
std::unique_ptr<MatchExpression> filterExpr = parseMatchExpression(filterObj);
addIndex(fromjson("{a: 1}"), filterExpr.get());
- runQuery(fromjson("{$or: [{a: {$ne: 1}}, {_id: 1}]}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{$or: [{a: {$ne: 1}}, {_id: 1}]}"));
+ assertNoSolutions();
- runQuery(fromjson("{$or: [{a: {$ne: -1}}, {_id: 1}]}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{$or: [{a: {$ne: -1}}, {_id: 1}]}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexMultipleSameAnd) {
@@ -338,8 +338,8 @@ TEST_F(QueryPlannerTest, PartialIndexMultipleSameAnd) {
"{filter: null, pattern: {a: 1}, "
"bounds: {a: [[1, 1, true, true]]}}}}}");
- runQuery(fromjson("{a: 1, b: 1, f: -1, g: -1}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{a: 1, b: 1, f: -1, g: -1}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexMultipleSameAndCompoundSharedPrefix) {
@@ -362,8 +362,8 @@ TEST_F(QueryPlannerTest, PartialIndexMultipleSameAndCompoundSharedPrefix) {
"bounds: {a: [[1, 1, true, true]], "
"c: [['MinKey', 'MaxKey', true, true]]}}}}}");
- runQuery(fromjson("{a: 1, f: -1}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{a: 1, f: -1}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexMultipleSameOr) {
@@ -386,11 +386,11 @@ TEST_F(QueryPlannerTest, PartialIndexMultipleSameOr) {
"{filter: null, pattern: {b: 1}, "
"bounds: {b: [[1, 1, true, true]]}}}}}]}}");
- runQuery(fromjson("{$or: [{a: 1, f: 1}, {b: 1, g: -1}]}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{$or: [{a: 1, f: 1}, {b: 1, g: -1}]}"));
+ assertNoSolutions();
- runQuery(fromjson("{$or: [{a: 1, f: -1}, {b: 1, g: -1}]}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{$or: [{a: 1, f: -1}, {b: 1, g: -1}]}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexAndCompoundIndex) {
@@ -407,8 +407,8 @@ TEST_F(QueryPlannerTest, PartialIndexAndCompoundIndex) {
"bounds: {a: [[1, 1, true, true]], "
"b: [[1, 1, true, true]]}}}}}");
- runQuery(fromjson("{a: 1, b: 1, f: -1}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{a: 1, b: 1, f: -1}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexOrCompoundIndex) {
@@ -427,8 +427,8 @@ TEST_F(QueryPlannerTest, PartialIndexOrCompoundIndex) {
"{ixscan: {filter: null, pattern: {_id: 1}, "
"bounds: {_id: [[1, 1, true, true]]}}}]}}}}");
- runQuery(fromjson("{$or: [{a: 1, b: 1, f: -1}, {_id: 1}]}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{$or: [{a: 1, b: 1, f: -1}, {_id: 1}]}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexNor) {
@@ -438,11 +438,11 @@ TEST_F(QueryPlannerTest, PartialIndexNor) {
addIndex(fromjson("{a: 1}"), filterExpr.get());
// The following query could in theory use the partial index, but we don't support it right now.
- runQuery(fromjson("{$nor: [{a: 1, f: 1}, {_id: 1}]}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{$nor: [{a: 1, f: 1}, {_id: 1}]}"));
+ assertNoSolutions();
- runQuery(fromjson("{$nor: [{a: 1, f: -1}, {_id: 1}]}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{$nor: [{a: 1, f: -1}, {_id: 1}]}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexStringComparisonMatchingCollators) {
@@ -460,9 +460,9 @@ TEST_F(QueryPlannerTest, PartialIndexStringComparisonMatchingCollators) {
"{filter: null, pattern: {a: 1}, "
"bounds: {a: [['cba', 'cba', true, true]]}}}}}");
- runQueryAsCommand(
+ runInvalidQueryAsCommand(
fromjson("{find: 'testns', filter: {a: 'zaa'}, collation: {locale: 'reverse'}}"));
- assertNumSolutions(0U);
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexNoStringComparisonNonMatchingCollators) {
@@ -486,8 +486,8 @@ TEST_F(QueryPlannerTest, InternalExprEqCannotUsePartialIndex) {
auto filterExpr = parseMatchExpression(filterObj);
addIndex(fromjson("{a: 1}"), filterExpr.get());
- runQueryAsCommand(fromjson("{find: 'testns', filter: {a: {$_internalExprEq: 1}}}"));
- assertNumSolutions(0U);
+ runInvalidQueryAsCommand(fromjson("{find: 'testns', filter: {a: {$_internalExprEq: 1}}}"));
+ assertNoSolutions();
}
} // namespace
diff --git a/src/mongo/db/query/query_planner_test_fixture.cpp b/src/mongo/db/query/query_planner_test_fixture.cpp
index ff4aef1309e..db4ae678f87 100644
--- a/src/mongo/db/query/query_planner_test_fixture.cpp
+++ b/src/mongo/db/query/query_planner_test_fixture.cpp
@@ -59,6 +59,7 @@ void QueryPlannerTest::setUp() {
}
void QueryPlannerTest::clearState() {
+ plannerStatus = Status::OK();
solns.clear();
cq.reset();
relaxBoundsCheck = false;
@@ -418,7 +419,8 @@ void QueryPlannerTest::runInvalidQueryFull(const BSONObj& query,
cq = std::move(statusWithCQ.getValue());
auto statusWithSolutions = QueryPlanner::plan(*cq, params);
- ASSERT_NOT_OK(statusWithSolutions.getStatus());
+ plannerStatus = statusWithSolutions.getStatus();
+ ASSERT_NOT_OK(plannerStatus);
}
void QueryPlannerTest::runQueryAsCommand(const BSONObj& cmdObj) {
@@ -465,7 +467,8 @@ void QueryPlannerTest::runInvalidQueryAsCommand(const BSONObj& cmdObj) {
cq = std::move(statusWithCQ.getValue());
auto statusWithSolutions = QueryPlanner::plan(*cq, params);
- ASSERT_NOT_OK(statusWithSolutions.getStatus());
+ plannerStatus = statusWithSolutions.getStatus();
+ ASSERT_NOT_OK(plannerStatus);
}
size_t QueryPlannerTest::getNumSolutions() const {
@@ -537,6 +540,10 @@ void QueryPlannerTest::assertHasOneSolutionOf(const std::vector<std::string>& so
FAIL(ss);
}
+void QueryPlannerTest::assertNoSolutions() const {
+ ASSERT_EQUALS(plannerStatus.code(), ErrorCodes::NoQueryExecutionPlans);
+}
+
void QueryPlannerTest::assertHasOnlyCollscan() const {
assertNumSolutions(1U);
assertSolutionExists("{cscan: {dir: 1}}");
diff --git a/src/mongo/db/query/query_planner_test_fixture.h b/src/mongo/db/query/query_planner_test_fixture.h
index b314099057f..4743f505fa2 100644
--- a/src/mongo/db/query/query_planner_test_fixture.h
+++ b/src/mongo/db/query/query_planner_test_fixture.h
@@ -216,6 +216,11 @@ protected:
void assertHasOnlyCollscan() const;
/**
+ * Check that query planning failed with NoQueryExecutionPlans.
+ */
+ void assertNoSolutions() const;
+
+ /**
* Helper function to parse a MatchExpression.
*/
static std::unique_ptr<MatchExpression> parseMatchExpression(
@@ -232,6 +237,7 @@ protected:
BSONObj queryObj;
std::unique_ptr<CanonicalQuery> cq;
QueryPlannerParams params;
+ Status plannerStatus = Status::OK();
std::vector<std::unique_ptr<QuerySolution>> solns;
bool relaxBoundsCheck = false;
diff --git a/src/mongo/db/query/query_planner_text_test.cpp b/src/mongo/db/query/query_planner_text_test.cpp
index ed4b1e45247..624eb1b9292 100644
--- a/src/mongo/db/query/query_planner_text_test.cpp
+++ b/src/mongo/db/query/query_planner_text_test.cpp
@@ -65,10 +65,9 @@ TEST_F(QueryPlannerTest, CantUseTextUnlessHaveTextPred) {
addIndex(BSON("a" << 1 << "_fts"
<< "text"
<< "_ftsx" << 1));
- runQuery(fromjson("{a:1}"));
-
// No table scans allowed so there is no solution.
- assertNumSolutions(0);
+ runInvalidQuery(fromjson("{a:1}"));
+ assertNoSolutions();
}
// But if you create an index {a:1, b:"text"} you can use it if it has a pred on 'a'
@@ -335,9 +334,9 @@ TEST_F(QueryPlannerTest, TextInsideOrOneBranchNotIndexed) {
addIndex(BSON("_fts"
<< "text"
<< "_ftsx" << 1));
- runQuery(fromjson("{a: 1, $or: [{b: 2}, {$text: {$search: 'foo'}}]}"));
+ runInvalidQuery(fromjson("{a: 1, $or: [{b: 2}, {$text: {$search: 'foo'}}]}"));
- assertNumSolutions(0);
+ assertNoSolutions();
}
// If the unindexable $or is not the one containing the $text predicate,
diff --git a/src/mongo/db/query/query_planner_wildcard_index_test.cpp b/src/mongo/db/query/query_planner_wildcard_index_test.cpp
index 794a959fa1f..318d43728e9 100644
--- a/src/mongo/db/query/query_planner_wildcard_index_test.cpp
+++ b/src/mongo/db/query/query_planner_wildcard_index_test.cpp
@@ -1022,8 +1022,8 @@ TEST_F(QueryPlannerWildcardTest, QueryNotInWildcardIndexHint) {
addWildcardIndex(BSON("a.$**" << 1));
addIndex(BSON("x" << 1));
- runQueryHint(fromjson("{x: {$eq: 1}}"), BSON("a.$**" << 1));
- assertNumSolutions(0U);
+ runInvalidQueryHint(fromjson("{x: {$eq: 1}}"), BSON("a.$**" << 1));
+ assertNoSolutions();
}
TEST_F(QueryPlannerWildcardTest, WildcardIndexDoesNotExist) {
@@ -1037,8 +1037,8 @@ TEST_F(QueryPlannerWildcardTest, WildcardIndexHintWithPartialFilter) {
auto filterExpr = QueryPlannerTest::parseMatchExpression(filterObj);
addWildcardIndex(BSON("$**" << 1), {}, {}, filterExpr.get());
- runQueryHint(fromjson("{a: {$eq: 1}}"), BSON("$**" << 1));
- assertNumSolutions(0U);
+ runInvalidQueryHint(fromjson("{a: {$eq: 1}}"), BSON("$**" << 1));
+ assertNoSolutions();
}
TEST_F(QueryPlannerWildcardTest, MultipleWildcardIndexesHintWithPartialFilter) {
@@ -1046,8 +1046,8 @@ TEST_F(QueryPlannerWildcardTest, MultipleWildcardIndexesHintWithPartialFilter) {
auto filterExpr = QueryPlannerTest::parseMatchExpression(filterObj);
addWildcardIndex(BSON("$**" << 1), {}, {}, filterExpr.get());
- runQueryHint(fromjson("{a: {$eq: 1}, b: {$eq: 1}}"), BSON("$**" << 1));
- assertNumSolutions(0U);
+ runInvalidQueryHint(fromjson("{a: {$eq: 1}, b: {$eq: 1}}"), BSON("$**" << 1));
+ assertNoSolutions();
}
//