mongo/jstests/core/explain_shell_helpers.js

448 lines
14 KiB
JavaScript

/**
* Cannot implicitly shard accessed collections because the explain output from a mongod when run
* against a sharded collection is wrapped in a "shards" object with keys for each shard.
*
* @tags: [assumes_unsharded_collection,
* does_not_support_stepdowns,
* requires_fastcount,
* # Projection push down works differently between 4.2 and 4.4. explain() provides a
* # different output between the two versions. This test expects only the 4.4 version
* # output.
* requires_fcv_44]
*/
// Tests for the .explain() shell helper, which provides syntactic sugar for the explain command.
var t = db.jstests_explain_helpers;
t.drop();
// Include helpers for analyzing explain output.
load("jstests/libs/analyze_plan.js");
var explain;
var stage;
t.ensureIndex({a: 1});
for (var i = 0; i < 10; i++) {
t.insert({_id: i, a: i, b: 1});
}
//
// Basic .find()
//
// No verbosity specified means that we should use "queryPlanner" verbosity.
explain = t.explain().find().finish();
assert.commandWorked(explain);
assert("queryPlanner" in explain);
assert(!("executionStats" in explain));
// .explain() can also come after .find().
explain = t.find().explain();
assert.commandWorked(explain);
assert("queryPlanner" in explain);
assert(!("executionStats" in explain));
// .explain(true) means get execution stats for all candidate plans.
explain = t.explain(true).find().finish();
assert.commandWorked(explain);
assert("queryPlanner" in explain);
assert("executionStats" in explain);
assert("allPlansExecution" in explain.executionStats);
// .explain(true) after .find().
explain = t.find().explain(true);
assert.commandWorked(explain);
assert("queryPlanner" in explain);
assert("executionStats" in explain);
assert("allPlansExecution" in explain.executionStats);
//
// Test verbosity specifiers.
//
// "queryPlanner"
explain = t.explain("queryPlanner").find().finish();
assert.commandWorked(explain);
assert("queryPlanner" in explain);
assert(!("executionStats" in explain));
explain = t.find().explain("queryPlanner");
assert.commandWorked(explain);
assert("queryPlanner" in explain);
assert(!("executionStats" in explain));
// "executionStats"
explain = t.explain("executionStats").find().finish();
assert.commandWorked(explain);
assert("queryPlanner" in explain);
assert("executionStats" in explain);
assert(!("allPlansExecution" in explain.executionStats));
explain = t.find().explain("executionStats");
assert.commandWorked(explain);
assert("queryPlanner" in explain);
assert("executionStats" in explain);
assert(!("allPlansExecution" in explain.executionStats));
// "allPlansExecution"
explain = t.explain("allPlansExecution").find().finish();
assert.commandWorked(explain);
assert("queryPlanner" in explain);
assert("executionStats" in explain);
assert("allPlansExecution" in explain.executionStats);
explain = t.find().explain("allPlansExecution");
assert.commandWorked(explain);
assert("queryPlanner" in explain);
assert("executionStats" in explain);
assert("allPlansExecution" in explain.executionStats);
//
// Tests for DBExplainQuery helpers.
//
// .limit()
explain = t.explain().find().limit(3).finish();
assert.commandWorked(explain);
explain = t.find().limit(3).explain();
assert.commandWorked(explain);
// .batchSize()
explain = t.explain().find().batchSize(3).finish();
assert.commandWorked(explain);
explain = t.find().batchSize(3).explain();
assert.commandWorked(explain);
// .addOption()
explain = t.explain().find().addOption(DBQuery.Option.noTimeout).finish();
assert.commandWorked(explain);
explain = t.find().batchSize(DBQuery.Option.noTimeout).explain();
assert.commandWorked(explain);
// .skip()
explain = t.explain().find().skip(3).finish();
assert.commandWorked(explain);
explain = t.find().skip(3).explain();
assert.commandWorked(explain);
// .sort()
explain = t.explain().find().sort({b: -1}).finish();
assert.commandWorked(explain);
assert(planHasStage(db, explain.queryPlanner.winningPlan, "SORT"));
explain = t.find().sort({b: -1}).explain();
assert.commandWorked(explain);
assert(planHasStage(db, explain.queryPlanner.winningPlan, "SORT"));
// .hint()
explain = t.explain().find().hint({a: 1}).finish();
assert.commandWorked(explain);
assert(isIxscan(db, explain.queryPlanner.winningPlan));
explain = t.explain().find().hint("a_1").finish();
assert.commandWorked(explain);
assert(isIxscan(db, explain.queryPlanner.winningPlan));
explain = t.find().hint({a: 1}).explain();
assert.commandWorked(explain);
assert(isIxscan(db, explain.queryPlanner.winningPlan));
explain = t.find().hint("a_1").explain();
assert.commandWorked(explain);
assert(isIxscan(db, explain.queryPlanner.winningPlan));
// .min()
explain = t.explain().find().min({a: 1}).hint({a: 1}).finish();
assert.commandWorked(explain);
assert(isIxscan(db, explain.queryPlanner.winningPlan));
explain = t.find().min({a: 1}).hint({a: 1}).explain();
assert.commandWorked(explain);
assert(isIxscan(db, explain.queryPlanner.winningPlan));
// .max()
explain = t.explain().find().max({a: 1}).hint({a: 1}).finish();
assert.commandWorked(explain);
assert(isIxscan(db, explain.queryPlanner.winningPlan));
explain = t.find().max({a: 1}).hint({a: 1}).explain();
assert.commandWorked(explain);
assert(isIxscan(db, explain.queryPlanner.winningPlan));
// .allowDiskUse()
explain = t.explain().find().allowDiskUse().finish();
assert.commandWorked(explain);
explain = t.find().allowDiskUse().explain();
assert.commandWorked(explain);
// .showDiskLoc()
explain = t.explain().find().showDiskLoc().finish();
assert.commandWorked(explain);
explain = t.find().showDiskLoc().explain();
assert.commandWorked(explain);
// .maxTimeMS()
// Provide longer maxTime when the test runs in suites which can affect query execution time.
const numConn = db.serverStatus().connections.current;
explain = t.explain().find().maxTimeMS(200 * numConn).finish();
assert.commandWorked(explain);
explain = t.find().maxTimeMS(200 * numConn).explain();
assert.commandWorked(explain);
// .readPref()
explain = t.explain().find().readPref("secondary").finish();
assert.commandWorked(explain);
explain = t.find().readPref("secondary").explain();
assert.commandWorked(explain);
// .comment()
explain = t.explain().find().comment("test .comment").finish();
assert.commandWorked(explain);
explain = t.find().comment("test .comment").explain();
assert.commandWorked(explain);
// .next()
explain = t.explain().find().next();
assert.commandWorked(explain);
assert("queryPlanner" in explain);
// .hasNext()
var explainQuery = t.explain().find();
assert(explainQuery.hasNext());
assert.commandWorked(explainQuery.next());
assert(!explainQuery.hasNext());
// .forEach()
var results = [];
t.explain().find().forEach(function(res) {
results.push(res);
});
assert.eq(1, results.length);
assert.commandWorked(results[0]);
//
// .aggregate()
//
explain = t.explain().aggregate([{$match: {a: 3}}, {$group: {_id: null}}]);
assert.commandWorked(explain);
assert.eq(2, explain.stages.length);
assert("queryPlanner" in explain.stages[0].$cursor);
// Legacy varargs format.
explain = t.explain().aggregate({$group: {_id: null}});
assert.commandWorked(explain);
assert.eq(2, explain.stages.length);
assert("queryPlanner" in explain.stages[0].$cursor);
explain = t.explain().aggregate({$project: {a: 3}}, {$group: {_id: null}});
assert.commandWorked(explain);
assert.eq(2, explain.stages.length);
assert("queryPlanner" in explain.stages[0].$cursor);
// Options already provided.
explain = t.explain().aggregate([{$match: {a: 3}}, {$group: {_id: null}}], {allowDiskUse: true});
assert.commandWorked(explain);
assert.eq(2, explain.stages.length);
assert("queryPlanner" in explain.stages[0].$cursor);
//
// .count()
//
// Basic count.
explain = t.explain().count();
assert.commandWorked(explain);
assert(planHasStage(db, explain.queryPlanner.winningPlan, "RECORD_STORE_FAST_COUNT"));
// Tests for applySkipLimit argument to .count. When we don't apply the skip, we
// count one result. When we do apply the skip we count zero.
explain = t.explain("executionStats").find({a: 3}).skip(1).count(false);
stage = explain.executionStats.executionStages;
if ("SINGLE_SHARD" === stage.stage) {
stage = stage.shards[0].executionStages;
}
assert.eq(1, stage.nCounted);
explain = t.explain("executionStats").find({a: 3}).skip(1).count(true);
stage = explain.executionStats.executionStages;
if ("SINGLE_SHARD" === stage.stage) {
stage = stage.shards[0].executionStages;
}
assert.eq(0, stage.nCounted);
// Count with hint.
explain = t.explain().find({a: 3}).hint({a: 1}).count();
assert.commandWorked(explain);
assert(planHasStage(db, explain.queryPlanner.winningPlan, "COUNT"));
assert(planHasStage(db, explain.queryPlanner.winningPlan, "COUNT_SCAN"));
// Explainable count with hint.
assert.commandWorked(t.ensureIndex({c: 1}, {sparse: true}));
explain = t.explain().count({c: {$exists: false}}, {hint: "c_1"});
assert.commandWorked(explain);
assert(planHasStage(db, explain.queryPlanner.winningPlan, "IXSCAN"));
assert.eq(getPlanStage(explain.queryPlanner.winningPlan, "IXSCAN").indexName, "c_1");
assert.commandWorked(t.dropIndex({c: 1}));
//
// .distinct()
//
explain = t.explain().distinct('_id');
assert.commandWorked(explain);
assert(planHasStage(db, explain.queryPlanner.winningPlan, "PROJECTION_COVERED"));
assert(planHasStage(db, explain.queryPlanner.winningPlan, "DISTINCT_SCAN"));
explain = t.explain().distinct('a');
assert.commandWorked(explain);
assert(planHasStage(db, explain.queryPlanner.winningPlan, "PROJECTION_COVERED"));
assert(planHasStage(db, explain.queryPlanner.winningPlan, "DISTINCT_SCAN"));
explain = t.explain().distinct('b');
assert.commandWorked(explain);
assert(planHasStage(db, explain.queryPlanner.winningPlan, "COLLSCAN"));
//
// .remove()
//
// Check that there is one matching document.
assert.eq(1, t.find({a: 3}).itcount());
// Explain a single-document delete.
explain = t.explain("executionStats").remove({a: 3}, true);
assert.commandWorked(explain);
assert.eq(1, explain.executionStats.totalDocsExamined);
// Document should not have been deleted.
assert.eq(1, t.find({a: 3}).itcount());
// Explain a single-document delete with the new syntax.
explain = t.explain("executionStats").remove({a: 3}, {justOne: true});
assert.commandWorked(explain);
assert.eq(1, explain.executionStats.totalDocsExamined);
// Document should not have been deleted.
assert.eq(1, t.find({a: 3}).itcount());
// Explain a multi-document delete.
explain = t.explain("executionStats").remove({a: {$lte: 2}});
assert.commandWorked(explain);
assert.eq(3, explain.executionStats.totalDocsExamined);
// All 10 docs in the collection should still be present.
assert.eq(10, t.count());
//
// .update()
//
// Basic update.
explain = t.explain("executionStats").update({a: 3}, {$set: {b: 3}});
assert.commandWorked(explain);
assert.eq(1, explain.executionStats.totalDocsExamined);
// Document should not have been updated.
assert.eq(1, t.findOne({a: 3})["b"]);
// Update with upsert flag set that should do an insert.
explain = t.explain("executionStats").update({a: 15}, {$set: {b: 3}}, true);
assert.commandWorked(explain);
stage = explain.executionStats.executionStages;
if ("SHARD_WRITE" === stage.stage) {
stage = stage.shards[0].executionStages;
}
assert.eq(stage.stage, "UPDATE");
assert(stage.wouldInsert);
// Make sure that the insert didn't actually happen.
assert.eq(10, t.count());
// Use the {upsert: true} syntax.
explain = t.explain("executionStats").update({a: 15}, {$set: {b: 3}}, {upsert: true});
assert.commandWorked(explain);
var stage = explain.executionStats.executionStages;
if ("SHARD_WRITE" === stage.stage) {
stage = stage.shards[0].executionStages;
}
assert.eq(stage.stage, "UPDATE");
assert(stage.wouldInsert);
assert.eq(0, stage.nMatched);
// Make sure that the insert didn't actually happen.
assert.eq(10, t.count());
// Update with multi-update flag set.
explain = t.explain("executionStats").update({a: {$lte: 2}}, {$set: {b: 3}}, false, true);
assert.commandWorked(explain);
var stage = explain.executionStats.executionStages;
if ("SHARD_WRITE" === stage.stage) {
stage = stage.shards[0].executionStages;
}
assert.eq(stage.stage, "UPDATE");
assert(!stage.wouldInsert);
assert.eq(3, stage.nMatched);
assert.eq(3, stage.nWouldModify);
// Use the {multi: true} syntax.
explain = t.explain("executionStats").update({a: {$lte: 2}}, {$set: {b: 3}}, {multi: true});
assert.commandWorked(explain);
var stage = explain.executionStats.executionStages;
if ("SHARD_WRITE" === stage.stage) {
stage = stage.shards[0].executionStages;
}
assert.eq(stage.stage, "UPDATE");
assert(!stage.wouldInsert);
assert.eq(3, stage.nMatched);
assert.eq(3, stage.nWouldModify);
//
// .findAndModify()
//
// Basic findAndModify with update.
explain = t.explain("executionStats").findAndModify({query: {a: 3}, update: {$set: {b: 3}}});
assert.commandWorked(explain);
assert.eq(1, explain.executionStats.totalDocsExamined);
// Document should not have been updated.
assert.eq(1, t.findOne({a: 3})["b"]);
// Basic findAndModify with delete.
explain = t.explain("executionStats").findAndModify({query: {a: 3}, remove: true});
assert.commandWorked(explain);
assert.eq(1, explain.executionStats.totalDocsExamined);
// Delete shouldn't have happened.
assert.eq(10, t.count());
// findAndModify with upsert flag set that should do an insert.
explain = t.explain("executionStats")
.findAndModify({query: {a: 15}, update: {$set: {b: 3}}, upsert: true});
assert.commandWorked(explain);
stage = explain.executionStats.executionStages;
if ("SINGLE_SHARD" === stage.stage) {
stage = stage.shards[0].executionStages;
}
assert.eq(stage.stage, "UPDATE");
assert(stage.wouldInsert);
// Make sure that the insert didn't actually happen.
assert.eq(10, t.count());
//
// Error cases.
//
// Can't explain an update without a query.
assert.throws(function() {
t.explain().update();
});
// Can't explain an update without mods.
assert.throws(function() {
t.explain().update({a: 3});
});
// Can't add fourth arg when using document-style specification of update options.
assert.throws(function() {
t.explain().update({a: 3}, {$set: {b: 4}}, {multi: true}, true);
});
// Can't specify both remove and update in a findAndModify
assert.throws(function() {
t.explain().findAndModify({remove: true, update: {$set: {b: 3}}});
});