diff options
author | David Percy <david.percy@mongodb.com> | 2019-11-11 18:50:37 +0000 |
---|---|---|
committer | evergreen <evergreen@mongodb.com> | 2019-11-11 18:50:37 +0000 |
commit | 2411853a626e2c28e8bc54c82490cbb2ab9947a0 (patch) | |
tree | 1fde60264e4ccfecaded6cbfa8aa15cd26e3951e /src/mongo/db/query | |
parent | 2b64ed2d9049a10cf105f3fb1258062678bfd682 (diff) | |
download | mongo-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.cpp | 10 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner.cpp | 104 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner_array_test.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner_geo_test.cpp | 30 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner_index_test.cpp | 9 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner_partialidx_test.cpp | 120 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner_test_fixture.cpp | 11 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner_test_fixture.h | 6 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner_text_test.cpp | 9 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner_wildcard_index_test.cpp | 12 |
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(); } // |