summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIan Boros <ian.boros@mongodb.com>2019-10-04 15:38:42 +0000
committerevergreen <evergreen@mongodb.com>2019-10-04 15:38:42 +0000
commit1f0ae1c45604798888f8b2425475a473d7265bd5 (patch)
treec331eebf5795422507c4826c4219c303a20edced
parent2d671ab90e0282773ea89f1860705503dd5bf74d (diff)
downloadmongo-1f0ae1c45604798888f8b2425475a473d7265bd5.tar.gz
SERVER-43202 propagate errors from interrupts during yielding
-rw-r--r--jstests/core/filemd5.js3
-rw-r--r--jstests/core/fts_querylang.js2
-rw-r--r--jstests/core/geo_s2nearwithin.js2
-rw-r--r--jstests/core/geonear_key.js9
-rw-r--r--jstests/core/minmax.js4
-rw-r--r--jstests/core/wildcard_index_hint.js2
-rw-r--r--jstests/noPassthrough/ignore_notablescan.js2
-rw-r--r--jstests/noPassthroughWithMongod/interrupt_while_yielded.js111
-rw-r--r--src/mongo/base/error_codes.err2
-rw-r--r--src/mongo/db/exec/cached_plan.cpp2
-rw-r--r--src/mongo/db/exec/subplan.cpp29
-rw-r--r--src/mongo/db/pipeline/dependencies.cpp5
-rw-r--r--src/mongo/db/pipeline/pipeline_d.cpp85
-rw-r--r--src/mongo/db/query/explain.cpp7
-rw-r--r--src/mongo/db/query/get_executor.cpp2
-rw-r--r--src/mongo/db/query/plan_yield_policy.cpp5
-rw-r--r--src/mongo/db/query/query_planner.cpp33
17 files changed, 229 insertions, 76 deletions
diff --git a/jstests/core/filemd5.js b/jstests/core/filemd5.js
index 9ea70283a73..d6be13b713e 100644
--- a/jstests/core/filemd5.js
+++ b/jstests/core/filemd5.js
@@ -14,7 +14,8 @@
db.fs.chunks.drop();
assert.writeOK(db.fs.chunks.insert({files_id: 1, n: 0, data: new BinData(0, "test")}));
-assert.commandFailedWithCode(db.runCommand({filemd5: 1, root: "fs"}), ErrorCodes.BadValue);
+assert.commandFailedWithCode(db.runCommand({filemd5: 1, root: "fs"}),
+ ErrorCodes.NoQueryExecutionPlans);
db.fs.chunks.ensureIndex({files_id: 1, n: 1});
assert.commandWorked(db.runCommand({filemd5: 1, root: "fs"}));
diff --git a/jstests/core/fts_querylang.js b/jstests/core/fts_querylang.js
index a80258940ba..87a5e1d760b 100644
--- a/jstests/core/fts_querylang.js
+++ b/jstests/core/fts_querylang.js
@@ -87,5 +87,5 @@ assert.commandFailedWithCode(
assert.throws(function() {
coll.find({$nor: [{$text: {$search: 'a'}}]}).itcount();
}),
- ErrorCodes.BadValue);
+ ErrorCodes.NoQueryExecutionPlans);
}());
diff --git a/jstests/core/geo_s2nearwithin.js b/jstests/core/geo_s2nearwithin.js
index d8f15dcdb54..a40aa34d255 100644
--- a/jstests/core/geo_s2nearwithin.js
+++ b/jstests/core/geo_s2nearwithin.js
@@ -25,7 +25,7 @@ let res = assert.commandFailedWithCode(t.runCommand("aggregate", {
{near: [0, 0], distanceField: "d", query: {geo: {$within: {$center: [[0, 0], 1]}}}}
}],
}),
- ErrorCodes.BadValue);
+ ErrorCodes.NoQueryExecutionPlans);
assert(res.errmsg.includes("unable to find index for $geoNear query"), tojson(res));
// Spherical is specified so this does work. Old style points are weird
diff --git a/jstests/core/geonear_key.js b/jstests/core/geonear_key.js
index 0238e012577..e86b6b3a699 100644
--- a/jstests/core/geonear_key.js
+++ b/jstests/core/geonear_key.js
@@ -62,7 +62,7 @@ assertGeoNearFails({near: [0, 0], key: ""}, ErrorCodes.BadValue);
assertGeoNearFails({near: [0, 0]}, ErrorCodes.IndexNotFound);
// Verify that the query system raises an error when an index is specified that doesn't exist.
-assertGeoNearFails({near: [0, 0], key: "a"}, ErrorCodes.BadValue);
+assertGeoNearFails({near: [0, 0], key: "a"}, ErrorCodes.NoQueryExecutionPlans);
// Create a number of 2d and 2dsphere indexes.
assert.commandWorked(coll.createIndex({a: "2d"}));
@@ -89,12 +89,13 @@ assertGeoNearSucceedsAndReturnsIds(
// Verify that $geoNear fails when a GeoJSON point is used with a 'key' path that only has a 2d
// index. GeoJSON points can only be used for spherical geometry.
-assertGeoNearFails({near: {type: "Point", coordinates: [0, 0]}, key: "b.c"}, ErrorCodes.BadValue);
+assertGeoNearFails({near: {type: "Point", coordinates: [0, 0]}, key: "b.c"},
+ ErrorCodes.NoQueryExecutionPlans);
// Verify that $geoNear fails when:
// -- The only index available over the 'key' path is 2dsphere.
// -- spherical=false.
// -- The search point is a legacy coordinate pair.
-assertGeoNearFails({near: [0, 0], key: "b.d"}, ErrorCodes.BadValue);
-assertGeoNearFails({near: [0, 0], key: "b.d", spherical: false}, ErrorCodes.BadValue);
+assertGeoNearFails({near: [0, 0], key: "b.d"}, ErrorCodes.NoQueryExecutionPlans);
+assertGeoNearFails({near: [0, 0], key: "b.d", spherical: false}, ErrorCodes.NoQueryExecutionPlans);
}());
diff --git a/jstests/core/minmax.js b/jstests/core/minmax.js
index 1a32fe9d059..14f4ed0ec2c 100644
--- a/jstests/core/minmax.js
+++ b/jstests/core/minmax.js
@@ -70,12 +70,12 @@ assert.eq(error.code, 51176, error);
error = assert.throws(function() {
coll.find().min({a: 1}).hint({$natural: 1}).toArray();
});
-assert.eq(error.code, ErrorCodes.BadValue, error);
+assert.eq(error.code, ErrorCodes.NoQueryExecutionPlans, error);
error = assert.throws(function() {
coll.find().max({a: 1}).hint({$natural: 1}).toArray();
});
-assert.eq(error.code, ErrorCodes.BadValue);
+assert.eq(error.code, ErrorCodes.NoQueryExecutionPlans);
coll.drop();
assert.commandWorked(coll.ensureIndex({a: 1}));
diff --git a/jstests/core/wildcard_index_hint.js b/jstests/core/wildcard_index_hint.js
index f20e2b238c1..ff481a62712 100644
--- a/jstests/core/wildcard_index_hint.js
+++ b/jstests/core/wildcard_index_hint.js
@@ -73,7 +73,7 @@ assert.commandWorked(coll.createIndex({"c.$**": 1}));
// Hint on path that is not in query argument.
assert.commandFailedWithCode(
db.runCommand({find: coll.getName(), filter: {"a": 1}, hint: {"c.$**": 1}}),
- ErrorCodes.BadValue);
+ ErrorCodes.NoQueryExecutionPlans);
// Hint on a path specified $** index.
assertExpectedIndexAnswersQueryWithHint(
diff --git a/jstests/noPassthrough/ignore_notablescan.js b/jstests/noPassthrough/ignore_notablescan.js
index 255b646f757..ab8c3e00911 100644
--- a/jstests/noPassthrough/ignore_notablescan.js
+++ b/jstests/noPassthrough/ignore_notablescan.js
@@ -29,7 +29,7 @@ function runTests(ServerType) {
// the reported query error code is the cryptic 'BadValue'.
assert.commandFailedWithCode(
primaryDB.runCommand({find: collName, filter: {any_nonexistent_field: {$exists: true}}}),
- ErrorCodes.BadValue);
+ ErrorCodes.NoQueryExecutionPlans);
s.stop();
}
diff --git a/jstests/noPassthroughWithMongod/interrupt_while_yielded.js b/jstests/noPassthroughWithMongod/interrupt_while_yielded.js
new file mode 100644
index 00000000000..935e5224ea9
--- /dev/null
+++ b/jstests/noPassthroughWithMongod/interrupt_while_yielded.js
@@ -0,0 +1,111 @@
+(function() {
+"use strict";
+
+const kFailPointName = "setYieldAllLocksHang";
+const kCommandComment = "interruptedWhileYieldedComment";
+
+const coll = db.interrupt_while_yielded;
+coll.drop();
+assert.commandWorked(coll.insert({a: 1, b: 1, c: 1}));
+assert.commandWorked(coll.insert({a: 1, b: 1, c: 1}));
+
+assert.commandWorked(coll.createIndex({a: 1, b: 1}));
+assert.commandWorked(coll.createIndex({a: 1, c: 1}));
+
+// This is needed to make sure that a yield point is reached.
+assert.commandWorked(db.adminCommand({setParameter: 1, internalQueryExecYieldIterations: 1}));
+
+function runTestWithQuery(queryFn) {
+ let waitForParallelShell = null;
+
+ try {
+ assert.commandWorked(db.adminCommand({
+ configureFailPoint: kFailPointName,
+ mode: "alwaysOn",
+ data: {namespace: coll.getFullName(), checkForInterruptAfterHang: true}
+ }));
+
+ // Run a command that should hit the fail point in a parallel shell.
+ let code = `let queryFn = ${queryFn};`;
+ code += `const coll = db.${coll.getName()};`;
+ code += `const kCommandComment = "${kCommandComment}";`;
+ function parallelShellFn() {
+ const err = assert.throws(queryFn);
+ assert.commandFailedWithCode(err, [ErrorCodes.Interrupted]);
+ }
+ code += "(" + parallelShellFn.toString() + ")();";
+
+ waitForParallelShell = startParallelShell(code);
+
+ // Find the operation running the query.
+ let opId = null;
+
+ assert.soon(function() {
+ const ops = db.getSiblingDB("admin")
+ .aggregate([
+ {$currentOp: {allUsers: true, localOps: true}},
+ {
+ $match: {
+ numYields: {$gt: 0},
+ ns: coll.getFullName(),
+ "command.comment": kCommandComment
+ }
+ }
+ ])
+ .toArray();
+
+ if (ops.length > 0) {
+ assert.eq(ops.length, 1);
+ opId = ops[0].opid;
+ return true;
+ }
+
+ return false;
+ });
+
+ // Kill the op.
+ db.killOp(opId);
+
+ } finally {
+ // Disable the failpoint so that the server will continue, and hit an interrupt check.
+ assert.commandWorked(db.adminCommand({configureFailPoint: kFailPointName, mode: "off"}));
+
+ if (waitForParallelShell) {
+ waitForParallelShell();
+ }
+ }
+
+ // Check that the server is still up.
+ assert.commandWorked(db.adminCommand({isMaster: 1}));
+}
+
+function rootedOr() {
+ coll.find({$or: [{a: 1}, {b: 1}]}).comment(kCommandComment).itcount();
+}
+runTestWithQuery(rootedOr);
+
+function groupFindDistinct() {
+ coll.aggregate([{$group: {_id: "$a"}}], {comment: kCommandComment}).itcount();
+}
+runTestWithQuery(groupFindDistinct);
+
+function projectImmediatelyAfterMatch() {
+ coll.aggregate([{$match: {a: 1}}, {$project: {_id: 0, a: 1}}, {$unwind: "$a"}],
+ {comment: kCommandComment})
+ .itcount();
+}
+runTestWithQuery(projectImmediatelyAfterMatch);
+
+function sortImmediatelyAfterMatch() {
+ coll.aggregate([{$match: {a: 1, b: 1, c: 1}}, {$sort: {a: 1}}], {comment: kCommandComment})
+ .itcount();
+}
+runTestWithQuery(sortImmediatelyAfterMatch);
+
+function sortAndProjectionImmediatelyAfterMatch() {
+ coll.aggregate([{$match: {a: 1}}, {$project: {_id: 0, a: 1}}, {$sort: {a: 1}}],
+ {comment: kCommandComment})
+ .itcount();
+}
+runTestWithQuery(sortAndProjectionImmediatelyAfterMatch);
+}());
diff --git a/src/mongo/base/error_codes.err b/src/mongo/base/error_codes.err
index 43b1656368b..377aa7649b7 100644
--- a/src/mongo/base/error_codes.err
+++ b/src/mongo/base/error_codes.err
@@ -290,6 +290,8 @@ error_code("TransactionCoordinatorDeadlineTaskCanceled", 287)
error_code("ChecksumMismatch", 288)
# Internal only code used by WaitForMajorityService.
error_code("WaitForMajorityServiceEarlierOpTimeAvailable", 289)
+# 290 is TransactionExceededLifetimeLimitSeconds, but not used.
+error_code("NoQueryExecutionPlans", 291)
# Error codes 4000-8999 are reserved.
diff --git a/src/mongo/db/exec/cached_plan.cpp b/src/mongo/db/exec/cached_plan.cpp
index e426a0ad340..f47a253669b 100644
--- a/src/mongo/db/exec/cached_plan.cpp
+++ b/src/mongo/db/exec/cached_plan.cpp
@@ -208,7 +208,7 @@ Status CachedPlanStage::replan(PlanYieldPolicy* yieldPolicy, bool shouldCache) {
// 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::BadValue,
+ return Status(ErrorCodes::NoQueryExecutionPlans,
str::stream() << "error processing query: " << _canonicalQuery->toString()
<< " No query solutions");
}
diff --git a/src/mongo/db/exec/subplan.cpp b/src/mongo/db/exec/subplan.cpp
index 168fc342d3d..b5d33755761 100644
--- a/src/mongo/db/exec/subplan.cpp
+++ b/src/mongo/db/exec/subplan.cpp
@@ -196,13 +196,13 @@ Status tagOrChildAccordingToCache(PlanCacheIndexTree* compositeCacheData,
// For example, we don't cache things for 2d indices.
str::stream ss;
ss << "No cache data for subchild " << orChild->debugString();
- return Status(ErrorCodes::BadValue, ss);
+ return Status(ErrorCodes::NoQueryExecutionPlans, ss);
}
if (SolutionCacheData::USE_INDEX_TAGS_SOLN != branchCacheData->solnType) {
str::stream ss;
ss << "No indexed cache data for subchild " << orChild->debugString();
- return Status(ErrorCodes::BadValue, ss);
+ return Status(ErrorCodes::NoQueryExecutionPlans, ss);
}
// Add the index assignments to our original query.
@@ -212,7 +212,7 @@ Status tagOrChildAccordingToCache(PlanCacheIndexTree* compositeCacheData,
if (!tagStatus.isOK()) {
str::stream ss;
ss << "Failed to extract indices from subchild " << orChild->debugString();
- return Status(ErrorCodes::BadValue, ss);
+ return tagStatus.withContext(ss);
}
// Add the child's cache data to the cache data we're creating for the main query.
@@ -293,7 +293,7 @@ Status SubplanStage::choosePlanForSubqueries(PlanYieldPolicy* yieldPolicy) {
str::stream ss;
ss << "Failed to pick best plan for subchild "
<< branchResult->canonicalQuery->toString();
- return Status(ErrorCodes::BadValue, ss);
+ return Status(ErrorCodes::NoQueryExecutionPlans, ss);
}
QuerySolution* bestSoln = multiPlanStage->bestSolution();
@@ -303,13 +303,13 @@ Status SubplanStage::choosePlanForSubqueries(PlanYieldPolicy* yieldPolicy) {
if (NULL == bestSoln->cacheData.get()) {
str::stream ss;
ss << "No cache data for subchild " << orChild->debugString();
- return Status(ErrorCodes::BadValue, ss);
+ return Status(ErrorCodes::NoQueryExecutionPlans, ss);
}
if (SolutionCacheData::USE_INDEX_TAGS_SOLN != bestSoln->cacheData->solnType) {
str::stream ss;
ss << "No indexed cache data for subchild " << orChild->debugString();
- return Status(ErrorCodes::BadValue, ss);
+ return Status(ErrorCodes::NoQueryExecutionPlans, ss);
}
// Add the index assignments to our original query.
@@ -319,7 +319,7 @@ Status SubplanStage::choosePlanForSubqueries(PlanYieldPolicy* yieldPolicy) {
if (!tagStatus.isOK()) {
str::stream ss;
ss << "Failed to extract indices from subchild " << orChild->debugString();
- return Status(ErrorCodes::BadValue, ss);
+ return tagStatus.withContext(ss);
}
cacheData->children.push_back(bestSoln->cacheData->tree->clone());
@@ -336,7 +336,7 @@ Status SubplanStage::choosePlanForSubqueries(PlanYieldPolicy* yieldPolicy) {
if (!solnRoot) {
str::stream ss;
ss << "Failed to build indexed data path for subplanned query\n";
- return Status(ErrorCodes::BadValue, ss);
+ return Status(ErrorCodes::NoQueryExecutionPlans, ss);
}
LOG(5) << "Subplanner: fully tagged tree is " << redact(solnRoot->toString());
@@ -348,7 +348,7 @@ Status SubplanStage::choosePlanForSubqueries(PlanYieldPolicy* yieldPolicy) {
if (NULL == _compositeSolution.get()) {
str::stream ss;
ss << "Failed to analyze subplanned query";
- return Status(ErrorCodes::BadValue, ss);
+ return Status(ErrorCodes::NoQueryExecutionPlans, ss);
}
LOG(5) << "Subplanner: Composite solution is " << redact(_compositeSolution->toString());
@@ -382,7 +382,7 @@ Status SubplanStage::choosePlanWholeQuery(PlanYieldPolicy* yieldPolicy) {
// 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::BadValue,
+ return Status(ErrorCodes::NoQueryExecutionPlans,
str::stream() << "error processing query: " << _query->toString()
<< " No query solutions");
}
@@ -453,11 +453,10 @@ Status SubplanStage::pickBestPlan(PlanYieldPolicy* yieldPolicy) {
// the overall winning plan from the resulting index tags.
Status subplanSelectStat = choosePlanForSubqueries(yieldPolicy);
if (!subplanSelectStat.isOK()) {
- if (subplanSelectStat == ErrorCodes::QueryPlanKilled ||
- subplanSelectStat == ErrorCodes::MaxTimeMSExpired) {
- // Query planning cannot continue if the plan for one of the subqueries was killed
- // because the collection or a candidate index may have been dropped, or if we've
- // exceeded the operation's time limit.
+ if (subplanSelectStat != ErrorCodes::NoQueryExecutionPlans) {
+ // Query planning can continue if we failed to find a solution for one of the
+ // children. Otherwise, it cannot, as it may no longer be safe to access the collection
+ // (and index may have been dropped, we may have exceeded the time limit, etc).
return subplanSelectStat;
}
return choosePlanWholeQuery(yieldPolicy);
diff --git a/src/mongo/db/pipeline/dependencies.cpp b/src/mongo/db/pipeline/dependencies.cpp
index 1586a68f96b..8c92cd5c751 100644
--- a/src/mongo/db/pipeline/dependencies.cpp
+++ b/src/mongo/db/pipeline/dependencies.cpp
@@ -102,6 +102,11 @@ BSONObj DepsTracker::toProjection() const {
}
last = field + '.';
+
+ // We should only have dependencies on fields that are valid in aggregation. Create a
+ // FieldPath to check this.
+ FieldPath fieldPath(field);
+
bb.append(field, 1);
}
diff --git a/src/mongo/db/pipeline/pipeline_d.cpp b/src/mongo/db/pipeline/pipeline_d.cpp
index 4a4f4b7ea14..69540a8b8bd 100644
--- a/src/mongo/db/pipeline/pipeline_d.cpp
+++ b/src/mongo/db/pipeline/pipeline_d.cpp
@@ -96,6 +96,39 @@ using std::unique_ptr;
using write_ops::Insert;
namespace {
+
+/**
+ * Return whether the given sort spec can be used in a find() sort.
+ */
+bool canSortBePushedDown(const BSONObj& sortSpec) {
+ // Return whether or not a sort stage can be pushed into the query layer.
+ for (auto&& elem : sortSpec) {
+ if (BSONType::Object != elem.type()) {
+ continue;
+ }
+
+ BSONObj subObj = elem.embeddedObject();
+ if (subObj.nFields() != 1) {
+ continue;
+ }
+
+ BSONElement firstElem = subObj.firstElement();
+ if (firstElem.fieldNameStringData() == "$meta" && firstElem.type() == BSONType::String) {
+ // Indeed it's a $meta.
+
+ // Technically sorting by {$meta: "textScore"} can be done in find() but requires a
+ // corresponding projection, so for simplicity we don't support it.
+ if (firstElem.valueStringData() == "textScore" ||
+ firstElem.valueStringData() == "randVal") {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+
/**
* Returns a PlanExecutor which uses a random cursor to sample documents if successful. Returns {}
* if the storage engine doesn't support random cursors, or if 'sampleSize' is a large enough
@@ -233,7 +266,7 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> attemptToGetExe
return distinctExecutor.getStatus().withContext(
"Unable to use distinct scan to optimize $group stage");
} else if (!distinctExecutor.getValue()) {
- return {ErrorCodes::OperationFailed,
+ return {ErrorCodes::NoQueryExecutionPlans,
"Unable to use distinct scan to optimize $group stage"};
} else {
return distinctExecutor;
@@ -680,17 +713,16 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> PipelineD::prep
pipeline->addInitialSource(groupTransform);
return swExecutorGrouped;
- } else if (swExecutorGrouped == ErrorCodes::QueryPlanKilled) {
- return {ErrorCodes::OperationFailed,
- str::stream() << "Failed to determine whether query system can provide a "
- "DISTINCT_SCAN grouping: "
- << swExecutorGrouped.getStatus().toString()};
+ } else if (swExecutorGrouped != ErrorCodes::NoQueryExecutionPlans) {
+ return swExecutorGrouped.getStatus().withContext(
+ "Failed to determine whether query system can provide a "
+ "DISTINCT_SCAN grouping");
}
}
const BSONObj emptyProjection;
- const BSONObj metaSortProjection = BSON("$meta"
- << "sortKey");
+ const BSONObj metaSortProjection = BSON("$sortKey" << BSON("$meta"
+ << "sortKey"));
// The only way to get meta information (e.g. the text score) is to let the query system handle
// the projection. In all other cases, unless the query system can do an index-covered
@@ -700,7 +732,7 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> PipelineD::prep
plannerOpts |= QueryPlannerParams::NO_UNCOVERED_PROJECTIONS;
}
- if (sortStage) {
+ if (sortStage && canSortBePushedDown(*sortObj)) {
// See if the query system can provide a non-blocking sort.
auto swExecutorSort =
attemptToGetExecutor(opCtx,
@@ -736,11 +768,11 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> PipelineD::prep
if (swExecutorSortAndProj.isOK()) {
// Success! We have a non-blocking sort and a covered projection.
exec = std::move(swExecutorSortAndProj.getValue());
- } else if (swExecutorSortAndProj == ErrorCodes::QueryPlanKilled) {
- return {ErrorCodes::OperationFailed,
- str::stream() << "Failed to determine whether query system can provide a "
- "covered projection in addition to a non-blocking sort: "
- << swExecutorSortAndProj.getStatus().toString()};
+ } else if (swExecutorSortAndProj != ErrorCodes::NoQueryExecutionPlans) {
+
+ return swExecutorSortAndProj.getStatus().withContext(
+ "Failed to determine whether query system can provide a "
+ "covered projection in addition to a non-blocking sort");
} else {
// The query system couldn't cover the projection.
*projectionObj = BSONObj();
@@ -755,20 +787,15 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> PipelineD::prep
pipeline->_sources.push_front(sortStage->getLimitSrc());
}
return std::move(exec);
- } else if (swExecutorSort == ErrorCodes::QueryPlanKilled) {
- return {
- ErrorCodes::OperationFailed,
- str::stream()
- << "Failed to determine whether query system can provide a non-blocking sort: "
- << swExecutorSort.getStatus().toString()};
+ } else if (swExecutorSort != ErrorCodes::NoQueryExecutionPlans) {
+ return swExecutorSort.getStatus().withContext(
+ "Failed to determine whether query system can provide a non-blocking sort");
}
- // The query system can't provide a non-blocking sort.
- *sortObj = BSONObj();
}
- // Either there was no $sort stage, or the query system could not provide a non-blocking
- // sort.
- dassert(sortObj->isEmpty());
+ // Either there's no sort or the query system can't provide a non-blocking sort.
+ *sortObj = BSONObj();
+
*projectionObj = removeSortKeyMetaProjection(*projectionObj);
const auto metadataRequired = deps.getAllRequiredMetadataTypes();
if (metadataRequired.size() == 1 &&
@@ -796,11 +823,9 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> PipelineD::prep
if (swExecutorProj.isOK()) {
// Success! We have a covered projection.
return std::move(swExecutorProj.getValue());
- } else if (swExecutorProj == ErrorCodes::QueryPlanKilled) {
- return {ErrorCodes::OperationFailed,
- str::stream()
- << "Failed to determine whether query system can provide a covered projection: "
- << swExecutorProj.getStatus().toString()};
+ } else if (swExecutorProj != ErrorCodes::NoQueryExecutionPlans) {
+ return swExecutorProj.getStatus().withContext(
+ "Failed to determine whether query system can provide a covered projection");
}
// The query system couldn't provide a covered or simple uncovered projection.
diff --git a/src/mongo/db/query/explain.cpp b/src/mongo/db/query/explain.cpp
index 977b7fdb1a1..f39f959b545 100644
--- a/src/mongo/db/query/explain.cpp
+++ b/src/mongo/db/query/explain.cpp
@@ -882,9 +882,10 @@ void Explain::explainStages(PlanExecutor* exec,
if (verbosity >= ExplainOptions::Verbosity::kExecStats) {
executePlanStatus = exec->executePlan();
- // If executing the query failed because it was killed, then the collection may no longer be
- // valid. We indicate this by setting our collection pointer to null.
- if (executePlanStatus == ErrorCodes::QueryPlanKilled) {
+ // If executing the query failed, for any number of reasons other than a planning failure,
+ // then the collection may no longer be valid. We conservatively set our collection pointer
+ // to null in case it is invalid.
+ if (executePlanStatus != ErrorCodes::NoQueryExecutionPlans) {
collection = nullptr;
}
}
diff --git a/src/mongo/db/query/get_executor.cpp b/src/mongo/db/query/get_executor.cpp
index ba1ad4a1e04..1c1f4f3387a 100644
--- a/src/mongo/db/query/get_executor.cpp
+++ b/src/mongo/db/query/get_executor.cpp
@@ -517,7 +517,7 @@ StatusWith<PrepareExecutionResult> prepareExecution(OperationContext* opCtx,
// 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::BadValue,
+ return Status(ErrorCodes::NoQueryExecutionPlans,
str::stream() << "error processing query: " << canonicalQuery->toString()
<< " No query solutions");
}
diff --git a/src/mongo/db/query/plan_yield_policy.cpp b/src/mongo/db/query/plan_yield_policy.cpp
index b86d3f437f4..534dec9d710 100644
--- a/src/mongo/db/query/plan_yield_policy.cpp
+++ b/src/mongo/db/query/plan_yield_policy.cpp
@@ -188,6 +188,11 @@ void PlanYieldPolicy::_yieldAllLocks(OperationContext* opCtx,
if (ns.empty() || ns == planExecNS.ns()) {
MONGO_FAIL_POINT_PAUSE_WHILE_SET(setYieldAllLocksHang);
}
+
+ if (config.getData().getField("checkForInterruptAfterHang").trueValue()) {
+ // Throws.
+ opCtx->checkForInterrupt();
+ }
}
MONGO_FAIL_POINT_BLOCK(setYieldAllLocksWait, customWait) {
diff --git a/src/mongo/db/query/query_planner.cpp b/src/mongo/db/query/query_planner.cpp
index 54c6a0b9fb0..3e40c5a4024 100644
--- a/src/mongo/db/query/query_planner.cpp
+++ b/src/mongo/db/query/query_planner.cpp
@@ -373,11 +373,11 @@ StatusWith<std::unique_ptr<PlanCacheIndexTree>> QueryPlanner::cacheDataFromTagge
Status QueryPlanner::tagAccordingToCache(MatchExpression* filter,
const PlanCacheIndexTree* const indexTree,
const map<IndexEntry::Identifier, size_t>& indexMap) {
- if (NULL == filter) {
- return Status(ErrorCodes::BadValue, "Cannot tag tree: filter is NULL.");
+ if (nullptr == filter) {
+ return Status(ErrorCodes::NoQueryExecutionPlans, "Cannot tag tree: filter is NULL.");
}
- if (NULL == indexTree) {
- return Status(ErrorCodes::BadValue, "Cannot tag tree: indexTree is NULL.");
+ if (nullptr == indexTree) {
+ return Status(ErrorCodes::NoQueryExecutionPlans, "Cannot tag tree: indexTree is NULL.");
}
// We're tagging the tree here, so it shouldn't have
@@ -389,7 +389,7 @@ Status QueryPlanner::tagAccordingToCache(MatchExpression* filter,
ss << "Cache topology and query did not match: "
<< "query has " << filter->numChildren() << " children "
<< "and cache has " << indexTree->children.size() << " children.";
- return Status(ErrorCodes::BadValue, ss);
+ return Status(ErrorCodes::NoQueryExecutionPlans, ss);
}
// Continue the depth-first tree traversal.
@@ -406,7 +406,7 @@ Status QueryPlanner::tagAccordingToCache(MatchExpression* filter,
for (const auto& orPushdown : indexTree->orPushdowns) {
auto index = indexMap.find(orPushdown.indexEntryId);
if (index == indexMap.end()) {
- return Status(ErrorCodes::BadValue,
+ return Status(ErrorCodes::NoQueryExecutionPlans,
str::stream() << "Did not find index: " << orPushdown.indexEntryId);
}
OrPushdownTag::Destination dest;
@@ -422,7 +422,7 @@ Status QueryPlanner::tagAccordingToCache(MatchExpression* filter,
if (got == indexMap.end()) {
str::stream ss;
ss << "Did not find index with name: " << indexTree->entry->identifier.catalogName;
- return Status(ErrorCodes::BadValue, ss);
+ return Status(ErrorCodes::NoQueryExecutionPlans, ss);
}
if (filter->getTag()) {
OrPushdownTag* orPushdownTag = static_cast<OrPushdownTag*>(filter->getTag());
@@ -454,7 +454,7 @@ StatusWith<std::unique_ptr<QuerySolution>> QueryPlanner::planFromCache(
auto soln = buildWholeIXSoln(
*winnerCacheData.tree->entry, query, params, winnerCacheData.wholeIXSolnDir);
if (!soln) {
- return Status(ErrorCodes::BadValue,
+ return Status(ErrorCodes::NoQueryExecutionPlans,
"plan cache error: soln that uses index to provide sort");
} else {
return {std::move(soln)};
@@ -464,7 +464,8 @@ StatusWith<std::unique_ptr<QuerySolution>> QueryPlanner::planFromCache(
// with tailable==true, hence the false below.
auto soln = buildCollscanSoln(query, false, params);
if (!soln) {
- return Status(ErrorCodes::BadValue, "plan cache error: collection scan soln");
+ return Status(ErrorCodes::NoQueryExecutionPlans,
+ "plan cache error: collection scan soln");
} else {
return {std::move(soln)};
}
@@ -512,14 +513,14 @@ StatusWith<std::unique_ptr<QuerySolution>> QueryPlanner::planFromCache(
query, std::move(clone), expandedIndexes, params));
if (!solnRoot) {
- return Status(ErrorCodes::BadValue,
+ return Status(ErrorCodes::NoQueryExecutionPlans,
str::stream() << "Failed to create data access plan from cache. Query: "
<< query.toStringShort());
}
auto soln = QueryPlannerAnalysis::analyzeDataAccess(query, params, std::move(solnRoot));
if (!soln) {
- return Status(ErrorCodes::BadValue,
+ return Status(ErrorCodes::NoQueryExecutionPlans,
str::stream()
<< "Failed to analyze plan from cache. Query: " << query.toStringShort());
}
@@ -726,7 +727,8 @@ StatusWith<std::vector<std::unique_ptr<QuerySolution>>> QueryPlanner::plan(
LOG(5) << "Unable to find index for $geoNear query.";
// Don't leave tags on query tree.
query.root()->resetTag();
- return Status(ErrorCodes::BadValue, "unable to find index for $geoNear query");
+ return Status(ErrorCodes::NoQueryExecutionPlans,
+ "unable to find index for $geoNear query");
}
LOG(5) << "Rated tree after geonear processing:" << redact(query.root()->debugString());
@@ -749,14 +751,15 @@ StatusWith<std::vector<std::unique_ptr<QuerySolution>>> QueryPlanner::plan(
if (textIndexCount != 1) {
// Don't leave tags on query tree.
query.root()->resetTag();
- return Status(ErrorCodes::BadValue, "need exactly one text index for $text query");
+ return Status(ErrorCodes::NoQueryExecutionPlans,
+ "need exactly one text index for $text query");
}
// Error if the text node is tagged with zero indices.
if (0 == tag->first.size() && 0 == tag->notFirst.size()) {
// Don't leave tags on query tree.
query.root()->resetTag();
- return Status(ErrorCodes::BadValue,
+ return Status(ErrorCodes::NoQueryExecutionPlans,
"failed to use text index to satisfy $text query (if text index is "
"compound, are equality predicates given for all prefix fields?)");
}
@@ -833,7 +836,7 @@ StatusWith<std::vector<std::unique_ptr<QuerySolution>>> QueryPlanner::plan(
MatchExpression* root = query.root();
for (size_t i = 0; i < root->numChildren(); ++i) {
if (textNode == root->getChild(i)) {
- return Status(ErrorCodes::BadValue,
+ return Status(ErrorCodes::NoQueryExecutionPlans,
"Failed to produce a solution for TEXT under OR - "
"other non-TEXT clauses under OR have to be indexed as well.");
}