diff options
author | David Storch <david.storch@10gen.com> | 2014-10-07 18:23:21 -0400 |
---|---|---|
committer | David Storch <david.storch@10gen.com> | 2014-10-13 19:59:21 -0400 |
commit | d601b91b6b16be3f93bac2f10952c1e5d273f91f (patch) | |
tree | f8b7daf9d3920ded5567489d0d65df50afed9542 | |
parent | e7a49e50e5a858b02c9c242c943d7559238bb2b6 (diff) | |
download | mongo-d601b91b6b16be3f93bac2f10952c1e5d273f91f.tar.gz |
SERVER-14875 explain helpers for the shell
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); } } } |