summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Storch <david.storch@10gen.com>2018-12-07 10:05:44 -0500
committerDavid Storch <david.storch@10gen.com>2018-12-07 16:14:56 -0500
commit7000ad6f39fe8a6cc82935bb5a81a0a278b98661 (patch)
treea462717033e6b07e49a86c2824c82e2165e65796
parent67e98183b1d80bd12c6fd815fffe4e51619e0156 (diff)
downloadmongo-7000ad6f39fe8a6cc82935bb5a81a0a278b98661.tar.gz
SERVER-38063 Fail cleanly on execution-level explain of $out pipeline.
-rw-r--r--jstests/aggregation/sources/explain_out.js71
-rw-r--r--src/mongo/db/pipeline/document_source_out.cpp9
2 files changed, 80 insertions, 0 deletions
diff --git a/jstests/aggregation/sources/explain_out.js b/jstests/aggregation/sources/explain_out.js
new file mode 100644
index 00000000000..5d49ffcec05
--- /dev/null
+++ b/jstests/aggregation/sources/explain_out.js
@@ -0,0 +1,71 @@
+/**
+ * Test aggregation explain of $out pipelines.
+ *
+ * The 'replaceCollection' $out mode is not allowed with a sharded output collection. Explain of
+ * $out does not accept writeConcern.
+ * @tags: [assumes_unsharded_collection, assumes_write_concern_unchanged]
+ */
+(function() {
+ "use strict";
+
+ load("jstests/libs/fixture_helpers.js"); // For FixtureHelpers.isMongos().
+ load("jstests/libs/analyze_plan.js"); // For getAggPlanStage().
+ load("jstests/aggregation/extras/out_helpers.js"); // For withEachOutMode().
+
+ // Mongos currently uses its own error code if any shard's explain fails.
+ const kErrorCode = FixtureHelpers.isMongos(db) ? 17403 : 51028;
+
+ let sourceColl = db.explain_out_source;
+ let targetColl = db.explain_out_target;
+ sourceColl.drop();
+ targetColl.drop();
+
+ assert.writeOK(sourceColl.insert({_id: 1}));
+
+ function assertQueryPlannerExplainSucceeds(outStage) {
+ let explain = sourceColl.explain("queryPlanner").aggregate([outStage]);
+ let outExplain = getAggPlanStage(explain, "$out");
+ assert.neq(outExplain, null, explain);
+ assert.eq(targetColl.find().itcount(), 0, explain);
+ return outExplain.$out;
+ }
+
+ // Test each out mode with 'queryPlanner' explain verbosity;
+ withEachOutMode(function(outMode) {
+ const outStage = {$out: {to: targetColl.getName(), mode: outMode}};
+ const explain = sourceColl.explain("queryPlanner").aggregate([outStage]);
+ const outExplain = getAggPlanStage(explain, "$out");
+ assert.neq(outExplain, null, explain);
+ assert(outExplain.hasOwnProperty("$out"), explain);
+ assert.eq(outExplain.$out.mode, outMode, outExplain);
+ assert.eq(outExplain.$out.uniqueKey, {_id: 1}, outExplain);
+ assert.eq(targetColl.find().itcount(), 0, explain);
+ });
+
+ function assertExecutionExplainFails(outStage, verbosity) {
+ assert.commandFailedWithCode(db.runCommand({
+ explain: {aggregate: sourceColl.getName(), pipeline: [outStage], cursor: {}},
+ verbosity: verbosity
+ }),
+ kErrorCode);
+ assert.eq(targetColl.find().itcount(), 0);
+ }
+
+ // Test that 'executionStats' and 'allPlansExec' level explain fail with each $out mode. These
+ // explain modes must fail, since they would attempt to do writes. Explain must always be
+ // read-only (including explain of update and delete, which describe what writes they _would_ do
+ // if exected for real).
+ withEachOutMode(function(outMode) {
+ const outStage = {$out: {to: targetColl.getName(), mode: outMode}};
+ assertExecutionExplainFails(outStage, "executionStats");
+ assertExecutionExplainFails(outStage, "allPlansExecution");
+ });
+
+ // Execution explain should fail even if the source collection does not exist.
+ sourceColl.drop();
+ withEachOutMode(function(outMode) {
+ const outStage = {$out: {to: targetColl.getName(), mode: outMode}};
+ assertExecutionExplainFails(outStage, "executionStats");
+ assertExecutionExplainFails(outStage, "allPlansExecution");
+ });
+}());
diff --git a/src/mongo/db/pipeline/document_source_out.cpp b/src/mongo/db/pipeline/document_source_out.cpp
index 13538d6f20c..e443ffb40fb 100644
--- a/src/mongo/db/pipeline/document_source_out.cpp
+++ b/src/mongo/db/pipeline/document_source_out.cpp
@@ -198,6 +198,15 @@ DocumentSource::GetNextResult DocumentSourceOut::getNext() {
}
if (!_initialized) {
+ // Explain of a $out should never try to actually execute any writes. We only ever expect
+ // getNext() to be called for the 'executionStats' and 'allPlansExecution' explain modes.
+ // This assertion should not be triggered for 'queryPlanner' explain of a $out, which is
+ // perfectly legal.
+ uassert(51028,
+ str::stream() << "explain of $out is not allowed with verbosity: "
+ << ExplainOptions::verbosityString(*pExpCtx->explain),
+ !pExpCtx->explain);
+
initializeWriteNs();
_initialized = true;
}