summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Storch <david.storch@10gen.com>2014-10-07 18:23:21 -0400
committerDavid Storch <david.storch@10gen.com>2014-10-13 19:59:21 -0400
commitd601b91b6b16be3f93bac2f10952c1e5d273f91f (patch)
treef8b7daf9d3920ded5567489d0d65df50afed9542
parente7a49e50e5a858b02c9c242c943d7559238bb2b6 (diff)
downloadmongo-d601b91b6b16be3f93bac2f10952c1e5d273f91f.tar.gz
SERVER-14875 explain helpers for the shell
-rw-r--r--jstests/auth/repl_auth.js8
-rw-r--r--jstests/core/and3.js2
-rw-r--r--jstests/core/batch_size.js6
-rw-r--r--jstests/core/covered_index_compound_1.js26
-rw-r--r--jstests/core/covered_index_negative_1.js16
-rw-r--r--jstests/core/covered_index_simple_1.js14
-rw-r--r--jstests/core/covered_index_simple_2.js12
-rw-r--r--jstests/core/covered_index_simple_3.js16
-rw-r--r--jstests/core/covered_index_simple_id.js12
-rw-r--r--jstests/core/covered_index_sort_1.js8
-rw-r--r--jstests/core/covered_index_sort_2.js2
-rw-r--r--jstests/core/covered_index_sort_3.js4
-rw-r--r--jstests/core/distinct_speed1.js2
-rw-r--r--jstests/core/explain1.js8
-rw-r--r--jstests/core/explain5.js8
-rw-r--r--jstests/core/explain_batch_size.js2
-rw-r--r--jstests/core/explain_shell_helpers.js389
-rw-r--r--jstests/core/fts_explain.js3
-rw-r--r--jstests/core/geo_2d_explain.js2
-rw-r--r--jstests/core/geo_center_sphere1.js2
-rw-r--r--jstests/core/geo_circle1.js2
-rw-r--r--jstests/core/geo_s2nearComplex.js14
-rw-r--r--jstests/core/geo_s2ordering.js4
-rw-r--r--jstests/core/geo_s2twofields.js4
-rw-r--r--jstests/core/index_check2.js6
-rw-r--r--jstests/core/index_check3.js10
-rw-r--r--jstests/core/index_check6.js9
-rw-r--r--jstests/core/index_check7.js8
-rw-r--r--jstests/core/index_elemmatch1.js2
-rw-r--r--jstests/core/indexj.js2
-rw-r--r--jstests/core/indexv.js4
-rw-r--r--jstests/core/mod1.js4
-rw-r--r--jstests/core/mr_index.js2
-rw-r--r--jstests/core/ne2.js2
-rw-r--r--jstests/core/regex3.js6
-rw-r--r--jstests/core/regex4.js4
-rw-r--r--jstests/core/regex6.js22
-rw-r--r--jstests/core/sortk.js2
-rw-r--r--jstests/libs/analyze_plan.js7
-rw-r--r--jstests/noPassthrough/indexbg1.js2
-rw-r--r--jstests/noPassthroughWithMongod/clonecollection.js2
-rw-r--r--jstests/sharding/auth_slaveok_routing.js4
-rw-r--r--jstests/sharding/covered_shard_key_indexes.js70
-rw-r--r--jstests/sharding/large_skip_one_shard.js20
-rw-r--r--jstests/sharding/limit_push.js10
-rwxr-xr-xjstests/sharding/read_pref.js37
-rw-r--r--jstests/sharding/shard2.js5
-rw-r--r--jstests/sharding/shard3.js8
-rw-r--r--src/mongo/SConscript2
-rw-r--r--src/mongo/client/dbclient_rs.cpp2
-rw-r--r--src/mongo/db/query/lite_parsed_query.cpp5
-rw-r--r--src/mongo/db/query/lite_parsed_query_test.cpp2
-rw-r--r--src/mongo/s/cluster_explain.cpp7
-rw-r--r--src/mongo/s/commands/cluster_find_cmd.cpp2
-rw-r--r--src/mongo/scripting/engine.cpp4
-rw-r--r--src/mongo/shell/bulk_api.js34
-rw-r--r--src/mongo/shell/collection.js90
-rw-r--r--src/mongo/shell/explain_query.js222
-rw-r--r--src/mongo/shell/explainable.js199
-rw-r--r--src/mongo/shell/mongo.js12
-rw-r--r--src/mongo/shell/query.js175
-rw-r--r--src/mongo/shell/shardingtest.js18
62 files changed, 1320 insertions, 268 deletions
diff --git a/jstests/auth/repl_auth.js b/jstests/auth/repl_auth.js
index f2e6739be1e..a5bde8167c9 100644
--- a/jstests/auth/repl_auth.js
+++ b/jstests/auth/repl_auth.js
@@ -49,18 +49,18 @@ rsTest.getSecondaries().forEach(function(sec) {
// pool if a different secondary is selected from the previous one so we have to iterate
// a couple of times.
for (var x = 0; x < 20; x++) {
- var explain = fooDB0.user.find().readPref('secondary').explain();
+ var explain = fooDB0.user.find().readPref('secondary').explain('executionStats');
assert.eq(1, explain.executionStats.nReturned);
assert.throws(function() {
- explain = barDB0.user.find().readPref('secondary').explain();
+ explain = barDB0.user.find().readPref('secondary').explain('executionStats');
});
assert.throws(function() {
- explain = fooDB1.user.find().readPref('secondary').explain();
+ explain = fooDB1.user.find().readPref('secondary').explain('executionStats');
});
- explain = barDB1.user.find().readPref('secondary').explain();
+ explain = barDB1.user.find().readPref('secondary').explain('executionStats');
assert.eq(1, explain.executionStats.nReturned);
}
diff --git a/jstests/core/and3.js b/jstests/core/and3.js
index 3f223265522..a0a779937b1 100644
--- a/jstests/core/and3.js
+++ b/jstests/core/and3.js
@@ -9,7 +9,7 @@ t.save( {a:'foo'} );
t.ensureIndex( {a:1} );
function checkScanMatch( query, docsExamined, n ) {
- var e = t.find( query ).hint( {a:1} ).explain();
+ var e = t.find( query ).hint( {a:1} ).explain( "executionStats" );
assert.eq( docsExamined, e.executionStats.totalDocsExamined );
assert.eq( n, e.executionStats.nReturned );
}
diff --git a/jstests/core/batch_size.js b/jstests/core/batch_size.js
index 645ee0031ab..389c9903eba 100644
--- a/jstests/core/batch_size.js
+++ b/jstests/core/batch_size.js
@@ -63,15 +63,15 @@ assert.eq(15, t.find({a: {$gte: 85}}).sort({b: 1}).hint({b: 1}).batchSize(2).itc
assert.eq(6, t.find({a: {$gte: 85}}).sort({b: 1}).hint({b: 1}).limit(6).itcount(), 'P');
// With explain.
-var explain = t.find({a: {$gte: 85}}).sort({b: 1}).batchSize(2).explain();
+var explain = t.find({a: {$gte: 85}}).sort({b: 1}).batchSize(2).explain("executionStats");
assert.eq(15, explain.executionStats.nReturned, 'Q');
-explain = t.find({a: {$gte: 85}}).sort({b: 1}).limit(6).explain();
+explain = t.find({a: {$gte: 85}}).sort({b: 1}).limit(6).explain("executionStats");
assert.eq(6, explain.executionStats.nReturned, 'R');
// Double check that we're not scanning more stuff than we have to.
// In order to get the sort using index 'a', we should need to scan
// about 50 keys and 50 documents.
-var explain = t.find({a: {$gte: 50}}).sort({b: 1}).hint({a: 1}).limit(6).explain();
+var explain = t.find({a: {$gte: 50}}).sort({b: 1}).hint({a: 1}).limit(6).explain("executionStats");
assert.lte(explain.executionStats.totalKeysExamined, 60, 'S');
assert.lte(explain.executionStats.totalDocsExamined, 60, 'T');
assert.eq(explain.executionStats.nReturned, 6, 'U');
diff --git a/jstests/core/covered_index_compound_1.js b/jstests/core/covered_index_compound_1.js
index 632a2330b44..9090f3168cd 100644
--- a/jstests/core/covered_index_compound_1.js
+++ b/jstests/core/covered_index_compound_1.js
@@ -11,49 +11,61 @@ for (i=0;i<100;i++) {
coll.ensureIndex({a:1,b:-1,c:1})
// Test equality - all indexed fields queried and projected
-var plan = coll.find({a:10, b:"strvar_10", c:0}, {a:1, b:1, c:1, _id:0}).hint({a:1, b:-1, c:1}).explain()
+var plan = coll.find({a:10, b:"strvar_10", c:0}, {a:1, b:1, c:1, _id:0})
+ .hint({a:1, b:-1, c:1})
+ .explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"compound.1.1 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
"compound.1.1 - nscannedObjects should be 0 for covered query")
// Test query on subset of fields queried and project all
-var plan = coll.find({a:26, b:"strvar_0"}, {a:1, b:1, c:1, _id:0}).hint({a:1, b:-1, c:1}).explain()
+var plan = coll.find({a:26, b:"strvar_0"}, {a:1, b:1, c:1, _id:0})
+ .hint({a:1, b:-1, c:1})
+ .explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"compound.1.2 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
"compound.1.2 - nscannedObjects should be 0 for covered query")
// Test query on all fields queried and project subset
-var plan = coll.find({a:38, b:"strvar_12", c: 8}, {b:1, c:1, _id:0}).hint({a:1, b:-1, c:1}).explain()
+var plan = coll.find({a:38, b:"strvar_12", c: 8}, {b:1, c:1, _id:0})
+ .hint({a:1, b:-1, c:1})
+ .explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"compound.1.3 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
"compound.1.3 - nscannedObjects should be 0 for covered query")
// Test no query
-var plan = coll.find({}, {b:1, c:1, _id:0}).hint({a:1, b:-1, c:1}).explain()
+var plan = coll.find({}, {b:1, c:1, _id:0}).hint({a:1, b:-1, c:1}).explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"compound.1.4 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
"compound.1.4 - nscannedObjects should be 0 for covered query")
// Test range query
-var plan = coll.find({a:{$gt:25,$lt:43}}, {b:1, c:1, _id:0}).hint({a:1, b:-1, c:1}).explain()
+var plan = coll.find({a:{$gt:25,$lt:43}}, {b:1, c:1, _id:0})
+ .hint({a:1, b:-1, c:1})
+ .explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"compound.1.5 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
"compound.1.5 - nscannedObjects should be 0 for covered query")
// Test in query
-var plan = coll.find({a:38, b:"strvar_12", c:{$in:[5,8]}}, {b:1, c:1, _id:0}).hint({a:1, b:-1, c:1}).explain()
+var plan = coll.find({a:38, b:"strvar_12", c:{$in:[5,8]}}, {b:1, c:1, _id:0})
+ .hint({a:1, b:-1, c:1})
+ .explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"compound.1.6 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
"compound.1.6 - nscannedObjects should be 0 for covered query")
// Test no result
-var plan = coll.find({a:38, b:"strvar_12", c:55},{a:1, b:1, c:1, _id:0}).hint({a:1, b:-1, c:1}).explain()
+var plan = coll.find({a:38, b:"strvar_12", c:55},{a:1, b:1, c:1, _id:0})
+ .hint({a:1, b:-1, c:1})
+ .explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"compound.1.7 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
diff --git a/jstests/core/covered_index_negative_1.js b/jstests/core/covered_index_negative_1.js
index 4b538b7c275..b2ab81b37cc 100644
--- a/jstests/core/covered_index_negative_1.js
+++ b/jstests/core/covered_index_negative_1.js
@@ -18,28 +18,30 @@ coll.ensureIndex({d:1})
coll.ensureIndex({f:"hashed"})
// Test no projection
-var plan = coll.find({a:10, b:"strvar_10", c:0}).hint({a:1, b:-1, c:1}).explain()
+var plan = coll.find({a:10, b:"strvar_10", c:0}).hint({a:1, b:-1, c:1}).explain("executionStats");
assert(!isIndexOnly(plan.queryPlanner.winningPlan),
"negative.1.1 - indexOnly should be false on a non covered query")
assert.neq(0, plan.executionStats.totalDocsExamined,
"negative.1.1 - docs examined should not be 0 for a non covered query")
// Test projection and not excluding _id
-var plan = coll.find({a:10, b:"strvar_10", c:0},{a:1, b:1, c:1}).hint({a:1, b:-1, c:1}).explain()
+var plan = coll.find({a:10, b:"strvar_10", c:0},{a:1, b:1, c:1})
+ .hint({a:1, b:-1, c:1})
+ .explain("executionStats");
assert(!isIndexOnly(plan.queryPlanner.winningPlan),
"negative.1.2 - indexOnly should be false on a non covered query")
assert.neq(0, plan.executionStats.totalDocsExamined,
"negative.1.2 - docs examined should not be 0 for a non covered query")
// Test projection of non-indexed field
-var plan = coll.find({d:100},{d:1, c:1, _id:0}).hint({d:1}).explain()
+var plan = coll.find({d:100},{d:1, c:1, _id:0}).hint({d:1}).explain("executionStats");
assert(!isIndexOnly(plan.queryPlanner.winningPlan),
"negative.1.3 - indexOnly should be false on a non covered query")
assert.neq(0, plan.executionStats.totalDocsExamined,
"negative.1.3 - docs examined should not be 0 for a non covered query")
// Test query and projection on a multi-key index
-var plan = coll.find({e:99},{e:1, _id:0}).hint({e:1}).explain()
+var plan = coll.find({e:99},{e:1, _id:0}).hint({e:1}).explain("executionStats");
assert(!isIndexOnly(plan.queryPlanner.winningPlan),
"negative.1.4 - indexOnly should be false on a non covered query")
assert.neq(0, plan.executionStats.totalDocsExamined,
@@ -59,14 +61,16 @@ assert.neq(0, plan.executionStats.totalDocsExamined,
// assert.neq(0, plan.nscannedObjects, "negative.1.6 - nscannedObjects should not be 0 for a non covered query")
// Test query on non-indexed field
-var plan = coll.find({d:{$lt:1000}},{a:1, b:1, c:1, _id:0}).hint({a:1, b:-1, c:1}).explain()
+var plan = coll.find({d:{$lt:1000}},{a:1, b:1, c:1, _id:0})
+ .hint({a:1, b:-1, c:1})
+ .explain("executionStats");
assert(!isIndexOnly(plan.queryPlanner.winningPlan),
"negative.1.7 - indexOnly should be false on a non covered query")
assert.neq(0, plan.executionStats.totalDocsExamined,
"negative.1.7 - docs examined should not be 0 for a non covered query")
// Test query on hashed indexed field
-var plan = coll.find({f:10},{f:1, _id:0}).hint({f:"hashed"}).explain()
+var plan = coll.find({f:10},{f:1, _id:0}).hint({f:"hashed"}).explain("executionStats");
assert(!isIndexOnly(plan.queryPlanner.winningPlan),
"negative.1.8 - indexOnly should be false on a non covered query")
assert.neq(0, plan.executionStats.totalDocsExamined,
diff --git a/jstests/core/covered_index_simple_1.js b/jstests/core/covered_index_simple_1.js
index 146f0751f1c..3edde455754 100644
--- a/jstests/core/covered_index_simple_1.js
+++ b/jstests/core/covered_index_simple_1.js
@@ -20,49 +20,49 @@ coll.insert({foo:null})
coll.ensureIndex({foo:1})
// Test equality with int value
-var plan = coll.find({foo:1}, {foo:1, _id:0}).hint({foo:1}).explain()
+var plan = coll.find({foo:1}, {foo:1, _id:0}).hint({foo:1}).explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"simple.1.1 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
"simple.1.1 - docs examined should be 0 for covered query")
// Test equality with string value
-var plan = coll.find({foo:"string"}, {foo:1, _id:0}).hint({foo:1}).explain()
+var plan = coll.find({foo:"string"}, {foo:1, _id:0}).hint({foo:1}).explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"simple.1.2 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
"simple.1.2 - docs examined should be 0 for covered query")
// Test equality with doc value
-var plan = coll.find({foo:{bar:1}}, {foo:1, _id:0}).hint({foo:1}).explain()
+var plan = coll.find({foo:{bar:1}}, {foo:1, _id:0}).hint({foo:1}).explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"simple.1.3 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
"simple.1.3 - docs examined should be 0 for covered query")
// Test no query
-var plan = coll.find({}, {foo:1, _id:0}).hint({foo:1}).explain()
+var plan = coll.find({}, {foo:1, _id:0}).hint({foo:1}).explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"simple.1.4 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
"simple.1.4 - docs examined should be 0 for covered query")
// Test range query
-var plan = coll.find({foo:{$gt:2,$lt:6}}, {foo:1, _id:0}).hint({foo:1}).explain()
+var plan = coll.find({foo:{$gt:2,$lt:6}}, {foo:1, _id:0}).hint({foo:1}).explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"simple.1.5 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
"simple.1.5 - docs examined should be 0 for covered query")
// Test in query
-var plan = coll.find({foo:{$in:[5,8]}}, {foo:1, _id:0}).hint({foo:1}).explain()
+var plan = coll.find({foo:{$in:[5,8]}}, {foo:1, _id:0}).hint({foo:1}).explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"simple.1.6 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
"simple.1.6 - docs examined should be 0 for covered query")
// Test no return
-var plan = coll.find({foo:"2"}, {foo:1, _id:0}).hint({foo:1}).explain()
+var plan = coll.find({foo:"2"}, {foo:1, _id:0}).hint({foo:1}).explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"simple.1.7 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
diff --git a/jstests/core/covered_index_simple_2.js b/jstests/core/covered_index_simple_2.js
index 014f235b711..7ca05b3f461 100644
--- a/jstests/core/covered_index_simple_2.js
+++ b/jstests/core/covered_index_simple_2.js
@@ -14,42 +14,42 @@ coll.insert({foo:null})
coll.ensureIndex({foo:1},{unique:true})
// Test equality with int value
-var plan = coll.find({foo:1}, {foo:1, _id:0}).hint({foo:1}).explain()
+var plan = coll.find({foo:1}, {foo:1, _id:0}).hint({foo:1}).explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"simple.2.1 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
"simple.2.1 - docs examined should be 0 for covered query")
// Test equality with string value
-var plan = coll.find({foo:"string"}, {foo:1, _id:0}).hint({foo:1}).explain()
+var plan = coll.find({foo:"string"}, {foo:1, _id:0}).hint({foo:1}).explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"simple.2.2 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
"simple.2.2 - docs examined should be 0 for covered query")
// Test equality with int value on a dotted field
-var plan = coll.find({foo:{bar:1}}, {foo:1, _id:0}).hint({foo:1}).explain()
+var plan = coll.find({foo:{bar:1}}, {foo:1, _id:0}).hint({foo:1}).explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"simple.2.3 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
"simple.2.3 - docs examined should be 0 for covered query");
// Test no query
-var plan = coll.find({}, {foo:1, _id:0}).hint({foo:1}).explain()
+var plan = coll.find({}, {foo:1, _id:0}).hint({foo:1}).explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"simple.2.4 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
"simple.2.4 - docs examined should be 0 for covered query");
// Test range query
-var plan = coll.find({foo:{$gt:2,$lt:6}}, {foo:1, _id:0}).hint({foo:1}).explain()
+var plan = coll.find({foo:{$gt:2,$lt:6}}, {foo:1, _id:0}).hint({foo:1}).explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"simple.2.5 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
"simple.2.5 - docs examined should be 0 for covered query");
// Test in query
-var plan = coll.find({foo:{$in:[5,8]}}, {foo:1, _id:0}).hint({foo:1}).explain()
+var plan = coll.find({foo:{$in:[5,8]}}, {foo:1, _id:0}).hint({foo:1}).explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"simple.2.6 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
diff --git a/jstests/core/covered_index_simple_3.js b/jstests/core/covered_index_simple_3.js
index 32f411798ec..88293fe68bd 100644
--- a/jstests/core/covered_index_simple_3.js
+++ b/jstests/core/covered_index_simple_3.js
@@ -17,49 +17,49 @@ coll.insert({foo:null});
coll.ensureIndex({foo:1}, {sparse:true, unique:true});
// Test equality with int value
-var plan = coll.find({foo:1}, {foo:1, _id:0}).hint({foo:1}).explain();
+var plan = coll.find({foo:1}, {foo:1, _id:0}).hint({foo:1}).explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"simple.3.1 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
"simple.3.1 - docs examined should be 0 for covered query")
// Test equality with string value
-var plan = coll.find({foo:"string"}, {foo:1, _id:0}).hint({foo:1}).explain();
+var plan = coll.find({foo:"string"}, {foo:1, _id:0}).hint({foo:1}).explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"simple.3.2 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
"simple.3.2 - docs examined should be 0 for covered query")
// Test equality with int value on a dotted field
-var plan = coll.find({foo:{bar:1}}, {foo:1, _id:0}).hint({foo:1}).explain();
+var plan = coll.find({foo:{bar:1}}, {foo:1, _id:0}).hint({foo:1}).explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"simple.3.3 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
"simple.3.3 - docs examined should be 0 for covered query")
// Test no query
-var plan = coll.find({}, {foo:1, _id:0}).hint({foo:1}).explain();
+var plan = coll.find({}, {foo:1, _id:0}).hint({foo:1}).explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"simple.3.4 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
"simple.3.4 - docs examined should be 0 for covered query")
// Test range query
-var plan = coll.find({foo:{$gt:2,$lt:6}}, {foo:1, _id:0}).hint({foo:1}).explain();
+var plan = coll.find({foo:{$gt:2,$lt:6}}, {foo:1, _id:0}).hint({foo:1}).explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"simple.3.5 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
"simple.3.5 - docs examined should be 0 for covered query")
// Test in query
-var plan = coll.find({foo:{$in:[5,8]}}, {foo:1, _id:0}).hint({foo:1}).explain();
+var plan = coll.find({foo:{$in:[5,8]}}, {foo:1, _id:0}).hint({foo:1}).explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"simple.3.6 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
"simple.3.6 - docs examined should be 0 for covered query")
// Test $exists true
-var plan = coll.find({foo:{$exists:true}}, {foo:1, _id:0}).hint({foo:1}).explain();
+var plan = coll.find({foo:{$exists:true}}, {foo:1, _id:0}).hint({foo:1}).explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"simple.3.7 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
@@ -68,7 +68,7 @@ assert.eq(0, plan.executionStats.totalDocsExamined,
// Check that $nin can be covered.
coll.dropIndexes();
coll.ensureIndex({bar: 1});
-var plan = coll.find({bar:{$nin:[5,8]}}, {bar:1, _id:0}).hint({bar:1}).explain()
+var plan = coll.find({bar:{$nin:[5,8]}}, {bar:1, _id:0}).hint({bar:1}).explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"simple.3.8 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
diff --git a/jstests/core/covered_index_simple_id.js b/jstests/core/covered_index_simple_id.js
index 8016854cb0a..3677e42056d 100644
--- a/jstests/core/covered_index_simple_id.js
+++ b/jstests/core/covered_index_simple_id.js
@@ -13,42 +13,42 @@ coll.insert({_id:{bar:1}})
coll.insert({_id:null})
// Test equality with int value
-var plan = coll.find({_id:1}, {_id:1}).hint({_id:1}).explain()
+var plan = coll.find({_id:1}, {_id:1}).hint({_id:1}).explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"simple.id.1 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
"simple.id.1 - docs examined should be 0 for covered query")
// Test equality with string value
-var plan = coll.find({_id:"string"}, {_id:1}).hint({_id:1}).explain()
+var plan = coll.find({_id:"string"}, {_id:1}).hint({_id:1}).explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"simple.id.2 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
"simple.id.2 - docs examined should be 0 for covered query")
// Test equality with int value on a dotted field
-var plan = coll.find({_id:{bar:1}}, {_id:1}).hint({_id:1}).explain()
+var plan = coll.find({_id:{bar:1}}, {_id:1}).hint({_id:1}).explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"simple.id.3 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
"simple.id.3 - docs examined should be 0 for covered query")
// Test no query
-var plan = coll.find({}, {_id:1}).hint({_id:1}).explain()
+var plan = coll.find({}, {_id:1}).hint({_id:1}).explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"simple.id.4 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
"simple.id.4 - docs examined should be 0 for covered query")
// Test range query
-var plan = coll.find({_id:{$gt:2,$lt:6}}, {_id:1}).hint({_id:1}).explain()
+var plan = coll.find({_id:{$gt:2,$lt:6}}, {_id:1}).hint({_id:1}).explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"simple.id.5 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
"simple.id.5 - docs examined should be 0 for covered query")
// Test in query
-var plan = coll.find({_id:{$in:[5,8]}}, {_id:1}).hint({_id:1}).explain()
+var plan = coll.find({_id:{$in:[5,8]}}, {_id:1}).hint({_id:1}).explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"simple.id.6 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
diff --git a/jstests/core/covered_index_sort_1.js b/jstests/core/covered_index_sort_1.js
index fd7d77d272e..d8b301ae3aa 100644
--- a/jstests/core/covered_index_sort_1.js
+++ b/jstests/core/covered_index_sort_1.js
@@ -20,21 +20,23 @@ coll.insert({foo:null})
coll.ensureIndex({foo:1})
// Test no query and sort ascending
-var plan = coll.find({}, {foo:1, _id:0}).sort({foo:1}).hint({foo:1}).explain()
+var plan = coll.find({}, {foo:1, _id:0}).sort({foo:1}).hint({foo:1}).explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"sort.1.1 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
"sort.1.1 - docs examined should be 0 for covered query")
// Test no query and sort descending
-var plan = coll.find({}, {foo:1, _id:0}).sort({foo:-1}).hint({foo:1}).explain()
+var plan = coll.find({}, {foo:1, _id:0}).sort({foo:-1}).hint({foo:1}).explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"sort.1.2 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
"sort.1.2 - docs examined should be 0 for covered query")
// Test range query with sort
-var plan = coll.find({foo:{$gt:2}}, {foo:1, _id:0}).sort({foo:-1}).hint({foo:1}).explain()
+var plan = coll.find({foo:{$gt:2}}, {foo:1, _id:0}).sort({foo:-1})
+ .hint({foo:1})
+ .explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"sort.1.3 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
diff --git a/jstests/core/covered_index_sort_2.js b/jstests/core/covered_index_sort_2.js
index 4315bdc448a..fc438b21554 100644
--- a/jstests/core/covered_index_sort_2.js
+++ b/jstests/core/covered_index_sort_2.js
@@ -13,7 +13,7 @@ coll.insert({_id:{bar:1}})
coll.insert({_id:null})
// Test no query
-var plan = coll.find({}, {_id:1}).sort({_id:-1}).hint({_id:1}).explain()
+var plan = coll.find({}, {_id:1}).sort({_id:-1}).hint({_id:1}).explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"sort.2.1 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
diff --git a/jstests/core/covered_index_sort_3.js b/jstests/core/covered_index_sort_3.js
index 6b5cae9def2..e87e6d0dff3 100644
--- a/jstests/core/covered_index_sort_3.js
+++ b/jstests/core/covered_index_sort_3.js
@@ -12,7 +12,9 @@ coll.insert
coll.ensureIndex({a:1,b:-1,c:1})
// Test no query, sort on all fields in index order
-var plan = coll.find({}, {b:1, c:1, _id:0}).sort({a:1,b:-1,c:1}).hint({a:1, b:-1, c:1}).explain()
+var plan = coll.find({}, {b:1, c:1, _id:0}).sort({a:1,b:-1,c:1})
+ .hint({a:1, b:-1, c:1})
+ .explain("executionStats");
assert(isIndexOnly(plan.queryPlanner.winningPlan),
"sort.3.1 - indexOnly should be true on covered query")
assert.eq(0, plan.executionStats.totalDocsExamined,
diff --git a/jstests/core/distinct_speed1.js b/jstests/core/distinct_speed1.js
index 2e21f5463b9..dff1094f994 100644
--- a/jstests/core/distinct_speed1.js
+++ b/jstests/core/distinct_speed1.js
@@ -9,7 +9,7 @@ for ( var i=0; i<10000; i++ ){
assert.eq( 10 , t.distinct("x").length , "A1" );
function fast(){
- t.find().explain().executionStats.executionTimeMillis;
+ t.find().explain("executionStats").executionStats.executionTimeMillis;
}
function slow(){
diff --git a/jstests/core/explain1.js b/jstests/core/explain1.js
index 59d29100507..2022a189a07 100644
--- a/jstests/core/explain1.js
+++ b/jstests/core/explain1.js
@@ -18,7 +18,7 @@ assert.eq( 49 , t.find( q ).count() , "D" );
assert.eq( 49 , t.find( q ).itcount() , "E" );
assert.eq( 20 , t.find( q ).limit(20).itcount() , "F" );
-assert.eq( 49 , t.find(q).explain().executionStats.nReturned , "G" );
-assert.eq( 20 , t.find(q).limit(20).explain().executionStats.nReturned , "H" );
-assert.eq( 20 , t.find(q).limit(-20).explain().executionStats.nReturned , "I" );
-assert.eq( 49 , t.find(q).batchSize(20).explain().executionStats.nReturned , "J" );
+assert.eq( 49 , t.find(q).explain("executionStats").executionStats.nReturned , "G" );
+assert.eq( 20 , t.find(q).limit(20).explain("executionStats").executionStats.nReturned , "H" );
+assert.eq( 20 , t.find(q).limit(-20).explain("executionStats").executionStats.nReturned , "I" );
+assert.eq( 49 , t.find(q).batchSize(20).explain("executionStats").executionStats.nReturned , "J" );
diff --git a/jstests/core/explain5.js b/jstests/core/explain5.js
index 22f8ae9f184..eb8e5d9f4a2 100644
--- a/jstests/core/explain5.js
+++ b/jstests/core/explain5.js
@@ -11,7 +11,9 @@ for( i = 0; i < 1000; ++i ) {
}
// Query with an initial set of documents.
-var explain1 = t.find( { a:{ $gte:0 }, b:2 } ).sort( { a:1 } ).hint( { a:1 } ).explain();
+var explain1 = t.find( { a:{ $gte:0 }, b:2 } ).sort( { a:1 } )
+ .hint( { a:1 } )
+ .explain("executionStats");
printjson(explain1);
var stats1 = explain1.executionStats;
assert.eq( 333, stats1.nReturned, 'wrong nReturned for explain1' );
@@ -22,7 +24,9 @@ for( i = 1000; i < 2000; ++i ) {
}
// Query with some additional documents.
-var explain2 = t.find( { a:{ $gte:0 }, b:2 } ).sort( { a:1 } ).hint ( { a:1 } ).explain();
+var explain2 = t.find( { a:{ $gte:0 }, b:2 } ).sort( { a:1 } )
+ .hint ( { a:1 } )
+ .explain("executionStats");
printjson(explain2);
var stats2 = explain2.executionStats;
assert.eq( 666, stats2.nReturned, 'wrong nReturned for explain2' );
diff --git a/jstests/core/explain_batch_size.js b/jstests/core/explain_batch_size.js
index 1722052c233..7528098107b 100644
--- a/jstests/core/explain_batch_size.js
+++ b/jstests/core/explain_batch_size.js
@@ -16,4 +16,4 @@ var q = {};
assert.eq( n , t.find( q ).count() , "A" );
assert.eq( n , t.find( q ).itcount() , "B" );
-assert.eq( n , t.find( q ).batchSize(1).explain().executionStats.nReturned , "C" );
+assert.eq( n , t.find( q ).batchSize(1).explain("executionStats").executionStats.nReturned , "C" );
diff --git a/jstests/core/explain_shell_helpers.js b/jstests/core/explain_shell_helpers.js
new file mode 100644
index 00000000000..5cc0a4bbfd3
--- /dev/null
+++ b/jstests/core/explain_shell_helpers.js
@@ -0,0 +1,389 @@
+// 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("rejectedPlansExecution" in explain.executionStats);
+
+// .explain(true) after .find().
+explain = t.find().explain(true);
+assert.commandWorked(explain);
+assert("queryPlanner" in explain);
+assert("executionStats" in explain);
+assert("rejectedPlansExecution" 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(!("rejectedPlansExecution" in explain.executionStats));
+explain = t.find().explain("executionStats");
+assert.commandWorked(explain);
+assert("queryPlanner" in explain);
+assert("executionStats" in explain);
+assert(!("rejectedPlansExecution" in explain.executionStats));
+
+// "allPlansExecution"
+explain = t.explain("allPlansExecution").find().finish();
+assert.commandWorked(explain);
+assert("queryPlanner" in explain);
+assert("executionStats" in explain);
+assert("rejectedPlansExecution" in explain.executionStats);
+explain = t.find().explain("allPlansExecution");
+assert.commandWorked(explain);
+assert("queryPlanner" in explain);
+assert("executionStats" in explain);
+assert("rejectedPlansExecution" 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(explain.queryPlanner.winningPlan, "SORT"));
+explain = t.find().sort({b: -1}).explain();
+assert.commandWorked(explain);
+assert(planHasStage(explain.queryPlanner.winningPlan, "SORT"));
+
+// .hint()
+explain = t.explain().find().hint({a: 1}).finish();
+assert.commandWorked(explain);
+assert(isIxscan(explain.queryPlanner.winningPlan));
+explain = t.find().hint({a: 1}).explain();
+assert.commandWorked(explain);
+assert(isIxscan(explain.queryPlanner.winningPlan));
+
+// .min()
+explain = t.explain().find().min({a: 1}).finish();
+assert.commandWorked(explain);
+assert(isIxscan(explain.queryPlanner.winningPlan));
+explain = t.find().min({a: 1}).explain();
+assert.commandWorked(explain);
+assert(isIxscan(explain.queryPlanner.winningPlan));
+
+// .max()
+explain = t.explain().find().max({a: 1}).finish();
+assert.commandWorked(explain);
+assert(isIxscan(explain.queryPlanner.winningPlan));
+explain = t.find().max({a: 1}).explain();
+assert.commandWorked(explain);
+assert(isIxscan(explain.queryPlanner.winningPlan));
+
+// .showDiskLoc()
+explain = t.explain().find().showDiskLoc().finish();
+assert.commandWorked(explain);
+explain = t.find().showDiskLoc().explain();
+assert.commandWorked(explain);
+
+// .maxTimeMS()
+explain = t.explain().find().maxTimeMS(200).finish();
+assert.commandWorked(explain);
+explain = t.find().maxTimeMS(200).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);
+
+// .snapshot()
+explain = t.explain().find().snapshot().finish();
+assert.commandWorked(explain);
+assert(isIxscan(explain.queryPlanner.winningPlan));
+explain = t.find().snapshot().explain();
+assert.commandWorked(explain);
+assert(isIxscan(explain.queryPlanner.winningPlan));
+
+// .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}}]);
+assert.commandWorked(explain);
+assert.eq(1, explain.stages.length);
+assert("queryPlanner" in explain.stages[0].$cursor);
+
+// Legacy varargs format.
+explain = t.explain().aggregate({$match: {a: 3}});
+assert.commandWorked(explain);
+assert.eq(1, explain.stages.length);
+assert("queryPlanner" in explain.stages[0].$cursor);
+
+explain = t.explain().aggregate({$match: {a: 3}}, {$project: {a: 1}});
+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}}], {allowDiskUse: true});
+assert.commandWorked(explain);
+assert.eq(1, explain.stages.length);
+assert("queryPlanner" in explain.stages[0].$cursor);
+
+//
+// .count()
+//
+
+// Basic count.
+explain = t.explain().count().finish();
+assert.commandWorked(explain);
+assert(planHasStage(explain.queryPlanner.winningPlan, "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).finish();
+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).finish();
+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().count({a: 3}).hint({a: 1}).finish();
+assert.commandWorked(explain);
+assert(planHasStage(explain.queryPlanner.winningPlan, "COUNT"));
+assert(planHasStage(explain.queryPlanner.winningPlan, "COUNT_SCAN"));
+
+// Count with hint using .find().count() syntax.
+explain = t.explain().find({a: 3}).count().hint({a: 1}).finish();
+assert.commandWorked(explain);
+assert(planHasStage(explain.queryPlanner.winningPlan, "COUNT"));
+assert(planHasStage(explain.queryPlanner.winningPlan, "COUNT_SCAN"));
+
+//
+// .group()
+//
+
+explain = t.explain().group({key: "a", initial: {}, reduce: function() { } });
+assert.commandWorked(explain);
+
+//
+// .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);
+
+//
+// Error cases.
+//
+
+// Invalid verbosity string.
+assert.throws(function() {
+ t.explain("foobar").find().finish();
+});
+assert.throws(function() {
+ t.find().explain("foobar");
+});
+
+// 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);
+});
+
+// Missing "initial" for explaining a group.
+assert.throws(function() {
+ t.explain().group({key: "a", reduce: function() { } });
+});
diff --git a/jstests/core/fts_explain.js b/jstests/core/fts_explain.js
index 263e4b04b38..3ec8fc0315d 100644
--- a/jstests/core/fts_explain.js
+++ b/jstests/core/fts_explain.js
@@ -12,6 +12,9 @@ assert.writeOK(res);
var explain = coll.find({$text:{$search: "\"a\" -b -\"c\""}}).explain(true);
var stage = explain.executionStats.executionStages;
+if ("SINGLE_SHARD" === stage.stage) {
+ stage = stage.shards[0].executionStages;
+}
assert.eq(stage.stage, "TEXT");
assert.eq(stage.parsedTextQuery.terms, ["a"]);
assert.eq(stage.parsedTextQuery.negatedTerms, ["b"]);
diff --git a/jstests/core/geo_2d_explain.js b/jstests/core/geo_2d_explain.js
index f1a1e2887e4..3968c745748 100644
--- a/jstests/core/geo_2d_explain.js
+++ b/jstests/core/geo_2d_explain.js
@@ -20,7 +20,7 @@ for (var i = 0; i < n; i++) {
t.save({_id: i, a: a, loc: loc});
}
-var explain = t.find({loc: {$near: [40, 40]}, _id: {$lt: 50}}).explain();
+var explain = t.find({loc: {$near: [40, 40]}, _id: {$lt: 50}}).explain("executionStats");
print('explain = ' + tojson(explain));
diff --git a/jstests/core/geo_center_sphere1.js b/jstests/core/geo_center_sphere1.js
index a0539965ccc..a5659391208 100644
--- a/jstests/core/geo_center_sphere1.js
+++ b/jstests/core/geo_center_sphere1.js
@@ -85,7 +85,7 @@ function test(index) {
assert.eq( numExpected , t.find( q ).itcount() , "itcount : " + tojson( searches[i] ) );
assert.eq( numExpected , t.find( q ).count() , "count : " + tojson( searches[i] ) );
if (index == "2d") {
- var explain = t.find( q ).explain();
+ var explain = t.find( q ).explain("executionStats");
print( 'explain for ' + tojson( q , '' , true ) + ' = ' + tojson( explain ) );
// The index should be at least minimally effective in preventing the full collection
// scan.
diff --git a/jstests/core/geo_circle1.js b/jstests/core/geo_circle1.js
index d74e6a1eea3..55cbbcc7e5c 100644
--- a/jstests/core/geo_circle1.js
+++ b/jstests/core/geo_circle1.js
@@ -37,7 +37,7 @@ for ( i=0; i<searches.length; i++ ){
assert.eq( correct[i].length , t.find( q ).itcount() , "itcount : " + tojson( searches[i] ) );
assert.eq( correct[i].length , t.find( q ).count() , "count : " + tojson( searches[i] ) );
- var explain = t.find( q ).explain();
+ var explain = t.find( q ).explain("executionStats");
print( 'explain for ' + tojson( q , '' , true ) + ' = ' + tojson( explain ) );
// The index should be at least minimally effective in preventing the full collection
// scan.
diff --git a/jstests/core/geo_s2nearComplex.js b/jstests/core/geo_s2nearComplex.js
index 9c6ac0098be..e6fa1f77bd7 100644
--- a/jstests/core/geo_s2nearComplex.js
+++ b/jstests/core/geo_s2nearComplex.js
@@ -164,7 +164,7 @@ uniformPoints(origin, 1000, 0.5, 1.5);
validateOrdering({geo: {$geoNear: {$geometry: originGeo}}})
print("Millis for uniform:")
-print(t.find(query).explain().executionStats.executionTimeMillis);
+print(t.find(query).explain("executionStats").executionStats.executionTimeMillis);
print("Total points:");
print(t.find(query).itcount());
@@ -176,7 +176,7 @@ uniformPointsWithGaps(origin, 1000, 1, 10.0, 5, 10);
validateOrdering({geo: {$geoNear: {$geometry: originGeo}}})
print("Millis for uniform with gaps:")
-print(t.find(query).explain().executionStats.executionTimeMillis);
+print(t.find(query).explain("executionStats").executionStats.executionTimeMillis);
print("Total points:");
print(t.find(query).itcount());
@@ -189,7 +189,7 @@ uniformPointsWithClusters(origin, 1000, 1, 10.0, 5, 10, 100);
validateOrdering({geo: {$geoNear: {$geometry: originGeo}}})
print("Millis for uniform with clusters:");
-print(t.find(query).explain().executionStats.executionTimeMillis);
+print(t.find(query).explain("executionStats").executionStats.executionTimeMillis);
print("Total points:");
print(t.find(query).itcount());
@@ -210,7 +210,7 @@ validateOrdering({geo: {$geoNear: {$geometry: originGeo}}})
print("Millis for uniform near pole:")
print(t.find({geo: {$geoNear: {$geometry: originGeo}}})
- .explain().executionStats.executionTimeMillis);
+ .explain("executionStats").executionStats.executionTimeMillis);
assert.eq(t.find({geo: {$geoNear: {$geometry: originGeo}}}).itcount(), 50);
t.drop()
@@ -228,7 +228,7 @@ validateOrdering({geo: {$geoNear: {$geometry: originGeo}}})
print("Millis for uniform on meridian:")
print(t.find({geo: {$geoNear: {$geometry: originGeo}}})
- .explain().executionStats.executionTimeMillis);
+ .explain("executionStats").executionStats.executionTimeMillis);
assert.eq(t.find({geo: {$geoNear: {$geometry: originGeo}}}).itcount(), 50);
t.drop()
@@ -246,7 +246,7 @@ validateOrdering({geo: {$near: {$geometry: originGeo}}})
print("Millis for uniform on negative meridian:");
print(t.find({geo: {$geoNear: {$geometry: originGeo}}})
- .explain().executionStats.executionTimeMillis);
+ .explain("executionStats").executionStats.executionTimeMillis);
assert.eq(t.find({geo: {$near: {$geometry: originGeo}}}).itcount(), 50);
// Near search with points that are really far away.
@@ -267,6 +267,6 @@ cur = t.find({geo: {$near: {$geometry: originGeo}}})
print("Near search on very distant points:");
print(t.find({geo: {$geoNear: {$geometry: originGeo}}})
- .explain().executionStats.executionTimeMillis);
+ .explain("executionStats").executionStats.executionTimeMillis);
pt = cur.next();
assert(pt)
diff --git a/jstests/core/geo_s2ordering.js b/jstests/core/geo_s2ordering.js
index 3dd75ff5785..490d4068170 100644
--- a/jstests/core/geo_s2ordering.js
+++ b/jstests/core/geo_s2ordering.js
@@ -35,8 +35,8 @@ function runTest(index) {
iterations = 10;
for (var x = 0; x < iterations; ++x) {
res = t.find({nongeo: needle, geo: {$within: {$centerSphere: [[0,0], Math.PI/180.0]}}})
- if (res.explain().executionStats.executionTimeMillis < mintime) {
- mintime = res.explain().executionStats.executionTimeMillis;
+ if (res.explain("executionStats").executionStats.executionTimeMillis < mintime) {
+ mintime = res.explain("executionStats").executionStats.executionTimeMillis;
resultcount = res.itcount()
}
}
diff --git a/jstests/core/geo_s2twofields.js b/jstests/core/geo_s2twofields.js
index 039223aadcf..7332c8e33e0 100644
--- a/jstests/core/geo_s2twofields.js
+++ b/jstests/core/geo_s2twofields.js
@@ -43,11 +43,11 @@ function semiRigorousTime(func) {
function timeWithoutAndWithAnIndex(index, query) {
t.dropIndex(index);
var withoutTime = semiRigorousTime(function() {
- return t.find(query).explain().executionStats.executionTimeMillis;
+ return t.find(query).explain("executionStats").executionStats.executionTimeMillis;
});
t.ensureIndex(index);
var withTime = semiRigorousTime(function() {
- return t.find(query).explain().executionStats.executionTimeMillis;
+ return t.find(query).explain("executionStats").executionStats.executionTimeMillis;
});
t.dropIndex(index);
return [withoutTime, withTime];
diff --git a/jstests/core/index_check2.js b/jstests/core/index_check2.js
index 9eade5a68fa..a4488dde229 100644
--- a/jstests/core/index_check2.js
+++ b/jstests/core/index_check2.js
@@ -32,9 +32,9 @@ assert( isIxscan(t.find(q1).explain().queryPlanner.winningPlan) , "e1" );
assert( isIxscan(t.find(q2).explain().queryPlanner.winningPlan) , "e2" );
assert( isIxscan(t.find(q3).explain().queryPlanner.winningPlan) , "e3" );
-scanned1 = t.find(q1).explain().executionStats.totalKeysExamined;
-scanned2 = t.find(q2).explain().executionStats.totalKeysExamined;
-scanned3 = t.find(q3).explain().executionStats.totalKeysExamined;
+scanned1 = t.find(q1).explain("executionStats").executionStats.totalKeysExamined;
+scanned2 = t.find(q2).explain("executionStats").executionStats.totalKeysExamined;
+scanned3 = t.find(q3).explain("executionStats").executionStats.totalKeysExamined;
//print( "scanned1: " + scanned1 + " scanned2: " + scanned2 + " scanned3: " + scanned3 );
diff --git a/jstests/core/index_check3.js b/jstests/core/index_check3.js
index bef79fd650d..78135ff30ca 100644
--- a/jstests/core/index_check3.js
+++ b/jstests/core/index_check3.js
@@ -29,9 +29,9 @@ for ( var i=0; i<100; i++ ){
t.ensureIndex( { foo : 1 } );
-var explain = t.find( { foo : { $lt : 50 } } ).explain();
+var explain = t.find( { foo : { $lt : 50 } } ).explain("executionStats");
assert.gt( 30 , explain.executionStats.totalKeysExamined , "lt" );
-var explain = t.find( { foo : { $gt : 50 } } ).explain();
+var explain = t.find( { foo : { $gt : 50 } } ).explain("executionStats");
assert.gt( 30 , explain.executionStats.totalKeysExamined , "gt" );
@@ -43,11 +43,11 @@ for( var i=0; i < 10; ++i ) {
t.ensureIndex( { i : 1 } );
-var explain = t.find( { i : { $lte : 'a' } } ).explain();
+var explain = t.find( { i : { $lte : 'a' } } ).explain("executionStats");
assert.gt( 3 , explain.executionStats.totalKeysExamined , "lte" );
//printjson( t.find( { i : { $gte : 'a' } } ).explain() );
// bug SERVER-99
-var explain = t.find( { i : { $gte : 'a' } } ).explain();
+var explain = t.find( { i : { $gte : 'a' } } ).explain("executionStats");
assert.gt( 3 , explain.executionStats.totalKeysExamined , "gte" );
assert.eq( 1 , t.find( { i : { $gte : 'a' } } ).count() , "gte a" );
assert.eq( 1 , t.find( { i : { $gte : 'a' } } ).itcount() , "gte b" );
@@ -56,7 +56,7 @@ assert.eq( 1 , t.find( { i : { $gte : 'a' } } ).sort( { i : 1 } ).itcount() , "g
t.save( { i : "b" } );
-var explain = t.find( { i : { $gte : 'a' } } ).explain();
+var explain = t.find( { i : { $gte : 'a' } } ).explain("executionStats");
assert.gt( 3 , explain.executionStats.totalKeysExamined , "gte" );
assert.eq( 2 , t.find( { i : { $gte : 'a' } } ).count() , "gte a2" );
assert.eq( 2 , t.find( { i : { $gte : 'a' } } ).itcount() , "gte b2" );
diff --git a/jstests/core/index_check6.js b/jstests/core/index_check6.js
index 090fd27d4c3..b1b624adbf6 100644
--- a/jstests/core/index_check6.js
+++ b/jstests/core/index_check6.js
@@ -3,7 +3,7 @@ t = db.index_check6;
t.drop();
function keysExamined(query, hint) {
- var explain = t.find(query).hint(hint).explain();
+ var explain = t.find(query).hint(hint).explain("executionStats");
return explain.executionStats.totalKeysExamined;
}
@@ -29,8 +29,9 @@ assert.eq( 4 , keysExamined( { age : { $gte : 29 , $lte : 30 } , rating : 5 },
assert.eq( 6 , keysExamined( { age : { $gte : 29 , $lte : 30 } , rating : { $gte : 4 , $lte : 5 } },
{age:1,rating:1} ) , "D" ); // SERVER-371
-assert.eq.automsg( "2", "t.find( { age:30, rating:{ $gte:4, $lte:5} } ).explain()" +
- ".executionStats.totalKeysExamined" );
+assert.eq.automsg( "2", "t.find( { age:30, rating:{ $gte:4, $lte:5} } )" +
+ ".explain('executionStats')" +
+ ".executionStats.totalKeysExamined" );
t.drop();
@@ -43,7 +44,7 @@ for ( var a=1; a<10; a++ ){
}
function doQuery( count, query, sort, index ) {
- var explain = t.find( query ).hint( index ).sort( sort ).explain();
+ var explain = t.find( query ).hint( index ).sort( sort ).explain("executionStats");
var nscanned = explain.executionStats.totalKeysExamined;
assert(Math.abs(count - nscanned) <= 2);
}
diff --git a/jstests/core/index_check7.js b/jstests/core/index_check7.js
index c23ef4eda1e..01a369a5300 100644
--- a/jstests/core/index_check7.js
+++ b/jstests/core/index_check7.js
@@ -6,10 +6,10 @@ for ( var i=0; i<100; i++ )
t.save( { x : i } )
t.ensureIndex( { x : 1 } )
-assert.eq( 1 , t.find( { x : 27 } ).explain().executionStats.totalKeysExamined , "A" )
+assert.eq( 1 , t.find( { x : 27 } ).explain(true).executionStats.totalKeysExamined , "A" )
t.ensureIndex( { x : -1 } )
-assert.eq( 1 , t.find( { x : 27 } ).explain().executionStats.totalKeysExamined , "B" )
-
-assert.eq( 40 , t.find( { x : { $gt : 59 } } ).explain().executionStats.totalKeysExamined , "C" );
+assert.eq( 1 , t.find( { x : 27 } ).explain(true).executionStats.totalKeysExamined , "B" )
+assert.eq( 40 , t.find( { x : { $gt : 59 } } ).explain(true)
+ .executionStats.totalKeysExamined , "C" );
diff --git a/jstests/core/index_elemmatch1.js b/jstests/core/index_elemmatch1.js
index 99418e83839..9641bb6cf0d 100644
--- a/jstests/core/index_elemmatch1.js
+++ b/jstests/core/index_elemmatch1.js
@@ -34,7 +34,7 @@ function nscannedForCursor( explain, cursor ) {
return -1;
}
-var explain = t.find(q).hint( { "arr.x" : 1 , a : 1 } ).explain();
+var explain = t.find(q).hint( { "arr.x" : 1 , a : 1 } ).explain("executionStats");
assert.eq( t.find(q).itcount(), explain.executionStats.totalKeysExamined );
printjson(t.find(q).explain());
diff --git a/jstests/core/indexj.js b/jstests/core/indexj.js
index e58104d75c6..cde81e95ea2 100644
--- a/jstests/core/indexj.js
+++ b/jstests/core/indexj.js
@@ -10,7 +10,7 @@ function keysExamined(query, hint, sort) {
if (!sort) {
sort = {};
}
- var explain = t.find(query).sort(sort).hint(hint).explain();
+ var explain = t.find(query).sort(sort).hint(hint).explain("executionStats");
return explain.executionStats.totalKeysExamined;
}
diff --git a/jstests/core/indexv.js b/jstests/core/indexv.js
index a30541de36c..95074da7333 100644
--- a/jstests/core/indexv.js
+++ b/jstests/core/indexv.js
@@ -6,13 +6,13 @@ t.drop();
t.ensureIndex( {'a.b':1} );
t.save( {a:[{},{b:1}]} );
-var e = t.find( {'a.b':null} ).explain();
+var e = t.find( {'a.b':null} ).explain("executionStats");
assert.eq( 1, e.executionStats.nReturned );
assert.eq( 1, e.executionStats.totalKeysExamined );
t.drop();
t.ensureIndex( {'a.b.c':1} );
t.save( {a:[{b:[]},{b:{c:1}}]} );
-var e = t.find( {'a.b.c':null} ).explain();
+var e = t.find( {'a.b.c':null} ).explain("executionStats");
assert.eq( 0, e.executionStats.nReturned );
assert.eq( 1, e.executionStats.totalKeysExamined );
diff --git a/jstests/core/mod1.js b/jstests/core/mod1.js
index d578190737f..834084e9301 100644
--- a/jstests/core/mod1.js
+++ b/jstests/core/mod1.js
@@ -11,7 +11,7 @@ t.save( { a : "adasdas" } );
assert.eq( 2 , t.find( "this.a % 10 == 1" ).itcount() , "A1" );
assert.eq( 2 , t.find( { a : { $mod : [ 10 , 1 ] } } ).itcount() , "A2" );
-assert.eq( 0 , t.find( { a : { $mod : [ 10 , 1 ] } } ).explain()
+assert.eq( 0 , t.find( { a : { $mod : [ 10 , 1 ] } } ).explain("executionStats")
.executionStats.totalKeysExamined , "A3" );
t.ensureIndex( { a : 1 } );
@@ -21,7 +21,7 @@ assert.eq( 2 , t.find( { a : { $mod : [ 10 , 1 ] } } ).itcount() , "B2" );
assert.eq( 1 , t.find( "this.a % 10 == 0" ).itcount() , "B3" );
assert.eq( 1 , t.find( { a : { $mod : [ 10 , 0 ] } } ).itcount() , "B4" );
-assert.eq( 4 , t.find( { a : { $mod : [ 10 , 1 ] } } ).explain()
+assert.eq( 4 , t.find( { a : { $mod : [ 10 , 1 ] } } ).explain("executionStats")
.executionStats.totalKeysExamined, "B5" );
assert.eq( 1, t.find( { a: { $gt: 5, $mod : [ 10, 1 ] } } ).itcount() );
diff --git a/jstests/core/mr_index.js b/jstests/core/mr_index.js
index 394ecc3f650..6c606cfa1dc 100644
--- a/jstests/core/mr_index.js
+++ b/jstests/core/mr_index.js
@@ -24,7 +24,7 @@ r = function( k , vs ){
}
ex = function(){
- return out.find().sort( { value : 1 } ).explain()
+ return out.find().sort( { value : 1 } ).explain("executionStats")
}
res = t.mapReduce( m , r , { out : outName } )
diff --git a/jstests/core/ne2.js b/jstests/core/ne2.js
index c34f482a389..b0960d69cfa 100644
--- a/jstests/core/ne2.js
+++ b/jstests/core/ne2.js
@@ -12,5 +12,5 @@ t.save( { a:0.5 } );
e = t.find( { a: { $ne: 0 } } ).explain( true );
assert.eq( 2, e.executionStats.nReturned, 'A' );
-e = t.find( { a: { $gt: -1, $lt: 1, $ne: 0 } } ).explain();
+e = t.find( { a: { $gt: -1, $lt: 1, $ne: 0 } } ).explain( true );
assert.eq( 2, e.executionStats.nReturned, 'B' );
diff --git a/jstests/core/regex3.js b/jstests/core/regex3.js
index 418492ce7f5..747fbf4d8a8 100644
--- a/jstests/core/regex3.js
+++ b/jstests/core/regex3.js
@@ -8,11 +8,11 @@ t.save( { name : "bob" } );
t.save( { name : "aaron" } );
assert.eq( 2 , t.find( { name : /^e.*/ } ).itcount() , "no index count" );
-assert.eq( 4 , t.find( { name : /^e.*/ } ).explain().executionStats.totalDocsExamined ,
+assert.eq( 4 , t.find( { name : /^e.*/ } ).explain(true).executionStats.totalDocsExamined ,
"no index explain" );
t.ensureIndex( { name : 1 } );
assert.eq( 2 , t.find( { name : /^e.*/ } ).itcount() , "index count" );
-assert.eq( 2 , t.find( { name : /^e.*/ } ).explain().executionStats.totalKeysExamined ,
+assert.eq( 2 , t.find( { name : /^e.*/ } ).explain(true).executionStats.totalKeysExamined ,
"index explain" ); // SERVER-239
t.drop();
@@ -25,7 +25,7 @@ t.save( { name : "c" } );
assert.eq( 3 , t.find( { name : /^aa*/ } ).itcount() , "B ni" );
t.ensureIndex( { name : 1 } );
assert.eq( 3 , t.find( { name : /^aa*/ } ).itcount() , "B i 1" );
-assert.eq( 4 , t.find( { name : /^aa*/ } ).explain().executionStats.totalKeysExamined ,
+assert.eq( 4 , t.find( { name : /^aa*/ } ).explain(true).executionStats.totalKeysExamined ,
"B i 1 e" );
assert.eq( 2 , t.find( { name : /^a[ab]/ } ).itcount() , "B i 2" );
diff --git a/jstests/core/regex4.js b/jstests/core/regex4.js
index e95daeafe7c..ed5e76331e0 100644
--- a/jstests/core/regex4.js
+++ b/jstests/core/regex4.js
@@ -8,13 +8,13 @@ t.save( { name : "bob" } );
t.save( { name : "aaron" } );
assert.eq( 2 , t.find( { name : /^e.*/ } ).count() , "no index count" );
-assert.eq( 4 , t.find( { name : /^e.*/ } ).explain().executionStats.totalDocsExamined ,
+assert.eq( 4 , t.find( { name : /^e.*/ } ).explain(true).executionStats.totalDocsExamined ,
"no index explain" );
//assert.eq( 2 , t.find( { name : { $ne : /^e.*/ } } ).count() , "no index count ne" ); // SERVER-251
t.ensureIndex( { name : 1 } );
assert.eq( 2 , t.find( { name : /^e.*/ } ).count() , "index count" );
-assert.eq( 2 , t.find( { name : /^e.*/ } ).explain().executionStats.totalKeysExamined ,
+assert.eq( 2 , t.find( { name : /^e.*/ } ).explain(true).executionStats.totalKeysExamined ,
"index explain" ); // SERVER-239
//assert.eq( 2 , t.find( { name : { $ne : /^e.*/ } } ).count() , "index count ne" ); // SERVER-251
diff --git a/jstests/core/regex6.js b/jstests/core/regex6.js
index 9ffa7499deb..4380ab1ab6b 100644
--- a/jstests/core/regex6.js
+++ b/jstests/core/regex6.js
@@ -11,31 +11,31 @@ t.save( { name : "[with]some?symbols" } );
t.ensureIndex( { name : 1 } );
assert.eq( 0 , t.find( { name : /^\// } ).count() , "index count" );
-assert.eq( 1 , t.find( { name : /^\// } ).explain().executionStats.totalKeysExamined ,
+assert.eq( 1 , t.find( { name : /^\// } ).explain(true).executionStats.totalKeysExamined ,
"index explain 1" );
-assert.eq( 0 , t.find( { name : /^é/ } ).explain().executionStats.totalKeysExamined ,
+assert.eq( 0 , t.find( { name : /^é/ } ).explain(true).executionStats.totalKeysExamined ,
"index explain 2" );
-assert.eq( 0 , t.find( { name : /^\é/ } ).explain().executionStats.totalKeysExamined ,
+assert.eq( 0 , t.find( { name : /^\é/ } ).explain(true).executionStats.totalKeysExamined ,
"index explain 3" );
-assert.eq( 1 , t.find( { name : /^\./ } ).explain().executionStats.totalKeysExamined ,
+assert.eq( 1 , t.find( { name : /^\./ } ).explain(true).executionStats.totalKeysExamined ,
"index explain 4" );
-assert.eq( 5 , t.find( { name : /^./ } ).explain().executionStats.totalKeysExamined ,
+assert.eq( 5 , t.find( { name : /^./ } ).explain(true).executionStats.totalKeysExamined ,
"index explain 5" );
// SERVER-2862
assert.eq( 0 , t.find( { name : /^\Qblah\E/ } ).count() , "index explain 6" );
-assert.eq( 1 , t.find( { name : /^\Qblah\E/ } ).explain().executionStats.totalKeysExamined ,
+assert.eq( 1 , t.find( { name : /^\Qblah\E/ } ).explain(true).executionStats.totalKeysExamined ,
"index explain 6" );
-assert.eq( 1 , t.find( { name : /^blah/ } ).explain().executionStats.totalKeysExamined ,
+assert.eq( 1 , t.find( { name : /^blah/ } ).explain(true).executionStats.totalKeysExamined ,
"index explain 6" );
assert.eq( 1 , t.find( { name : /^\Q[\Ewi\Qth]some?s\Eym/ } ).count() , "index count 2" );
-assert.eq( 2 , t.find( { name : /^\Q[\Ewi\Qth]some?s\Eym/ } ).explain()
+assert.eq( 2 , t.find( { name : /^\Q[\Ewi\Qth]some?s\Eym/ } ).explain(true)
.executionStats.totalKeysExamined ,
"index explain 6" );
-assert.eq( 2 , t.find( { name : /^bob/ } ).explain().executionStats.totalKeysExamined ,
+assert.eq( 2 , t.find( { name : /^bob/ } ).explain(true).executionStats.totalKeysExamined ,
"index explain 6" ); // proof executionStats.totalKeysExamined == count+1
-assert.eq( 1, t.find( { name : { $regex : "^e", $gte: "emily" } } ).explain()
+assert.eq( 1, t.find( { name : { $regex : "^e", $gte: "emily" } } ).explain(true)
.executionStats.totalKeysExamined , "ie7" );
-assert.eq( 1, t.find( { name : { $gt : "a", $regex: "^emily" } } ).explain()
+assert.eq( 1, t.find( { name : { $gt : "a", $regex: "^emily" } } ).explain(true)
.executionStats.totalKeysExamined , "ie7" );
diff --git a/jstests/core/sortk.js b/jstests/core/sortk.js
index 20ef08f7cca..da00fe80ba5 100644
--- a/jstests/core/sortk.js
+++ b/jstests/core/sortk.js
@@ -40,7 +40,7 @@ assert.eq( 1, simpleQueryWithLimit( -1 ).skip( 1 )[ 0 ].b );
// No limit is applied.
assert.eq( 6, simpleQueryWithLimit( 0 ).itcount() );
-assert.eq( 6, simpleQueryWithLimit( 0 ).explain().executionStats.totalKeysExamined );
+assert.eq( 6, simpleQueryWithLimit( 0 ).explain( true ).executionStats.totalKeysExamined );
assert.eq( 5, simpleQueryWithLimit( 0 ).skip( 1 ).itcount() );
// The query has additional constriants, preventing limit optimization.
diff --git a/jstests/libs/analyze_plan.js b/jstests/libs/analyze_plan.js
index 9c2ebffd890..0fd8b3bc8fe 100644
--- a/jstests/libs/analyze_plan.js
+++ b/jstests/libs/analyze_plan.js
@@ -20,6 +20,13 @@ function planHasStage(root, stage) {
}
}
}
+ else if ("shards" in root) {
+ for (var i = 0; i < root.shards.length; i++) {
+ if (planHasStage(root.shards[i].winningPlan, stage)) {
+ return true;
+ }
+ }
+ }
return false;
}
diff --git a/jstests/noPassthrough/indexbg1.js b/jstests/noPassthrough/indexbg1.js
index 640e703d1aa..666f80284b6 100644
--- a/jstests/noPassthrough/indexbg1.js
+++ b/jstests/noPassthrough/indexbg1.js
@@ -59,7 +59,7 @@ while( 1 ) { // if indexing finishes before we can run checks, try indexing w/ m
q.next();
assert( q.hasNext(), "no next" );
}
- var ex = t.find( {i:100} ).limit(-1).explain()
+ var ex = t.find( {i:100} ).limit(-1).explain("executionStats")
printjson(ex)
assert( ex.executionStats.totalKeysExamined < 1000 ,
"took too long to find 100: " + tojson( ex ) );
diff --git a/jstests/noPassthroughWithMongod/clonecollection.js b/jstests/noPassthroughWithMongod/clonecollection.js
index e71b2bc3668..f06ae41bc4d 100644
--- a/jstests/noPassthroughWithMongod/clonecollection.js
+++ b/jstests/noPassthroughWithMongod/clonecollection.js
@@ -30,7 +30,7 @@ if ( t.a.getIndexes().length != 2 ) {
}
assert.eq( 2, t.a.getIndexes().length, "expected index missing" );
// Verify index works
-x = t.a.find( { i: 50 } ).hint( { i: 1 } ).explain()
+x = t.a.find( { i: 50 } ).hint( { i: 1 } ).explain("executionStats")
printjson( x )
assert.eq( 1, x.executionStats.nReturned , "verify 1" );
assert.eq( 1, t.a.find( { i: 50 } ).hint( { i: 1 } ).toArray().length, "match length did not match expected" );
diff --git a/jstests/sharding/auth_slaveok_routing.js b/jstests/sharding/auth_slaveok_routing.js
index a6552590351..96cc1cf6155 100644
--- a/jstests/sharding/auth_slaveok_routing.js
+++ b/jstests/sharding/auth_slaveok_routing.js
@@ -13,7 +13,9 @@
* @return {boolean} true if query was routed to a secondary node.
*/
function doesRouteToSec( coll, query ) {
- var serverInfo = coll.find( query ).explain().serverInfo;
+ var explain = coll.find( query ).explain();
+ assert.eq("SINGLE_SHARD", explain.queryPlanner.winningPlan.stage);
+ var serverInfo = explain.queryPlanner.winningPlan.shards[0].serverInfo;
var conn = new Mongo( serverInfo.host + ":" + serverInfo.port.toString());
var cmdRes = conn.getDB( 'admin' ).runCommand({ isMaster: 1 });
diff --git a/jstests/sharding/covered_shard_key_indexes.js b/jstests/sharding/covered_shard_key_indexes.js
index 22e3aebd984..6a4c0a7bb26 100644
--- a/jstests/sharding/covered_shard_key_indexes.js
+++ b/jstests/sharding/covered_shard_key_indexes.js
@@ -28,35 +28,31 @@ st.printShardingStatus();
// Insert some data
assert.writeOK(coll.insert({ _id : true, a : true, b : true }));
-var shardExplain = function(mongosExplainDoc) {
- var explainDoc = mongosExplainDoc.shards[shards[0].host][0];
- printjson(explainDoc);
- return explainDoc.executionStats;
-};
-
assert.commandWorked(st.shard0.adminCommand({ setParameter: 1,
logComponentVerbosity: { query: { verbosity: 5 }}}));
//
// Index without shard key query - not covered
assert.commandWorked(coll.ensureIndex({ a : 1 }));
-assert.eq(1, shardExplain(coll.find({ a : true }).explain()).totalDocsExamined);
-assert.eq(1, shardExplain(coll.find({ a : true }, { _id : 1, a : 1 }).explain()).totalDocsExamined);
+assert.eq(1, coll.find({ a : true }).explain(true).executionStats.totalDocsExamined);
+assert.eq(1, coll.find({ a : true }, { _id : 1, a : 1 })
+ .explain(true).executionStats.totalDocsExamined);
//
// Index with shard key query - covered when projecting
assert.commandWorked(coll.dropIndexes());
assert.commandWorked(coll.ensureIndex({ a : 1, _id : 1 }));
-assert.eq(1, shardExplain(coll.find({ a : true }).explain()).totalDocsExamined);
-assert.eq(0, shardExplain(coll.find({ a : true }, { _id : 1, a : 1 }).explain()).totalDocsExamined);
+assert.eq(1, coll.find({ a : true }).explain(true).executionStats.totalDocsExamined);
+assert.eq(0, coll.find({ a : true }, { _id : 1, a : 1 })
+ .explain(true).executionStats.totalDocsExamined);
//
// Compound index with shard key query - covered when projecting
assert.commandWorked(coll.dropIndexes());
assert.commandWorked(coll.ensureIndex({ a : 1, b : 1, _id : 1 }));
-assert.eq(1, shardExplain(coll.find({ a : true, b : true }).explain()).totalDocsExamined);
-assert.eq(0, shardExplain(coll.find({ a : true, b : true }, { _id : 1, a : 1 })
- .explain()).totalDocsExamined);
+assert.eq(1, coll.find({ a : true, b : true }).explain(true).executionStats.totalDocsExamined);
+assert.eq(0, coll.find({ a : true, b : true }, { _id : 1, a : 1 })
+ .explain(true).executionStats.totalDocsExamined);
//
//
@@ -71,14 +67,16 @@ assert.writeOK(coll.insert({ _id : true, a : true, b : true }));
//
// Index without shard key query - not covered
assert.commandWorked(coll.ensureIndex({ a : 1 }));
-assert.eq(1, shardExplain(coll.find({ a : true }).explain()).totalDocsExamined);
-assert.eq(1, shardExplain(coll.find({ a : true }, { _id : 0, a : 1 }).explain()).totalDocsExamined);
+assert.eq(1, coll.find({ a : true }).explain(true).executionStats.totalDocsExamined);
+assert.eq(1, coll.find({ a : true }, { _id : 0, a : 1 })
+ .explain(true).executionStats.totalDocsExamined);
//
// Index with shard key query - can't be covered since hashed index
assert.commandWorked(coll.dropIndex({ a : 1 }));
-assert.eq(1, shardExplain(coll.find({ _id : true }).explain()).totalDocsExamined);
-assert.eq(1, shardExplain(coll.find({ _id : true }, { _id : 0 }).explain()).totalDocsExamined);
+assert.eq(1, coll.find({ _id : true }).explain(true).executionStats.totalDocsExamined);
+assert.eq(1, coll.find({ _id : true }, { _id : 0 })
+ .explain(true).executionStats.totalDocsExamined);
//
//
@@ -93,25 +91,25 @@ assert.writeOK(coll.insert({ _id : true, a : true, b : true, c : true, d : true
//
// Index without shard key query - not covered
assert.commandWorked(coll.ensureIndex({ c : 1 }));
-assert.eq(1, shardExplain(coll.find({ c : true }).explain()).totalDocsExamined);
-assert.eq(1, shardExplain(coll.find({ c : true }, { _id : 0, a : 1, b : 1, c : 1 })
- .explain()).totalDocsExamined);
+assert.eq(1, coll.find({ c : true }).explain(true).executionStats.totalDocsExamined);
+assert.eq(1, coll.find({ c : true }, { _id : 0, a : 1, b : 1, c : 1 })
+ .explain(true).executionStats.totalDocsExamined);
//
// Index with shard key query - covered when projecting
assert.commandWorked(coll.dropIndex({ c : 1 }));
assert.commandWorked(coll.ensureIndex({ c : 1, b : 1, a : 1 }));
-assert.eq(1, shardExplain(coll.find({ c : true }).explain()).totalDocsExamined);
-assert.eq(0, shardExplain(coll.find({ c : true }, { _id : 0, a : 1, b : 1, c : 1 })
- .explain()).totalDocsExamined);
+assert.eq(1, coll.find({ c : true }).explain(true).executionStats.totalDocsExamined);
+assert.eq(0, coll.find({ c : true }, { _id : 0, a : 1, b : 1, c : 1 })
+ .explain(true).executionStats.totalDocsExamined);
//
// Compound index with shard key query - covered when projecting
assert.commandWorked(coll.dropIndex({ c : 1, b : 1, a : 1 }));
assert.commandWorked(coll.ensureIndex({ c : 1, d : 1, a : 1, b : 1, _id : 1 }));
-assert.eq(1, shardExplain(coll.find({ c : true, d : true }).explain()).totalDocsExamined);
-assert.eq(0, shardExplain(coll.find({ c : true, d : true }, { a : 1, b : 1, c : 1, d : 1 })
- .explain()).totalDocsExamined);
+assert.eq(1, coll.find({ c : true, d : true }).explain(true).executionStats.totalDocsExamined);
+assert.eq(0, coll.find({ c : true, d : true }, { a : 1, b : 1, c : 1, d : 1 })
+ .explain(true).executionStats.totalDocsExamined);
//
//
@@ -126,17 +124,17 @@ assert.writeOK(coll.insert({ _id : true, a : { b : true }, c : true }));
//
// Index without shard key query - not covered
assert.commandWorked(coll.ensureIndex({ c : 1 }));
-assert.eq(1, shardExplain(coll.find({ c : true }).explain()).totalDocsExamined);
-assert.eq(1, shardExplain(coll.find({ c : true }, { _id : 0, 'a.b' : 1, c : 1 })
- .explain()).totalDocsExamined);
+assert.eq(1, coll.find({ c : true }).explain(true).executionStats.totalDocsExamined);
+assert.eq(1, coll.find({ c : true }, { _id : 0, 'a.b' : 1, c : 1 })
+ .explain(true).executionStats.totalDocsExamined);
//
// Index with shard key query - nested query not covered even when projecting
assert.commandWorked(coll.dropIndex({ c : 1 }));
assert.commandWorked(coll.ensureIndex({ c : 1, 'a.b' : 1 }));
-assert.eq(1, shardExplain(coll.find({ c : true }).explain()).totalDocsExamined);
-assert.eq(1, shardExplain(coll.find({ c : true }, { _id : 0, 'a.b' : 1, c : 1 })
- .explain()).totalDocsExamined);
+assert.eq(1, coll.find({ c : true }).explain(true).executionStats.totalDocsExamined);
+assert.eq(1, coll.find({ c : true }, { _id : 0, 'a.b' : 1, c : 1 })
+ .explain(true).executionStats.totalDocsExamined);
//
//
@@ -151,10 +149,10 @@ assert.writeOK(st.shard0.getCollection(coll.toString()).insert({ _id : "bad data
//
// Index without shard key query - not covered but succeeds
assert.commandWorked(coll.ensureIndex({ c : 1 }));
-var explain = shardExplain(coll.find({ c : true }).explain());
+var explain = coll.find({ c : true }).explain(true).executionStats;
assert.eq(0, explain.nReturned);
assert.eq(1, explain.totalDocsExamined);
-assert.eq(1, getChunkSkips(explain.executionStages));
+assert.eq(1, getChunkSkips(explain.executionStages.shards[0].executionStages));
//
// Index with shard key query - covered and succeeds and returns result
@@ -162,10 +160,10 @@ assert.eq(1, getChunkSkips(explain.executionStages));
// value for indexes
assert.commandWorked(coll.ensureIndex({ c : 1, a : 1 }));
jsTest.log(tojson(coll.find({ c : true }, { _id : 0, a : 1, c : 1 }).toArray()));
-var explain = shardExplain(coll.find({ c : true }, { _id : 0, a : 1, c : 1 }).explain());
+var explain = coll.find({ c : true }, { _id : 0, a : 1, c : 1 }).explain(true).executionStats;
assert.eq(1, explain.nReturned);
assert.eq(0, explain.totalDocsExamined);
-assert.eq(0, getChunkSkips(explain.executionStages));
+assert.eq(0, getChunkSkips(explain.executionStages.shards[0].executionStages));
jsTest.log("DONE!");
st.stop();
diff --git a/jstests/sharding/large_skip_one_shard.js b/jstests/sharding/large_skip_one_shard.js
index ec8f250de03..49e6551dec0 100644
--- a/jstests/sharding/large_skip_one_shard.js
+++ b/jstests/sharding/large_skip_one_shard.js
@@ -26,22 +26,11 @@ function testSelectWithSkip(coll){
}
// Run a query which only requires 5 results from a single shard
- var explain = coll.find({ _id : { $gt : 1 }}).sort({ _id : 1 }).skip(90).limit(5).explain();
- printjson(explain);
-
- if (explain.shards) {
- // We can't use Object.keys here, so a bit awkward to get the first key
- var shardEntry = null;
- var numEntries = 0;
- for (shardEntry in explain.shards) numEntries++;
- assert.eq(numEntries, 1);
-
- var shardExplain = explain.shards[shardEntry];
- assert.eq(shardExplain.length, 1);
- explain = shardExplain[0];
- }
+ var explain = coll.find({ _id : { $gt : 1 }}).sort({ _id : 1 })
+ .skip(90)
+ .limit(5)
+ .explain("executionStats");
- // What we're actually testing
assert.lt(explain.executionStats.nReturned, 90);
}
@@ -50,4 +39,3 @@ testSelectWithSkip(collUnSharded);
jsTest.log("DONE!");
st.stop();
-
diff --git a/jstests/sharding/limit_push.js b/jstests/sharding/limit_push.js
index d0bd259780b..ad9d8b9a383 100644
--- a/jstests/sharding/limit_push.js
+++ b/jstests/sharding/limit_push.js
@@ -35,17 +35,17 @@ assert.eq( 60 , db.limit_push.find( q ).count() , "Did not find 60 documents" );
// Now make sure that the explain shos that each shard is returning a single document as indicated
// by the "n" element for each shard
-exp = db.limit_push.find( q ).sort( { x:-1} ).limit(1).explain();
+exp = db.limit_push.find( q ).sort( { x:-1} ).limit(1).explain("executionStats");
printjson( exp )
-assert.eq("ParallelSort", exp.clusteredType, "Not a ParallelSort");
+var execStages = exp.executionStats.executionStages;
+assert.eq("SHARD_MERGE_SORT", execStages.stage, "Expected SHARD_MERGE_SORT as root stage");
var k = 0;
-for (var j in exp.shards) {
- assert.eq( 1 , exp.shards[j][0].executionStats.nReturned,
+for (var j in execStages.shards) {
+ assert.eq( 1 , execStages.shards[j].executionStages.nReturned,
"'n' is not 1 from shard000" + k.toString());
k++
}
s.stop();
-
diff --git a/jstests/sharding/read_pref.js b/jstests/sharding/read_pref.js
index 431fe0aa6d5..1d33866d432 100755
--- a/jstests/sharding/read_pref.js
+++ b/jstests/sharding/read_pref.js
@@ -108,27 +108,41 @@ var doTest = function(useDollarQuerySyntax) {
$explain: true }).limit(-1).next();
}
else {
- return coll.find().readPref(readPrefMode, readPrefTags).explain();
+ return coll.find().readPref(readPrefMode, readPrefTags).explain("executionStats");
}
};
+ var getExplainServer = function(explain) {
+ var serverInfo;
+
+ if (useDollarQuerySyntax) {
+ serverInfo = explain.serverInfo;
+ }
+ else {
+ assert.eq("SINGLE_SHARD", explain.queryPlanner.winningPlan.stage);
+ serverInfo = explain.queryPlanner.winningPlan.shards[0].serverInfo;
+ }
+
+ return serverInfo.host + ":" + serverInfo.port.toString();
+ };
+
// Read pref should work without slaveOk
var explain = getExplain("secondary");
- var explainServer = explain.serverInfo.host + ":" + explain.serverInfo.port.toString();
+ var explainServer = getExplainServer(explain);
assert.neq( primaryNode.name, explainServer );
conn.setSlaveOk();
// It should also work with slaveOk
explain = getExplain("secondary");
- explainServer = explain.serverInfo.host + ":" + explain.serverInfo.port.toString();
+ explainServer = getExplainServer(explain);
assert.neq( primaryNode.name, explainServer );
// Check that $readPreference does not influence the actual query
assert.eq( 1, explain.executionStats.nReturned );
explain = getExplain("secondaryPreferred", [{ s: "2" }]);
- explainServer = explain.serverInfo.host + ":" + explain.serverInfo.port.toString();
+ explainServer = getExplainServer(explain);
checkTag( explainServer, { s: "2" });
assert.eq( 1, explain.executionStats.nReturned );
@@ -138,23 +152,23 @@ var doTest = function(useDollarQuerySyntax) {
});
// Ok to use empty tags on primaryOnly
- explain = coll.find().readPref("primary", [{}]).explain();
- explainServer = explain.serverInfo.host + ":" + explain.serverInfo.port.toString();
+ explain = getExplain("primary", [{}]);
+ explainServer = getExplainServer(explain);
assert.eq(primaryNode.name, explainServer);
- explain = coll.find().readPref("primary", []).explain();
- explainServer = explain.serverInfo.host + ":" + explain.serverInfo.port.toString();
+ explain = getExplain("primary", []);
+ explainServer = getExplainServer(explain);
assert.eq(primaryNode.name, explainServer);
// Check that mongos will try the next tag if nothing matches the first
explain = getExplain("secondary", [{ z: "3" }, { dc: "jp" }]);
- explainServer = explain.serverInfo.host + ":" + explain.serverInfo.port.toString();
+ explainServer = getExplainServer(explain);
checkTag( explainServer, { dc: "jp" });
assert.eq( 1, explain.executionStats.nReturned );
// Check that mongos will fallback to primary if none of tags given matches
explain = getExplain("secondaryPreferred", [{ z: "3" }, { dc: "ph" }]);
- explainServer = explain.serverInfo.host + ":" + explain.serverInfo.port.toString();
+ explainServer = getExplainServer(explain);
// Call getPrimary again since the primary could have changed after the restart.
assert.eq(replTest.getPrimary().name, explainServer);
assert.eq( 1, explain.executionStats.nReturned );
@@ -178,7 +192,7 @@ var doTest = function(useDollarQuerySyntax) {
// Test to make sure that connection is ok, in prep for priOnly test
explain = getExplain("nearest");
- explainServer = explain.serverInfo.host + ":" + explain.serverInfo.port.toString();
+ explainServer = getExplainServer(explain);
assert.eq( explainServer, replTest.nodes[NODES - 1].name );
assert.eq( 1, explain.executionStats.nReturned );
@@ -192,4 +206,3 @@ var doTest = function(useDollarQuerySyntax) {
doTest(false);
doTest(true);
-
diff --git a/jstests/sharding/shard2.js b/jstests/sharding/shard2.js
index a229c4dc4b1..41b46158167 100644
--- a/jstests/sharding/shard2.js
+++ b/jstests/sharding/shard2.js
@@ -140,12 +140,13 @@ placeCheck( 7 );
db.foo.find().sort( { _id : 1 } ).forEach( function(z){ print( z._id ); } )
-zzz = db.foo.find().explain();
+zzz = db.foo.find().explain("executionStats").executionStats;
assert.eq( 0 , zzz.totalKeysExamined , "EX1a" )
assert.eq( 6 , zzz.nReturned , "EX1b" )
assert.eq( 6 , zzz.totalDocsExamined , "EX1c" )
-zzz = db.foo.find().hint( { _id : 1 } ).sort( { _id : 1 } ).explain();
+zzz = db.foo.find().hint( { _id : 1 } ).sort( { _id : 1 } )
+ .explain("executionStats").executionStats;
assert.eq( 6 , zzz.totalKeysExamined , "EX2a" )
assert.eq( 6 , zzz.nReturned , "EX2b" )
assert.eq( 6 , zzz.totalDocsExamined , "EX2c" )
diff --git a/jstests/sharding/shard3.js b/jstests/sharding/shard3.js
index 5ecf1fb8140..290349aee58 100644
--- a/jstests/sharding/shard3.js
+++ b/jstests/sharding/shard3.js
@@ -66,15 +66,15 @@ function doCounts( name , total , onlyItCounts ){
var total = doCounts( "before wrong save" )
assert.writeOK(secondary.insert( { _id : 111 , num : -3 } ));
doCounts( "after wrong save" , total , true )
-e = a.find().explain();
+e = a.find().explain("executionStats").executionStats;
assert.eq( 3 , e.nReturned , "ex1" )
assert.eq( 0 , e.totalKeysExamined , "ex2" )
assert.eq( 4 , e.totalDocsExamined , "ex3" )
var chunkSkips = 0;
-for (var shard in e.shards) {
- var theShard = e.shards[shard][0];
- chunkSkips += getChunkSkips(theShard.executionStats.executionStages);
+for (var shard in e.executionStages.shards) {
+ var theShard = e.executionStages.shards[shard];
+ chunkSkips += getChunkSkips(theShard.executionStages);
}
assert.eq( 1 , chunkSkips , "ex4" )
diff --git a/src/mongo/SConscript b/src/mongo/SConscript
index e69678711e9..a321e666b99 100644
--- a/src/mongo/SConscript
+++ b/src/mongo/SConscript
@@ -1160,6 +1160,8 @@ env.JSHeader(
"shell/bulk_api.js",
"shell/collection.js",
"shell/db.js",
+ "shell/explain_query.js",
+ "shell/explainable.js",
"shell/mongo.js",
"shell/mr.js",
"shell/query.js",
diff --git a/src/mongo/client/dbclient_rs.cpp b/src/mongo/client/dbclient_rs.cpp
index 7b5789c8a20..0ccac0f4df3 100644
--- a/src/mongo/client/dbclient_rs.cpp
+++ b/src/mongo/client/dbclient_rs.cpp
@@ -59,6 +59,8 @@ namespace {
_secOkCmdList.insert("count");
_secOkCmdList.insert("distinct");
_secOkCmdList.insert("dbStats");
+ _secOkCmdList.insert("explain");
+ _secOkCmdList.insert("find");
_secOkCmdList.insert("geoNear");
_secOkCmdList.insert("geoSearch");
_secOkCmdList.insert("geoWalk");
diff --git a/src/mongo/db/query/lite_parsed_query.cpp b/src/mongo/db/query/lite_parsed_query.cpp
index fe9c4383a3a..67019db5504 100644
--- a/src/mongo/db/query/lite_parsed_query.cpp
+++ b/src/mongo/db/query/lite_parsed_query.cpp
@@ -176,7 +176,7 @@ namespace mongo {
return status;
}
- pq->_wantMore = el.boolean();
+ pq->_wantMore = !el.boolean();
}
else if (mongoutils::str::equals(fieldName, "options")) {
Status status = checkFieldType(el, Object);
@@ -189,6 +189,9 @@ namespace mongo {
return parseStatus;
}
}
+ else if (mongoutils::str::equals(fieldName, "$readPreference")) {
+ pq->_options.hasReadPref = true;
+ }
else {
mongoutils::str::stream ss;
ss << "Failed to parse: " << cmdObj.toString() << ". "
diff --git a/src/mongo/db/query/lite_parsed_query_test.cpp b/src/mongo/db/query/lite_parsed_query_test.cpp
index db77954c551..aceea8c9933 100644
--- a/src/mongo/db/query/lite_parsed_query_test.cpp
+++ b/src/mongo/db/query/lite_parsed_query_test.cpp
@@ -404,7 +404,7 @@ namespace {
ASSERT_EQUALS(3, lpq->getLimit());
ASSERT_EQUALS(5, lpq->getSkip());
ASSERT_EQUALS(90, lpq->getBatchSize());
- ASSERT(!lpq->wantMore());
+ ASSERT(lpq->wantMore());
}
//
diff --git a/src/mongo/s/cluster_explain.cpp b/src/mongo/s/cluster_explain.cpp
index 5927ece9feb..6e0a928f85d 100644
--- a/src/mongo/s/cluster_explain.cpp
+++ b/src/mongo/s/cluster_explain.cpp
@@ -107,6 +107,11 @@ namespace mongo {
BSONObjBuilder* out) {
out->append("explain", cmdObj);
out->append("verbosity", ExplainCommon::verbosityString(verbosity));
+
+ // If the command has a readPreference, then pull it up to the top level.
+ if (cmdObj.hasField("$readPreference")) {
+ out->append("$queryOptions", cmdObj["$readPreference"].wrap());
+ }
}
// static
@@ -193,6 +198,8 @@ namespace mongo {
BSONObj serverInfo = shardResults[i].result["serverInfo"].Obj();
singleShardBob.append("shardName", shardResults[i].shardTarget.getName());
+ std::string connStr = shardResults[i].shardTarget.getAddress().toString();
+ singleShardBob.append("connectionString", connStr);
appendIfRoom(&singleShardBob, serverInfo, "serverInfo");
appendElementsIfRoom(&singleShardBob, queryPlanner);
diff --git a/src/mongo/s/commands/cluster_find_cmd.cpp b/src/mongo/s/commands/cluster_find_cmd.cpp
index ca82e17ce3a..f64816d2c03 100644
--- a/src/mongo/s/commands/cluster_find_cmd.cpp
+++ b/src/mongo/s/commands/cluster_find_cmd.cpp
@@ -77,7 +77,7 @@ namespace mongo {
vector<Strategy::CommandResult> shardResults;
STRATEGY->commandOp(dbname,
explainCmdBob.obj(),
- 0,
+ lpq->getOptions().toInt(),
fullns,
lpq->getFilter(),
&shardResults);
diff --git a/src/mongo/scripting/engine.cpp b/src/mongo/scripting/engine.cpp
index 57b62f7931c..61ba9e0ef98 100644
--- a/src/mongo/scripting/engine.cpp
+++ b/src/mongo/scripting/engine.cpp
@@ -262,6 +262,8 @@ namespace {
namespace JSFiles {
extern const JSFile collection;
extern const JSFile db;
+ extern const JSFile explain_query;
+ extern const JSFile explainable;
extern const JSFile mongo;
extern const JSFile mr;
extern const JSFile query;
@@ -282,6 +284,8 @@ namespace {
execSetup(JSFiles::query);
execSetup(JSFiles::bulk_api);
execSetup(JSFiles::collection);
+ execSetup(JSFiles::explain_query);
+ execSetup(JSFiles::explainable);
execSetup(JSFiles::upgrade_check);
}
diff --git a/src/mongo/shell/bulk_api.js b/src/mongo/shell/bulk_api.js
index 6d056064f81..df2c868fa5c 100644
--- a/src/mongo/shell/bulk_api.js
+++ b/src/mongo/shell/bulk_api.js
@@ -810,12 +810,11 @@ var _bulk_api_module = (function() {
bulkResult.writeConcernErrors.push(new WriteConcernError(result.writeConcernError));
}
}
-
+
//
- // Execute the batch
- var executeBatch = function(batch) {
+ // Constructs the write batch command.
+ var buildBatchCmd = function(batch) {
var cmd = null;
- var result = null;
// Generate the right update
if(batch.batchType == UPDATE) {
@@ -837,6 +836,15 @@ var _bulk_api_module = (function() {
cmd.writeConcern = writeConcern;
}
+ return cmd;
+ }
+
+ //
+ // Execute the batch
+ var executeBatch = function(batch) {
+ var result = null;
+ var cmd = buildBatchCmd(batch);
+
// Run the command (may throw)
// Get command collection
@@ -1156,6 +1164,24 @@ var _bulk_api_module = (function() {
return typedResult;
}
+
+ // Generate an explain command for the bulk operation. Currently we only support single batches
+ // of size 1, which must be either delete or update.
+ this.convertToExplainCmd = function(verbosity) {
+ // If we have current batch
+ if (currentBatch) {
+ batches.push(currentBatch);
+ }
+
+ // We can only explain singleton batches.
+ if (batches.length !== 1) {
+ throw Error("Explained bulk operations must consist of exactly 1 batch");
+ }
+
+ var explainBatch = batches[0];
+ var writeCmd = buildBatchCmd(explainBatch);
+ return {"explain": writeCmd, "verbosity": verbosity};
+ }
}
//
diff --git a/src/mongo/shell/collection.js b/src/mongo/shell/collection.js
index 380563d8533..0d346ef4c27 100644
--- a/src/mongo/shell/collection.js
+++ b/src/mongo/shell/collection.js
@@ -295,24 +295,45 @@ DBCollection.prototype._validateRemoveDoc = function(doc) {
}
};
-DBCollection.prototype.remove = function( t , justOne ){
- if (t == undefined) throw Error("remove needs a query");
- var result = undefined;
- var startTime = (typeof(_verboseShell) === 'undefined' ||
- !_verboseShell) ? 0 : new Date().getTime();
+/**
+ * Does validation of the remove args. Throws if the parse is not successful, otherwise
+ * returns a document {query: <query>, justOne: <limit>, wc: <writeConcern>}.
+ */
+DBCollection.prototype._parseRemove = function( t , justOne ) {
+ if (undefined === t) throw Error("remove needs a query");
+
+ var query = this._massageObject(t);
var wc = undefined;
- if ( typeof(justOne) === 'object' ) {
+ if (typeof(justOne) === "object") {
var opts = justOne;
wc = opts.writeConcern;
justOne = opts.justOne;
}
- if (!wc)
+ // Normalize "justOne" to a bool.
+ justOne = justOne ? true : false;
+
+ // Handle write concern.
+ if (!wc) {
wc = this.getWriteConcern();
+ }
+
+ return {"query": query, "justOne": justOne, "wc": wc};
+}
+
+DBCollection.prototype.remove = function( t , justOne ){
+ var parsed = this._parseRemove(t, justOne);
+ var query = parsed.query;
+ var justOne = parsed.justOne;
+ var wc = parsed.wc;
+
+ var result = undefined;
+ var startTime = (typeof(_verboseShell) === 'undefined' ||
+ !_verboseShell) ? 0 : new Date().getTime();
+
if ( this.getMongo().writeMode() != "legacy" ) {
- var query = (typeof(t) == 'undefined')? {} : this._massageObject(t);
var bulk = this.initializeOrderedBulkOp();
var removeOp = bulk.find(query);
@@ -338,8 +359,8 @@ DBCollection.prototype.remove = function( t , justOne ){
}
else {
this._validateRemoveDoc(t);
- this.getMongo().remove(this._fullName, this._massageObject(t), justOne ? true : false );
-
+ this.getMongo().remove(this._fullName, query, justOne );
+
// enforce write concern, if required
if (wc)
result = this.runCommand("getLastError", wc instanceof WriteConcern ? wc.toJSON() : wc);
@@ -366,15 +387,23 @@ DBCollection.prototype._validateUpdateDoc = function(doc) {
}
};
-DBCollection.prototype.update = function( query , obj , upsert , multi ){
- assert( query , "need a query" );
- assert( obj , "need an object" );
+/**
+ * Does validation of the update args. Throws if the parse is not successful, otherwise
+ * returns a document containing fields for query, obj, upsert, multi, and wc.
+ *
+ * Throws if the arguments are invalid.
+ */
+DBCollection.prototype._parseUpdate = function( query , obj , upsert , multi ){
+ if (!query) throw Error("need a query");
+ if (!obj) throw Error("need an object");
var wc = undefined;
// can pass options via object for improved readability
- if ( typeof(upsert) === 'object' ) {
- assert( multi === undefined,
- "Fourth argument must be empty when specifying upsert and multi with an object." );
+ if ( typeof(upsert) === "object" ) {
+ if (multi) {
+ throw Error("Fourth argument must be empty when specifying " +
+ "upsert and multi with an object.");
+ }
var opts = upsert;
multi = opts.multi;
@@ -382,13 +411,33 @@ DBCollection.prototype.update = function( query , obj , upsert , multi ){
upsert = opts.upsert;
}
+ // Normalize 'upsert' and 'multi' to booleans.
+ upsert = upsert ? true : false;
+ multi = multi ? true : false;
+
+ if (!wc) {
+ wc = this.getWriteConcern();
+ }
+
+ return {"query": query,
+ "obj": obj,
+ "upsert": upsert,
+ "multi": multi,
+ "wc": wc};
+}
+
+DBCollection.prototype.update = function( query , obj , upsert , multi ){
+ var parsed = this._parseUpdate(query, obj, upsert, multi);
+ var query = parsed.query;
+ var obj = parsed.obj;
+ var upsert = parsed.upsert;
+ var multi = parsed.multi;
+ var wc = parsed.wc;
+
var result = undefined;
var startTime = (typeof(_verboseShell) === 'undefined' ||
!_verboseShell) ? 0 : new Date().getTime();
- if (!wc)
- wc = this.getWriteConcern();
-
if ( this.getMongo().writeMode() != "legacy" ) {
var bulk = this.initializeOrderedBulkOp();
var updateOp = bulk.find(query);
@@ -419,8 +468,7 @@ DBCollection.prototype.update = function( query , obj , upsert , multi ){
}
else {
this._validateUpdateDoc(obj);
- this.getMongo().update(this._fullName, query, obj,
- upsert ? true : false, multi ? true : false );
+ this.getMongo().update(this._fullName, query, obj, upsert, multi);
// enforce write concern, if required
if (wc)
diff --git a/src/mongo/shell/explain_query.js b/src/mongo/shell/explain_query.js
new file mode 100644
index 00000000000..b58cfc9b917
--- /dev/null
+++ b/src/mongo/shell/explain_query.js
@@ -0,0 +1,222 @@
+//
+// A DBQuery which is explained rather than executed normally. Also could be thought of as
+// an "explainable cursor". Explains of .find() operations run through this abstraction.
+//
+
+var DBExplainQuery = (function() {
+
+ //
+ // Private methods.
+ //
+
+ /**
+ * In 2.6 and before, .explain(), .explain(false), or .explain(<falsy value>) instructed the
+ * shell to reduce the explain verbosity by removing certain fields from the output. This
+ * is implemented here for backwards compatibility.
+ */
+ function removeVerboseFields(obj) {
+ if (typeof(obj) !== "object") {
+ return;
+ }
+
+ delete obj.allPlans;
+ delete obj.oldPlan;
+ delete obj.stats;
+
+ if (typeof(obj.length) === "number") {
+ for (var i=0; i < obj.length; i++) {
+ removeVerboseFields(obj[i]);
+ }
+ }
+
+ if (obj.shards){
+ for (var key in obj.shards) {
+ removeVerboseFields(obj.shards[key]);
+ }
+ }
+
+ if (obj.clauses) {
+ removeVerboseFields(obj.clauses);
+ }
+ }
+
+ /**
+ * Many of the methods of an explain query just pass through to the underlying
+ * non-explained DBQuery. Use this to generate a function which calls function 'name' on
+ * 'destObj' and then returns this.
+ */
+ function createDelegationFunc(explainQuery, dbQuery, name) {
+ return function() {
+ dbQuery[name].apply(dbQuery, arguments);
+ return explainQuery;
+ }
+ }
+
+ function constructor(query, verbosity) {
+
+ //
+ // Private vars.
+ //
+
+ this._query = query;
+ this._verbosity = Explainable.parseVerbosity(verbosity);
+ this._mongo = query._mongo;
+ this._finished = false;
+
+ // Used if this query is a count, not a find.
+ this._isCount = false;
+ this._applySkipLimit = false;
+
+ //
+ // Public delegation methods. These just pass through to the underlying
+ // DBQuery.
+ //
+
+ var delegationFuncNames = [
+ "addOption",
+ "batchSize",
+ "comment",
+ "hint",
+ "limit",
+ "max",
+ "maxTimeMS",
+ "min",
+ "readPref",
+ "showDiskLoc",
+ "skip",
+ "snapshot",
+ "sort",
+ ];
+
+ // Generate the delegation methods from the list of their names.
+ var that = this;
+ delegationFuncNames.forEach(function(name) {
+ that[name] = createDelegationFunc(that, that._query, name);
+ });
+
+ //
+ // Core public methods.
+ //
+
+ /**
+ * Indicates that we are done building the query to explain, and sends the explain
+ * command or query to the server.
+ *
+ * Returns the result of running the explain.
+ */
+ this.finish = function() {
+ if (this._finished) {
+ throw Error("query has already been explained");
+ }
+
+ // Mark this query as finished. Shouldn't be used for another explain.
+ this._finished = true;
+
+ // Explain always gets pretty printed.
+ this._query._prettyShell = true;
+
+ // Explain always passes a negative value for limit.
+ this._query._limit = Math.abs(this._query._limit) * -1;
+
+ if (this._mongo.hasExplainCommand()) {
+ // The wire protocol version indicates that the server has the explain command.
+ // Convert this explain query into an explain command, and send the command to
+ // the server.
+ var innerCmd;
+ if (this._isCount) {
+ // True means to always apply the skip and limit values.
+ innerCmd = this._query._convertToCountCmd(this._applySkipLimit);
+ }
+ else {
+ innerCmd = this._query._convertToCommand();
+ }
+
+ var explainCmd = {explain: innerCmd};
+ explainCmd["verbosity"] = this._verbosity;
+
+ var explainResult = this._query._db.runCommand(explainCmd);
+ return Explainable.throwOrReturn(explainResult);
+ }
+ else {
+ // The wire protocol version indicates that the server does not have the explain
+ // command. Add $explain to the query and send it to the server.
+ var clone = this._query.clone();
+ clone._addSpecial("$explain", true);
+ var result = clone.next();
+
+ // Remove some fields from the explain if verbosity is
+ // just "queryPlanner".
+ if ("queryPlanner" === this._verbosity) {
+ removeVerboseFields(result);
+ }
+
+ return Explainable.throwOrReturn(result);
+ }
+ }
+
+ this.next = function() {
+ return this.finish();
+ }
+
+ this.hasNext = function() {
+ return !this._finished;
+ }
+
+ this.forEach = function(func) {
+ while (this.hasNext()) {
+ func(this.next());
+ }
+ }
+
+ /**
+ * Mark this query as a count rather than a regular query. This causes .finish() to
+ * create an explain of a count command rather than an explain of a find command.
+ */
+ this.count = function(applySkipLimit) {
+ this._isCount = true;
+ if (applySkipLimit) {
+ this._applySkipLimit = true;
+ }
+ return this;
+ }
+
+ /**
+ * This gets called automatically by the shell in interactive mode. It should
+ * print the result of running the explain.
+ */
+ this.shellPrint = function() {
+ var result = this.finish();
+ return tojson(result);
+ }
+
+ /**
+ * Display help text.
+ */
+ this.help = function() {
+ print("Explain query methods");
+ print("\t.finish() - sends explain command to the server and returns the result");
+ print("\t.forEach(func) - apply a function to the explain results");
+ print("\t.hasNext() - whether this explain query still has a result to retrieve");
+ print("\t.next() - alias for .finish()");
+ print("Explain query modifiers");
+ print("\t.addOption(n)");
+ print("\t.batchSize(n)");
+ print("\t.comment(comment)");
+ print("\t.count()");
+ print("\t.hint(hintSpec)");
+ print("\t.limit(n)");
+ print("\t.maxTimeMS(n)");
+ print("\t.max(idxDoc)");
+ print("\t.min(idxDoc)");
+ print("\t.readPref(mode, tagSet)");
+ print("\t.showDiskLoc()");
+ print("\t.skip(n)");
+ print("\t.snapshot()");
+ print("\t.sort(sortSpec)");
+ return __magicNoPrint;
+ }
+
+ }
+
+ return constructor;
+})();
diff --git a/src/mongo/shell/explainable.js b/src/mongo/shell/explainable.js
new file mode 100644
index 00000000000..70f3540842f
--- /dev/null
+++ b/src/mongo/shell/explainable.js
@@ -0,0 +1,199 @@
+//
+// A view of a collection against which operations are explained rather than executed
+// normally.
+//
+
+var Explainable = (function() {
+
+ var parseVerbosity = function(verbosity) {
+ // Truthy non-strings are interpreted as "allPlansExecution" verbosity.
+ if (verbosity && (typeof verbosity !== "string")) {
+ return "allPlansExecution";
+ }
+
+ // Falsy non-strings are interpreted as "queryPlanner" verbosity.
+ if (!verbosity && (typeof verbosity !== "string")) {
+ return "queryPlanner";
+ }
+
+ // If we're here, then the verbosity is a string. We reject invalid strings.
+ if (verbosity !== "queryPlanner" &&
+ verbosity !== "executionStats" &&
+ verbosity !== "allPlansExecution") {
+ throw Error("explain verbosity must be one of {" +
+ "'queryPlanner'," +
+ "'executionStats'," +
+ "'allPlansExecution'}");
+ }
+
+ return verbosity;
+ }
+
+ var throwOrReturn = function(explainResult) {
+ if (("ok" in explainResult && !explainResult.ok) || explainResult.$err) {
+ throw Error("explain failed: " + tojson(explainResult));
+ }
+
+ return explainResult;
+ }
+
+ function constructor(collection, verbosity) {
+
+ //
+ // Private vars.
+ //
+
+ this._collection = collection;
+ this._verbosity = parseVerbosity(verbosity);
+
+ //
+ // Public methods.
+ //
+
+ this.getCollection = function() {
+ return this._collection;
+ }
+
+ this.getVerbosity = function() {
+ return this._verbosity;
+ }
+
+ this.setVerbosity = function(verbosity) {
+ this._verbosity = parseVerbosity(verbosity);
+ return this;
+ }
+
+ this.help = function() {
+ print("Explainable operations");
+ print("\t.aggregate(...) - explain an aggregation operation");
+ print("\t.count(...) - explain a count operation");
+ print("\t.find(...) - get an explainable query");
+ print("\t.group(...) - explain a group operation");
+ print("\t.remove(...) - explain a remove operation");
+ print("\t.update(...) - explain an update operation");
+ print("Explainable collection methods");
+ print("\t.getCollection()");
+ print("\t.getVerbosity()");
+ print("\t.setVerbosity(verbosity)");
+ return __magicNoPrint;
+ }
+
+ //
+ // Pretty representations.
+ //
+
+ this.toString = function() {
+ return "Explainable(" + this._collection.getFullName() + ")";
+ };
+
+ this.shellPrint = function() {
+ return this.toString();
+ };
+
+ //
+ // Explainable operations.
+ //
+
+ /**
+ * Adds "explain: true" to "extraOpts", and then passes through to the regular collection's
+ * aggregate helper.
+ */
+ this.aggregate = function(pipeline, extraOpts) {
+ if (!(pipeline instanceof Array)) {
+ // support legacy varargs form. (Also handles db.foo.aggregate())
+ pipeline = argumentsToArray(arguments)
+ extraOpts = {}
+ }
+
+ // Add the explain option.
+ extraOpts = extraOpts || {};
+ extraOpts.explain = true;
+
+ return this._collection.aggregate(pipeline, extraOpts);
+ }
+
+ this.count = function(query) {
+ return this.find(query).count();
+ }
+
+ /**
+ * .explain().find() and .find().explain() mean the same thing. In both cases, we use
+ * the DBExplainQuery abstraction in order to construct the proper explain command to send
+ * to the server.
+ */
+ this.find = function() {
+ var cursor = this._collection.find.apply(this._collection, arguments);
+ return new DBExplainQuery(cursor, this._verbosity);
+ }
+
+ this.group = function(params) {
+ params.ns = this._collection.getName();
+ var grpCmd = {"group": this._collection.getDB()._groupFixParms(params)};
+ var explainCmd = {"explain": grpCmd, "verbosity": this._verbosity};
+ var explainResult = this._collection.runCommand(explainCmd);
+ return throwOrReturn(explainResult);
+ }
+
+ this.remove = function() {
+ var parsed = this._collection._parseRemove.apply(this._collection, arguments);
+ var query = parsed.query;
+ var justOne = parsed.justOne;
+
+ var bulk = this._collection.initializeOrderedBulkOp();
+ var removeOp = bulk.find(query);
+ if (justOne) {
+ removeOp.removeOne();
+ }
+ else {
+ removeOp.remove();
+ }
+
+ var explainCmd = bulk.convertToExplainCmd(this._verbosity);
+ var explainResult = this._collection.runCommand(explainCmd);
+ return throwOrReturn(explainResult);
+ }
+
+ this.update = function() {
+ var parsed = this._collection._parseUpdate.apply(this._collection, arguments);
+ var query = parsed.query;
+ var obj = parsed.obj;
+ var upsert = parsed.upsert;
+ var multi = parsed.multi;
+
+ var bulk = this._collection.initializeOrderedBulkOp();
+ var updateOp = bulk.find(query);
+
+ if (upsert) {
+ updateOp = updateOp.upsert();
+ }
+
+ if (multi) {
+ updateOp.update(obj);
+ }
+ else {
+ updateOp.updateOne(obj);
+ }
+
+ var explainCmd = bulk.convertToExplainCmd(this._verbosity);
+ var explainResult = this._collection.runCommand(explainCmd);
+ return throwOrReturn(explainResult);
+ }
+
+ }
+
+ //
+ // Public static methods.
+ //
+
+ constructor.parseVerbosity = parseVerbosity;
+ constructor.throwOrReturn = throwOrReturn;
+
+ return constructor;
+})();
+
+/**
+ * This is the user-facing method for creating an Explainable from a collection.
+ */
+DBCollection.prototype.explain = function(verbosity) {
+ return new Explainable(this, verbosity);
+};
diff --git a/src/mongo/shell/mongo.js b/src/mongo/shell/mongo.js
index a28e3b2b692..8b0d3191414 100644
--- a/src/mongo/shell/mongo.js
+++ b/src/mongo/shell/mongo.js
@@ -178,6 +178,18 @@ Mongo.prototype.hasWriteCommands = function() {
return this._hasWriteCommands;
}
+Mongo.prototype.hasExplainCommand = function() {
+ if ( !('_hasExplainCommand' in this) ) {
+ var isMaster = this.getDB("admin").runCommand({ isMaster : 1 });
+ this._hasExplainCommand = (isMaster.ok &&
+ 'minWireVersion' in isMaster &&
+ isMaster.minWireVersion <= 3 &&
+ 3 <= isMaster.maxWireVersion );
+ }
+
+ return this._hasExplainCommand;
+}
+
/**
* {String} Returns the current mode set. Will be commands/legacy/compatibility
*
diff --git a/src/mongo/shell/query.js b/src/mongo/shell/query.js
index 304a96c84ad..0c1bd14779e 100644
--- a/src/mongo/shell/query.js
+++ b/src/mongo/shell/query.js
@@ -86,6 +86,132 @@ DBQuery.prototype._exec = function(){
return this._cursor;
}
+/**
+ * Helper for _convertToCommand() which constructs the "options" part of the find command.
+ */
+DBQuery.prototype._buildCmdOptions = function() {
+ var options = {};
+
+ if (this._query.$comment) {
+ options["comment"] = this._query.$comment;
+ }
+
+ if (this._query.$maxScan) {
+ options["maxScan"] = this._query.$maxScan;
+ }
+
+ if (this._query.$maxTimeMS) {
+ options["maxTimeMS"] = this._query.$maxTimeMS;
+ }
+
+ if (this._query.$max) {
+ options["max"] = this._query.$max;
+ }
+
+ if (this._query.$min) {
+ options["min"] = this._query.$min;
+ }
+
+ if (this._query.$returnKey) {
+ options["returnKey"] = this._query.$returnKey;
+ }
+
+ if (this._query.$showDiskLoc) {
+ options["showDiskLoc"] = this._query.$showDiskLoc;
+ }
+
+ if (this._query.$snapshot) {
+ options["snapshot"] = this._query.$snapshot;
+ }
+
+ if ((this._options & DBQuery.Option.tailable) != 0) {
+ options["tailable"] = true;
+ }
+
+ if ((this._options & DBQuery.Option.slaveOk) != 0) {
+ options["slaveOk"] = true;
+ }
+
+ if ((this._options & DBQuery.Option.oplogReplay) != 0) {
+ options["oplogReplay"] = true;
+ }
+
+ if ((this._options & DBQuery.Option.noTimeout) != 0) {
+ options["noCursorTimeout"] = true;
+ }
+
+ if ((this._options & DBQuery.Option.awaitData) != 0) {
+ options["awaitData"] = true;
+ }
+
+ if ((this._options & DBQuery.Option.exhaust) != 0) {
+ options["exhaust"] = true;
+ }
+
+ if ((this._options & DBQuery.Option.partial) != 0) {
+ options["partial"] = true;
+ }
+
+ return options;
+}
+
+/**
+ * Internal helper used to convert this cursor into the format required by the find command.
+ */
+DBQuery.prototype._convertToCommand = function() {
+ var cmd = {};
+
+ cmd["find"] = this._collection.getName();
+
+ if (this._special) {
+ if (this._query.query) {
+ cmd["query"] = this._query.query;
+ }
+ }
+ else if (this._query) {
+ cmd["query"] = this._query;
+ }
+
+ if (this._skip) {
+ cmd["skip"] = this._skip
+ }
+
+ if (this._batchSize) {
+ cmd["batchSize"] = this._batchSize || 101;
+ }
+
+ if (this._limit) {
+ if (this._limit < 0) {
+ cmd["limit"] = -this._limit;
+ cmd["singleBatch"] = true;
+ }
+ else {
+ cmd["limit"] = this._limit;
+ cmd["singleBatch"] = false;
+ }
+ }
+
+ if (this._query.orderby) {
+ cmd["sort"] = this._query.orderby;
+ }
+
+ if (this._fields) {
+ cmd["projection"] = this._fields;
+ }
+
+ if (this._query.$hint) {
+ cmd["hint"] = this._query.$hint;
+ }
+
+ if (this._query.$readPreference) {
+ cmd["$readPreference"] = this._query.$readPreference;
+ }
+
+ cmd["options"] = this._buildCmdOptions();
+
+ return cmd;
+}
+
DBQuery.prototype.limit = function( limit ){
this._checkModify();
this._limit = limit;
@@ -163,8 +289,9 @@ DBQuery.prototype.toArray = function(){
return a;
}
-DBQuery.prototype.count = function( applySkipLimit ) {
+DBQuery.prototype._convertToCountCmd = function( applySkipLimit ) {
var cmd = { count: this._collection.getName() };
+
if ( this._query ) {
if ( this._special ) {
cmd.query = this._query.query;
@@ -187,7 +314,13 @@ DBQuery.prototype.count = function( applySkipLimit ) {
if ( this._skip )
cmd.skip = this._skip;
}
-
+
+ return cmd;
+}
+
+DBQuery.prototype.count = function( applySkipLimit ) {
+ var cmd = this._convertToCountCmd( applySkipLimit );
+
var res = this._db.runCommand( cmd );
if( res && res.n != null ) return res.n;
throw Error( "count failed: " + tojson( res ) );
@@ -297,42 +430,8 @@ DBQuery.prototype.comment = function (comment) {
}
DBQuery.prototype.explain = function (verbose) {
- /* verbose=true --> include allPlans, oldPlan fields */
- var n = this.clone();
- n._addSpecial( "$explain", true );
- n._limit = Math.abs(n._limit) * -1;
- var e = n.next();
-
- function cleanup(obj){
- if (typeof(obj) != 'object'){
- return;
- }
-
- delete obj.allPlans;
- delete obj.oldPlan;
- delete obj.stats;
-
- if (typeof(obj.length) == 'number'){
- for (var i=0; i < obj.length; i++){
- cleanup(obj[i]);
- }
- }
-
- if (obj.shards){
- for (var key in obj.shards){
- cleanup(obj.shards[key]);
- }
- }
-
- if (obj.clauses){
- cleanup(obj.clauses);
- }
- }
-
- if (!verbose)
- cleanup(e);
-
- return e;
+ var explainQuery = new DBExplainQuery(this, verbose);
+ return explainQuery.finish();
}
DBQuery.prototype.snapshot = function(){
diff --git a/src/mongo/shell/shardingtest.js b/src/mongo/shell/shardingtest.js
index 0b483e1deea..3a9303d66ac 100644
--- a/src/mongo/shell/shardingtest.js
+++ b/src/mongo/shell/shardingtest.js
@@ -949,16 +949,18 @@ ShardingTest.prototype.getShards = function( coll, query, includeEmpty ){
if( ! coll.getDB )
coll = this.s.getCollection( coll )
- var explain = coll.find( query ).explain()
+ var explain = coll.find( query ).explain("executionStats")
var shards = []
- if( explain.shards ){
- for( var shardName in explain.shards ){
- for( var i = 0; i < explain.shards[shardName].length; i++ ){
- var hasResults = explain.shards[shardName][i].executionStats.nReturned &&
- explain.shards[shardName][i].executionStats.nReturned > 0;
- if( includeEmpty || hasResults )
- shards.push( shardName )
+ var execStages = explain.executionStats.executionStages;
+ var plannerShards = explain.queryPlanner.winningPlan.shards;
+
+ if( execStages.shards ){
+ for( var i = 0; i < execStages.shards.length; i++ ){
+ var hasResults = execStages.shards[i].executionStages.nReturned &&
+ execStages.shards[i].executionStages.nReturned > 0;
+ if( includeEmpty || hasResults ){
+ shards.push(plannerShards[i].connectionString);
}
}
}