summaryrefslogtreecommitdiff
path: root/jstests
diff options
context:
space:
mode:
Diffstat (limited to 'jstests')
-rw-r--r--jstests/sharding/query/collation_targeting.js19
-rw-r--r--jstests/sharding/query/collation_targeting_inherited.js17
-rw-r--r--jstests/sharding/query/explain_cmd.js12
-rw-r--r--jstests/sharding/query/explain_find_and_modify_sharded.js62
-rw-r--r--jstests/sharding/updateOne_without_shard_key/explain.js490
5 files changed, 569 insertions, 31 deletions
diff --git a/jstests/sharding/query/collation_targeting.js b/jstests/sharding/query/collation_targeting.js
index 602c63c990a..329e65567c5 100644
--- a/jstests/sharding/query/collation_targeting.js
+++ b/jstests/sharding/query/collation_targeting.js
@@ -190,11 +190,10 @@ if (WriteWithoutShardKeyTestUtil.isWriteWithoutShardKeyFeatureEnabled(testDB)) {
coll.findAndModify({query: {a: "foo"}, update: {$set: {b: 1}}, collation: caseInsensitive});
assert(res.a === "foo" || res.a === "FOO");
- // TODO: SERVER-69925 Implement explain for findAndModify.
- // assert.throws(function() {
- // coll.explain().findAndModify(
- // {query: {a: "foo"}, update: {$set: {b: 1}}, collation: caseInsensitive});
- // });
+ explain = coll.explain().findAndModify(
+ {query: {a: "foo"}, update: {$set: {b: 1}}, collation: caseInsensitive});
+ assert.commandWorked(explain);
+ assert.eq(1, explain.queryPlanner.winningPlan.shards.length);
} else {
// Sharded findAndModify on strings with non-simple collation should fail, because findAndModify
// must target a single shard.
@@ -318,9 +317,13 @@ if (WriteWithoutShardKeyTestUtil.isWriteWithoutShardKeyFeatureEnabled(testDB)) {
assert.eq(1, writeRes.nRemoved);
let afterNumDocsMatch = coll.find({a: "foo"}).collation(caseInsensitive).count();
assert.eq(beforeNumDocsMatch - 1, afterNumDocsMatch);
+
+ explain = coll.explain().remove({a: "foo"}, {justOne: true, collation: caseInsensitive});
+ assert.commandWorked(explain);
+ assert.eq(1, explain.queryPlanner.winningPlan.shards.length);
+
coll.insert(a_foo);
coll.insert(a_FOO);
- // TODO: SERVER-69924 Implement explain for deleteOne
} else {
// A single remove (justOne: true) must be single-shard or an exact-ID query. A query is
// exact-ID if it contains an equality on _id and either has the collection default collation or
@@ -421,7 +424,9 @@ if (WriteWithoutShardKeyTestUtil.isWriteWithoutShardKeyFeatureEnabled(testDB)) {
writeRes =
assert.commandWorked(coll.update({a: "foo"}, {$set: {b: 1}}, {collation: caseInsensitive}));
assert.eq(1, writeRes.nMatched);
- // TODO: SERVER-69922 Implement explain for updateOne
+ explain = coll.explain().update({a: "foo"}, {$set: {b: 1}}, {collation: caseInsensitive});
+ assert.commandWorked(explain);
+ assert.eq(1, explain.queryPlanner.winningPlan.shards.length);
} else {
// A single (non-multi) update must be single-shard or an exact-ID query. A query is exact-ID if
// it contains an equality on _id and either has the collection default collation or _id is not
diff --git a/jstests/sharding/query/collation_targeting_inherited.js b/jstests/sharding/query/collation_targeting_inherited.js
index 7662baeee0f..4cc9e23765d 100644
--- a/jstests/sharding/query/collation_targeting_inherited.js
+++ b/jstests/sharding/query/collation_targeting_inherited.js
@@ -208,11 +208,10 @@ assert.eq(1, explain.queryPlanner.winningPlan.shards.length);
if (WriteWithoutShardKeyTestUtil.isWriteWithoutShardKeyFeatureEnabled(testDB)) {
let res = collCaseInsensitive.findAndModify({query: {a: "foo"}, update: {$set: {b: 1}}});
assert(res.a === "foo" || res.a === "FOO");
-
- // TODO: SERVER-69925 Implement explain for findAndModify.
- // assert.throws(function() {
- // collCaseInsensitive.explain().findAndModify({query: {a: "foo"}, update: {$set: {b: 1}}});
- // });
+ explain =
+ collCaseInsensitive.explain().findAndModify({query: {a: "foo"}, update: {$set: {b: 1}}});
+ assert.commandWorked(explain);
+ assert.eq(1, explain.queryPlanner.winningPlan.shards.length);
} else {
// Sharded findAndModify on strings with non-simple collation inherited from the collection
// default should fail, because findAndModify must target a single shard.
@@ -340,7 +339,9 @@ if (WriteWithoutShardKeyTestUtil.isWriteWithoutShardKeyFeatureEnabled(testDB)) {
assert.eq(1, writeRes.nRemoved);
let afterNumDocsMatch = collCaseInsensitive.find({a: "foo"}).collation(caseInsensitive).count();
assert.eq(beforeNumDocsMatch - 1, afterNumDocsMatch);
- // TODO: SERVER-69924 Implement explain for deleteOne
+ explain = collCaseInsensitive.explain().remove({a: "foo"}, {justOne: true});
+ assert.commandWorked(explain);
+ assert.eq(1, explain.queryPlanner.winningPlan.shards.length);
// Re-insert documents for later test cases.
collCaseInsensitive.insert(a_foo);
@@ -451,7 +452,9 @@ assert.eq(1, explain.queryPlanner.winningPlan.shards.length);
if (WriteWithoutShardKeyTestUtil.isWriteWithoutShardKeyFeatureEnabled(testDB)) {
writeRes = assert.commandWorked(collCaseInsensitive.update({a: "foo"}, {$set: {b: 1}}));
assert.eq(1, writeRes.nMatched);
- // TODO: SERVER-69922 Implement explain for updateOne
+ explain = collCaseInsensitive.explain().update({a: "foo"}, {$set: {b: 1}});
+ assert.commandWorked(explain);
+ assert.eq(1, explain.queryPlanner.winningPlan.shards.length);
} else {
// A single (non-multi) update must be single-shard or an exact-ID query. A query is exact-ID if
// it
diff --git a/jstests/sharding/query/explain_cmd.js b/jstests/sharding/query/explain_cmd.js
index 544c426f546..249c7e30f42 100644
--- a/jstests/sharding/query/explain_cmd.js
+++ b/jstests/sharding/query/explain_cmd.js
@@ -135,15 +135,21 @@ assert.eq(explain.queryPlanner.winningPlan.shards.length, 1);
// Check that the upsert didn't actually happen.
assert.eq(0, collSharded.count({a: 10}));
+// Sharded updateOne that does not target a single shard can now be executed with a two phase
+// write protocol that will target at most 1 matching document.
if (WriteWithoutShardKeyTestUtil.isWriteWithoutShardKeyFeatureEnabled(collSharded.getDB())) {
// Explain an upsert operation which cannot be targeted and verify that it is successful.
- // TODO SERVER-69922: Verify expected response.
explain = db.runCommand({
explain: {update: collSharded.getName(), updates: [{q: {b: 10}, u: {b: 10}, upsert: true}]},
verbosity: "allPlansExecution"
});
- assert.commandWorked(explain, tojson(explain));
- assert.eq(explain.queryPlanner.winningPlan.shards.length, 2);
+ assert(explain.queryPlanner);
+ assert(explain.executionStats);
+ assert.eq(explain.queryPlanner.winningPlan.stage, "SHARD_WRITE");
+ assert.eq(explain.queryPlanner.winningPlan.inputStage.winningPlan.stage, "SHARD_MERGE");
+ assert.eq(explain.executionStats.executionStages.stage, "SHARD_WRITE");
+ assert.eq(explain.executionStats.inputStage.executionStages.stage, "SHARD_MERGE");
+
// Check that the upsert didn't actually happen.
assert.eq(0, collSharded.count({b: 10}));
} else {
diff --git a/jstests/sharding/query/explain_find_and_modify_sharded.js b/jstests/sharding/query/explain_find_and_modify_sharded.js
index 65f5dc17d6c..29dab239f5e 100644
--- a/jstests/sharding/query/explain_find_and_modify_sharded.js
+++ b/jstests/sharding/query/explain_find_and_modify_sharded.js
@@ -5,6 +5,8 @@
(function() {
'use strict';
+load("jstests/sharding/updateOne_without_shard_key/libs/write_without_shard_key_test_util.js");
+
var collName = 'explain_find_and_modify';
// Create a cluster with 2 shards.
@@ -37,21 +39,53 @@ assert.commandWorked(testDB.adminCommand(
var res;
-// Queries that do not involve the shard key are invalid.
-res = testDB.runCommand(
- {explain: {findAndModify: collName, query: {b: 1}, remove: true}, verbosity: 'queryPlanner'});
-assert.commandFailed(res);
+// Sharded updateOne that does not target a single shard can now be executed with a two phase
+// write protocol that will target at most 1 matching document.
+if (WriteWithoutShardKeyTestUtil.isWriteWithoutShardKeyFeatureEnabled(testDB)) {
+ res = assert.commandWorked(testDB.runCommand({
+ explain: {findAndModify: collName, query: {b: 1}, remove: true},
+ verbosity: 'queryPlanner'
+ }));
-// Queries that have non-equality queries on the shard key are invalid.
-res = testDB.runCommand({
- explain: {
- findAndModify: collName,
- query: {a: {$gt: 5}},
- update: {$inc: {b: 7}},
- },
- verbosity: 'allPlansExecution'
-});
-assert.commandFailed(res);
+ assert(res.queryPlanner);
+ assert(!res.executionStats);
+ assert.eq(res.queryPlanner.winningPlan.stage, "SHARD_WRITE");
+ assert.eq(res.queryPlanner.winningPlan.inputStage.winningPlan.stage, "SHARD_MERGE");
+
+ res = assert.commandWorked(testDB.runCommand({
+ explain: {
+ findAndModify: collName,
+ query: {a: {$gt: 5}},
+ update: {$inc: {b: 7}},
+ },
+ verbosity: 'allPlansExecution'
+ }));
+
+ assert(res.queryPlanner);
+ assert(res.executionStats);
+ assert.eq(res.queryPlanner.winningPlan.stage, "SHARD_WRITE");
+ assert.eq(res.queryPlanner.winningPlan.inputStage.winningPlan.stage, "SHARD_MERGE");
+ assert.eq(res.executionStats.executionStages.stage, "SHARD_WRITE");
+ assert.eq(res.executionStats.inputStage.executionStages.stage, "SHARD_MERGE");
+} else {
+ // Queries that do not involve the shard key are invalid.
+ res = testDB.runCommand({
+ explain: {findAndModify: collName, query: {b: 1}, remove: true},
+ verbosity: 'queryPlanner'
+ });
+ assert.commandFailed(res);
+
+ // Queries that have non-equality queries on the shard key are invalid.
+ res = testDB.runCommand({
+ explain: {
+ findAndModify: collName,
+ query: {a: {$gt: 5}},
+ update: {$inc: {b: 7}},
+ },
+ verbosity: 'allPlansExecution'
+ });
+ assert.commandFailed(res);
+}
// Asserts that the explain command ran on the specified shard and used the given stage
// for performing the findAndModify command.
diff --git a/jstests/sharding/updateOne_without_shard_key/explain.js b/jstests/sharding/updateOne_without_shard_key/explain.js
new file mode 100644
index 00000000000..1decea44604
--- /dev/null
+++ b/jstests/sharding/updateOne_without_shard_key/explain.js
@@ -0,0 +1,490 @@
+/**
+ * Test explain output for updateOne, deleteOne, and findAndModify without shard key.
+ *
+ * @tags: [
+ * requires_sharding,
+ * requires_fcv_71,
+ * featureFlagUpdateOneWithoutShardKey,
+ * ]
+ */
+
+(function() {
+"use strict";
+
+load("jstests/sharding/updateOne_without_shard_key/libs/write_without_shard_key_test_util.js");
+
+// 2 shards single node, 1 mongos, 1 config server 3-node.
+const st = new ShardingTest({});
+const dbName = "testDb";
+const collName = "testColl";
+const nss = dbName + "." + collName;
+const splitPoint = 0;
+const dbConn = st.s.getDB(dbName);
+const docsToInsert = [
+ {_id: 0, x: -2, y: 1, a: [1, 2, 3]},
+ {_id: 1, x: -1, y: 1, a: [1, 2, 3]},
+ {_id: 2, x: 1, y: 1, a: [1, 2, 3]},
+ {_id: 3, x: 2, y: 1, a: [1, 2, 3]}
+];
+
+// Sets up a 2 shard cluster using 'x' as a shard key where Shard 0 owns x <
+// splitPoint and Shard 1 splitPoint >= 0.
+WriteWithoutShardKeyTestUtil.setupShardedCollection(
+ st, nss, {x: 1}, [{x: splitPoint}], [{query: {x: splitPoint}, shard: st.shard1.shardName}]);
+
+assert.commandWorked(dbConn[collName].insert(docsToInsert));
+
+let listCollRes = assert.commandWorked(dbConn.runCommand({listCollections: 1}));
+// There should only be one collection created in this test.
+const usingClusteredIndex = listCollRes.cursor.firstBatch[0].options.clusteredIndex != null;
+
+let testCases = [
+ {
+ logMessage: "Running explain for findAndModify update with sort.",
+ hasSort: true,
+ opType: "UPDATE",
+ cmdObj: {
+ explain: {
+ findAndModify: collName,
+ query: {y: 1},
+ sort: {x: 1},
+ update: {$inc: {z: 1}},
+ }
+ },
+ },
+ {
+ logMessage: "Running explain for findAndModify update with sort and upsert: true.",
+ hasSort: true,
+ isUpsert: true,
+ opType: "UPDATE",
+ cmdObj: {
+ explain: {
+ findAndModify: collName,
+ query: {y: 5}, // Query matches no documents.
+ sort: {x: 1},
+ update: {$inc: {z: 1}},
+ upsert: true
+ }
+ },
+ },
+ {
+ logMessage: "Running explain for findAndModify update.",
+ opType: "UPDATE",
+ cmdObj: {
+ explain: {
+ findAndModify: collName,
+ query: {y: 1},
+ update: {$inc: {z: 1}},
+ }
+ },
+ },
+ {
+ logMessage: "Running explain for findAndModify update without sort and upsert: true.",
+ isUpsert: true,
+ opType: "UPDATE",
+ cmdObj: {
+ explain: {
+ findAndModify: collName,
+ query: {y: 5}, // Query matches no documents.
+ update: {$inc: {z: 1}},
+ upsert: true,
+ }
+ },
+ },
+ {
+ logMessage: "Running explain for findAndModify remove with sort.",
+ hasSort: true,
+ opType: "DELETE",
+ cmdObj: {
+ explain: {
+ findAndModify: collName,
+ query: {y: 1},
+ sort: {x: 1},
+ remove: true,
+ }
+ },
+ },
+ {
+ logMessage: "Running explain for findAndModify remove without sort.",
+ opType: "DELETE",
+ cmdObj: {
+ explain: {
+ findAndModify: collName,
+ query: {y: 1},
+ remove: true,
+ }
+ },
+ },
+ {
+ logMessage:
+ "Running explain for findAndModify remove with positional projection with sort.",
+ opType: "DELETE",
+ hasSort: true,
+ isPositionalProjection: true,
+ cmdObj: {
+ explain: {
+ findAndModify: collName,
+ query: {y: 1, a: 1},
+ fields: {'a.$': 1},
+ sort: {x: 1},
+ remove: true,
+ }
+ }
+ },
+ {
+ logMessage:
+ "Running explain for findAndModify remove with positional projection without sort.",
+ opType: "DELETE",
+ isPositionalProjection: true,
+ cmdObj: {
+ explain: {
+ findAndModify: collName,
+ query: {y: 1, a: 1},
+ fields: {'a.$': 1},
+ remove: true,
+ }
+ }
+ },
+ {
+ logMessage:
+ "Running explain for findAndModify update with positional projection with sort.",
+ opType: "UPDATE",
+ hasSort: true,
+ isPositionalProjection: true,
+ cmdObj: {
+ explain: {
+ findAndModify: collName,
+ query: {y: 1, a: 1},
+ sort: {x: 1},
+ fields: {'a.$': 1},
+ update: {$inc: {z: 1}},
+ }
+ }
+ },
+ {
+ logMessage:
+ "Running explain for findAndModify update with positional projection without sort.",
+ opType: "UPDATE",
+ isPositionalProjection: true,
+ cmdObj: {
+ explain: {
+ findAndModify: collName,
+ query: {y: 1, a: 1},
+ fields: {'a.$': 1},
+ update: {$inc: {z: 1}},
+ }
+ }
+ },
+ {
+ logMessage: "Running explain for findAndModify update with positional update with sort.",
+ opType: "UPDATE",
+ hasSort: true,
+ isPositionalUpdate: true,
+ cmdObj: {
+ explain: {
+ findAndModify: collName,
+ query: {y: 1, a: 1},
+ sort: {x: 1},
+ update: {$set: {"a.$": 3}},
+ }
+ }
+ },
+ {
+ logMessage: "Running explain for findAndModify update with positional update without sort.",
+ opType: "UPDATE",
+ isPositionalUpdate: true,
+ cmdObj: {
+ explain: {
+ findAndModify: collName,
+ query: {y: 1, a: 1},
+ update: {$set: {"a.$": 3}},
+ }
+ }
+ },
+ {
+ logMessage: "Running explain for updateOne.",
+ opType: "UPDATE",
+ cmdObj: {
+ explain: {
+ update: collName,
+ updates: [{
+ q: {y: 1},
+ u: {$set: {z: 1}},
+ multi: false,
+ upsert: false,
+ }],
+ },
+ },
+ },
+ {
+ logMessage: "Running explain for updateOne and upsert: true.",
+ isUpsert: true,
+ opType: "UPDATE",
+ cmdObj: {
+ explain: {
+ update: collName,
+ updates: [{
+ q: {y: 5},
+ u: {$set: {z: 1}},
+ multi: false,
+ upsert: true,
+ }], // Query matches no documents.
+ },
+ },
+ },
+ {
+ logMessage: "Running explain for updateOne and with positional update.",
+ opType: "UPDATE",
+ isPositionalUpdate: true,
+ cmdObj: {
+ explain: {
+ update: collName,
+ updates: [{
+ q: {y: 1, a: 1},
+ u: {$set: {"a.$": 3}},
+ multi: false,
+ upsert: false,
+ }],
+ },
+ },
+ },
+ {
+ logMessage: "Running explain for deleteOne.",
+ opType: "DELETE",
+ cmdObj: {
+ explain: {
+ delete: collName,
+ deletes: [{q: {y: 1}, limit: 1}],
+ },
+ },
+ },
+];
+
+function runTestCase(testCase) {
+ jsTestLog(testCase.logMessage + "\n" + tojson(testCase));
+
+ let verbosityLevels = ["queryPlanner", "executionStats", "allPlansExecution"];
+ verbosityLevels.forEach(verbosityLevel => {
+ jsTestLog("Running with verbosity level: " + verbosityLevel);
+ let explainCmdObj = Object.assign(testCase.cmdObj, {verbosity: verbosityLevel});
+ let res = assert.commandWorked(dbConn.runCommand(explainCmdObj));
+ validateResponse(res, testCase, verbosityLevel);
+ });
+}
+
+function validateResponse(res, testCase, verbosity) {
+ assert.eq(res.queryPlanner.winningPlan.stage, "SHARD_WRITE");
+
+ if (testCase.hasSort) {
+ assert.eq(res.queryPlanner.winningPlan.inputStage.winningPlan.stage, "SHARD_MERGE_SORT");
+ } else {
+ assert.eq(res.queryPlanner.winningPlan.inputStage.winningPlan.stage, "SHARD_MERGE");
+ }
+
+ if (testCase.isPositionalProjection) {
+ assert.eq(res.queryPlanner.winningPlan.shards[0].winningPlan.stage, "PROJECTION_DEFAULT");
+ assert.eq(res.queryPlanner.winningPlan.shards[0].winningPlan.inputStage.stage,
+ testCase.opType);
+ if (testCase.hasSort) {
+ assert.eq(res.queryPlanner.winningPlan.shards[0]
+ .winningPlan.inputStage.inputStage.inputStage.stage,
+ "FETCH");
+ assert.eq(res.queryPlanner.winningPlan.shards[0]
+ .winningPlan.inputStage.inputStage.inputStage.inputStage.stage,
+ "IXSCAN");
+ } else {
+ if (usingClusteredIndex) {
+ assert.eq(
+ res.queryPlanner.winningPlan.shards[0].winningPlan.inputStage.inputStage.stage,
+ "CLUSTERED_IXSCAN");
+ } else {
+ assert.eq(
+ res.queryPlanner.winningPlan.shards[0].winningPlan.inputStage.inputStage.stage,
+ "FETCH",
+ res);
+ assert.eq(res.queryPlanner.winningPlan.shards[0]
+ .winningPlan.inputStage.inputStage.inputStage.stage,
+ "IXSCAN");
+ }
+ }
+ } else if (testCase.isPositionalUpdate) {
+ assert.eq(res.queryPlanner.winningPlan.shards[0].winningPlan.stage, testCase.opType);
+ if (testCase.hasSort) {
+ assert.eq(
+ res.queryPlanner.winningPlan.shards[0].winningPlan.inputStage.inputStage.stage,
+ "FETCH");
+ assert.eq(res.queryPlanner.winningPlan.shards[0]
+ .winningPlan.inputStage.inputStage.inputStage.stage,
+ "IXSCAN");
+ } else {
+ if (usingClusteredIndex) {
+ assert.eq(res.queryPlanner.winningPlan.shards[0].winningPlan.inputStage.stage,
+ "CLUSTERED_IXSCAN");
+ } else {
+ assert.eq(res.queryPlanner.winningPlan.shards[0].winningPlan.inputStage.stage,
+ "FETCH");
+ assert.eq(
+ res.queryPlanner.winningPlan.shards[0].winningPlan.inputStage.inputStage.stage,
+ "IXSCAN");
+ }
+ }
+ } else {
+ if (usingClusteredIndex) {
+ assert.eq(res.queryPlanner.winningPlan.shards[0].winningPlan.inputStage.stage,
+ "CLUSTERED_IXSCAN");
+ } else {
+ assert.eq(res.queryPlanner.winningPlan.shards[0].winningPlan.stage, testCase.opType);
+ assert.eq(res.queryPlanner.winningPlan.shards[0].winningPlan.inputStage.stage,
+ "IDHACK");
+ }
+ }
+
+ assert.eq(res.queryPlanner.winningPlan.shards.length,
+ 1); // Only 1 shard targeted by the write.
+ assert.eq(res.queryPlanner.winningPlan.inputStage.winningPlan.shards.length,
+ 2); // 2 shards had matching documents.
+
+ if (verbosity === "queryPlanner") {
+ assert.eq(res.executionStats, null);
+ } else {
+ assert.eq(res.executionStats.executionStages.stage, "SHARD_WRITE");
+ if (testCase.isPositionalProjection) {
+ assert.eq(res.executionStats.executionStages.shards[0].executionStages.stage,
+ "PROJECTION_DEFAULT");
+ assert.eq(res.executionStats.executionStages.shards[0].executionStages.inputStage.stage,
+ testCase.opType);
+ if (testCase.hasSort) {
+ assert.eq(res.executionStats.executionStages.shards[0]
+ .executionStages.inputStage.inputStage.inputStage.stage,
+ "FETCH");
+ assert.eq(res.executionStats.executionStages.shards[0]
+ .executionStages.inputStage.inputStage.inputStage.inputStage.stage,
+ "IXSCAN");
+ } else {
+ if (usingClusteredIndex) {
+ assert.eq(res.executionStats.executionStages.shards[0]
+ .executionStages.inputStage.inputStage.stage,
+ "CLUSTERED_IXSCAN");
+ } else {
+ assert.eq(res.executionStats.executionStages.shards[0]
+ .executionStages.inputStage.inputStage.stage,
+ "FETCH");
+ assert.eq(res.executionStats.executionStages.shards[0]
+ .executionStages.inputStage.inputStage.inputStage.stage,
+ "IXSCAN");
+ }
+ }
+ } else if (testCase.isPositionalUpdate) {
+ assert.eq(res.executionStats.executionStages.shards[0].executionStages.stage,
+ testCase.opType);
+ if (testCase.hasSort) {
+ assert.eq(res.executionStats.executionStages.shards[0]
+ .executionStages.inputStage.inputStage.stage,
+ "FETCH");
+ assert.eq(res.executionStats.executionStages.shards[0]
+ .executionStages.inputStage.inputStage.inputStage.stage,
+ "IXSCAN");
+ } else {
+ if (usingClusteredIndex) {
+ assert.eq(res.executionStats.executionStages.shards[0]
+ .executionStages.inputStage.stage,
+ "CLUSTERED_IXSCAN");
+ } else {
+ assert.eq(res.executionStats.executionStages.shards[0]
+ .executionStages.inputStage.stage,
+ "FETCH");
+ assert.eq(res.executionStats.executionStages.shards[0]
+ .executionStages.inputStage.inputStage.stage,
+ "IXSCAN");
+ }
+ }
+ } else {
+ if (usingClusteredIndex) {
+ assert.eq(
+ res.executionStats.executionStages.shards[0].executionStages.inputStage.stage,
+ "CLUSTERED_IXSCAN");
+
+ } else {
+ assert.eq(res.executionStats.executionStages.shards[0].executionStages.stage,
+ testCase.opType);
+ assert.eq(
+ res.executionStats.executionStages.shards[0].executionStages.inputStage.stage,
+ "IDHACK");
+ }
+ }
+ assert.eq(res.executionStats.executionStages.shards.length,
+ 1); // Only 1 shard targeted by the write.
+ assert.eq(res.executionStats.inputStage.executionStages.shards.length,
+ 2); // 2 shards had matching documents.
+
+ // We use a dummy _id target document for the Write Phase which should not match any
+ // existing documents in the collection. This will at least preserve the query plan,
+ // but may lead to incorrect executionStats.
+ if (testCase.isUpsert) {
+ assert.eq(res.executionStats.nReturned, 0);
+ assert.eq(res.executionStats.executionStages.shards[0].executionStages.nWouldModify, 0);
+ assert.eq(res.executionStats.executionStages.shards[0].executionStages.nWouldUpsert, 1);
+ assert.eq(res.executionStats.inputStage.nReturned, 0);
+ } else {
+ // TODO SERVER-29449: Properly report explain results for sharded queries with a
+ // limit. assert.eq(res.executionStats.nReturned, 1);
+ if (testCase.opType === "DELETE") {
+ // We use a dummy _id target document for the Write Phase which should not match any
+ // existing documents in the collection. This will at least preserve the query plan,
+ // but may lead to incorrect executionStats.
+ if (testCase.isPositionalProjection) {
+ assert.eq(res.executionStats.executionStages.shards[0]
+ .executionStages.inputStage.nWouldDelete,
+ 0);
+ } else {
+ assert.eq(
+ res.executionStats.executionStages.shards[0].executionStages.nWouldDelete,
+ 0);
+ }
+ } else {
+ // We use a dummy _id target document for the Write Phase which should not match any
+ // existing documents in the collection. This will at least preserve the query plan,
+ // but may lead to incorrect executionStats.
+ if (testCase.isPositionalProjection) {
+ assert.eq(res.executionStats.executionStages.shards[0]
+ .executionStages.inputStage.nWouldModify,
+ 0);
+ assert.eq(res.executionStats.executionStages.shards[0]
+ .executionStages.inputStage.nWouldUpsert,
+ 0);
+ } else {
+ assert.eq(
+ res.executionStats.executionStages.shards[0].executionStages.nWouldModify,
+ 0);
+ assert.eq(
+ res.executionStats.executionStages.shards[0].executionStages.nWouldUpsert,
+ 0);
+ }
+ }
+ assert.eq(res.executionStats.inputStage.nReturned, 2);
+ }
+
+ if (testCase.hasSort) {
+ assert.eq(res.executionStats.inputStage.executionStages.stage, "SHARD_MERGE_SORT");
+ } else {
+ assert.eq(res.executionStats.inputStage.executionStages.stage, "SHARD_MERGE");
+ }
+ }
+
+ assert(res.serverInfo);
+ assert(res.serverParameters);
+ assert(res.command);
+
+ // Checks that 'command' field of the explain output is the same command that we originally
+ // wanted to explain.
+ for (const [key, value] of Object.entries(testCase.cmdObj.explain)) {
+ assert.eq(res.command[key], value);
+ }
+}
+
+testCases.forEach(testCase => {
+ runTestCase(testCase);
+});
+
+st.stop();
+})();