mirror of https://github.com/mongodb/mongo
448 lines
14 KiB
JavaScript
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}}});
|
|
});
|