summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Percy <david.percy@mongodb.com>2019-11-11 18:50:37 +0000
committerevergreen <evergreen@mongodb.com>2019-11-11 18:50:37 +0000
commit2411853a626e2c28e8bc54c82490cbb2ab9947a0 (patch)
tree1fde60264e4ccfecaded6cbfa8aa15cd26e3951e
parent2b64ed2d9049a10cf105f3fb1258062678bfd682 (diff)
downloadmongo-2411853a626e2c28e8bc54c82490cbb2ab9947a0.tar.gz
SERVER-14643 Return informative Status from QueryPlanner::plan
-rw-r--r--buildscripts/resmokeconfig/suites/logical_session_cache_sharding_100ms_refresh_jscore_passthrough.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/logical_session_cache_sharding_10sec_refresh_jscore_passthrough.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/logical_session_cache_sharding_1sec_refresh_jscore_passthrough.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/logical_session_cache_sharding_default_refresh_jscore_passthrough.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/multi_shard_local_read_write_multi_stmt_txn_jscore_passthrough.yml2
-rw-r--r--buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_jscore_passthrough.yml2
-rw-r--r--buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_kill_primary_jscore_passthrough.yml2
-rw-r--r--buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_stepdown_primary_jscore_passthrough.yml2
-rw-r--r--buildscripts/resmokeconfig/suites/multi_stmt_txn_jscore_passthrough_with_migration.yml2
-rw-r--r--buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_kill_primary_jscore_passthrough.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_stepdown_jscore_passthrough.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_terminate_primary_jscore_passthrough.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/sharded_collections_jscore_passthrough.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/sharded_multi_stmt_txn_jscore_passthrough.yml2
-rw-r--r--buildscripts/resmokeconfig/suites/sharding_jscore_op_query_passthrough.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/sharding_jscore_passthrough.yml1
-rw-r--r--jstests/core/notablescan.js19
-rw-r--r--jstests/core/notablescan_capped.js32
-rw-r--r--src/mongo/db/exec/cached_plan.cpp9
-rw-r--r--src/mongo/db/exec/subplan.cpp16
-rw-r--r--src/mongo/db/query/get_executor.cpp10
-rw-r--r--src/mongo/db/query/query_planner.cpp104
-rw-r--r--src/mongo/db/query/query_planner_array_test.cpp5
-rw-r--r--src/mongo/db/query/query_planner_geo_test.cpp30
-rw-r--r--src/mongo/db/query/query_planner_index_test.cpp9
-rw-r--r--src/mongo/db/query/query_planner_partialidx_test.cpp120
-rw-r--r--src/mongo/db/query/query_planner_test_fixture.cpp11
-rw-r--r--src/mongo/db/query/query_planner_test_fixture.h6
-rw-r--r--src/mongo/db/query/query_planner_text_test.cpp9
-rw-r--r--src/mongo/db/query/query_planner_wildcard_index_test.cpp12
-rw-r--r--src/mongo/s/chunk_manager.cpp26
31 files changed, 249 insertions, 191 deletions
diff --git a/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_100ms_refresh_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_100ms_refresh_jscore_passthrough.yml
index 2637e1f0042..980e9176b70 100644
--- a/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_100ms_refresh_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_100ms_refresh_jscore_passthrough.yml
@@ -29,7 +29,6 @@ selector:
- jstests/core/geo_update_btree2.js # notablescan.
- jstests/core/index_id_options.js # "local" database.
- jstests/core/index9.js # "local" database.
- - jstests/core/notablescan.js # notablescan.
- jstests/core/queryoptimizera.js # "local" database.
- jstests/core/rename*.js # renameCollection.
- jstests/core/stages*.js # stageDebug.
diff --git a/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_10sec_refresh_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_10sec_refresh_jscore_passthrough.yml
index 646c3262ad2..4a0763a61c7 100644
--- a/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_10sec_refresh_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_10sec_refresh_jscore_passthrough.yml
@@ -29,7 +29,6 @@ selector:
- jstests/core/geo_update_btree2.js # notablescan.
- jstests/core/index_id_options.js # "local" database.
- jstests/core/index9.js # "local" database.
- - jstests/core/notablescan.js # notablescan.
- jstests/core/queryoptimizera.js # "local" database.
- jstests/core/rename*.js # renameCollection.
- jstests/core/stages*.js # stageDebug.
diff --git a/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_1sec_refresh_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_1sec_refresh_jscore_passthrough.yml
index 3df4bb4b236..c9083725495 100644
--- a/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_1sec_refresh_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_1sec_refresh_jscore_passthrough.yml
@@ -29,7 +29,6 @@ selector:
- jstests/core/geo_update_btree2.js # notablescan.
- jstests/core/index_id_options.js # "local" database.
- jstests/core/index9.js # "local" database.
- - jstests/core/notablescan.js # notablescan.
- jstests/core/queryoptimizera.js # "local" database.
- jstests/core/rename*.js # renameCollection.
- jstests/core/stages*.js # stageDebug.
diff --git a/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_default_refresh_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_default_refresh_jscore_passthrough.yml
index b89a3fe21f4..d9f9d1ec2d9 100644
--- a/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_default_refresh_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_default_refresh_jscore_passthrough.yml
@@ -29,7 +29,6 @@ selector:
- jstests/core/geo_update_btree2.js # notablescan.
- jstests/core/index_id_options.js # "local" database.
- jstests/core/index9.js # "local" database.
- - jstests/core/notablescan.js # notablescan.
- jstests/core/queryoptimizera.js # "local" database.
- jstests/core/rename*.js # renameCollection.
- jstests/core/stages*.js # stageDebug.
diff --git a/buildscripts/resmokeconfig/suites/multi_shard_local_read_write_multi_stmt_txn_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/multi_shard_local_read_write_multi_stmt_txn_jscore_passthrough.yml
index a8d69533c20..d77680f2395 100644
--- a/buildscripts/resmokeconfig/suites/multi_shard_local_read_write_multi_stmt_txn_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/multi_shard_local_read_write_multi_stmt_txn_jscore_passthrough.yml
@@ -30,7 +30,6 @@ selector:
- jstests/core/geo_update_btree2.js # notablescan.
- jstests/core/index_id_options.js # "local" database.
- jstests/core/index9.js # "local" database.
- - jstests/core/notablescan.js # notablescan.
- jstests/core/queryoptimizera.js # "local" database.
- jstests/core/rename*.js # renameCollection.
- jstests/core/stages*.js # stageDebug.
@@ -120,7 +119,6 @@ selector:
- jstests/core/json_schema/json_schema.js
- jstests/core/mr_bigobject.js
- jstests/core/not2.js
- - jstests/core/notablescan.js
- jstests/core/null_query_semantics.js
- jstests/core/or1.js
- jstests/core/or2.js
diff --git a/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_jscore_passthrough.yml
index c68cefa67bc..18f468c702d 100644
--- a/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_jscore_passthrough.yml
@@ -30,7 +30,6 @@ selector:
- jstests/core/geo_update_btree2.js # notablescan.
- jstests/core/index_id_options.js # "local" database.
- jstests/core/index9.js # "local" database.
- - jstests/core/notablescan.js # notablescan.
- jstests/core/queryoptimizera.js # "local" database.
- jstests/core/rename*.js # renameCollection.
- jstests/core/stages*.js # stageDebug.
@@ -119,7 +118,6 @@ selector:
- jstests/core/json_schema/json_schema.js
- jstests/core/mr_bigobject.js
- jstests/core/not2.js
- - jstests/core/notablescan.js
- jstests/core/null_query_semantics.js
- jstests/core/or1.js
- jstests/core/or2.js
diff --git a/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_kill_primary_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_kill_primary_jscore_passthrough.yml
index c4355033af9..25a44cf7e98 100644
--- a/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_kill_primary_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_kill_primary_jscore_passthrough.yml
@@ -30,7 +30,6 @@ selector:
- jstests/core/geo_update_btree2.js # notablescan.
- jstests/core/index_id_options.js # "local" database.
- jstests/core/index9.js # "local" database.
- - jstests/core/notablescan.js # notablescan.
- jstests/core/queryoptimizera.js # "local" database.
- jstests/core/rename*.js # renameCollection.
- jstests/core/stages*.js # stageDebug.
@@ -119,7 +118,6 @@ selector:
- jstests/core/json_schema/json_schema.js
- jstests/core/mr_bigobject.js
- jstests/core/not2.js
- - jstests/core/notablescan.js
- jstests/core/null_query_semantics.js
- jstests/core/or1.js
- jstests/core/or2.js
diff --git a/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_stepdown_primary_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_stepdown_primary_jscore_passthrough.yml
index dbdfc78e5ef..d1e3cf04876 100644
--- a/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_stepdown_primary_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_stepdown_primary_jscore_passthrough.yml
@@ -30,7 +30,6 @@ selector:
- jstests/core/geo_update_btree2.js # notablescan.
- jstests/core/index_id_options.js # "local" database.
- jstests/core/index9.js # "local" database.
- - jstests/core/notablescan.js # notablescan.
- jstests/core/queryoptimizera.js # "local" database.
- jstests/core/rename*.js # renameCollection.
- jstests/core/stages*.js # stageDebug.
@@ -119,7 +118,6 @@ selector:
- jstests/core/json_schema/json_schema.js
- jstests/core/mr_bigobject.js
- jstests/core/not2.js
- - jstests/core/notablescan.js
- jstests/core/null_query_semantics.js
- jstests/core/or1.js
- jstests/core/or2.js
diff --git a/buildscripts/resmokeconfig/suites/multi_stmt_txn_jscore_passthrough_with_migration.yml b/buildscripts/resmokeconfig/suites/multi_stmt_txn_jscore_passthrough_with_migration.yml
index 700b4acca99..c875e22fceb 100644
--- a/buildscripts/resmokeconfig/suites/multi_stmt_txn_jscore_passthrough_with_migration.yml
+++ b/buildscripts/resmokeconfig/suites/multi_stmt_txn_jscore_passthrough_with_migration.yml
@@ -30,7 +30,6 @@ selector:
- jstests/core/geo_update_btree2.js # notablescan.
- jstests/core/index_id_options.js # "local" database.
- jstests/core/index9.js # "local" database.
- - jstests/core/notablescan.js # notablescan.
- jstests/core/queryoptimizera.js # "local" database.
- jstests/core/rename*.js # renameCollection.
- jstests/core/stages*.js # stageDebug.
@@ -124,7 +123,6 @@ selector:
- jstests/core/json_schema/json_schema.js
- jstests/core/mr_bigobject.js
- jstests/core/not2.js
- - jstests/core/notablescan.js
- jstests/core/null_query_semantics.js
- jstests/core/or1.js
- jstests/core/or2.js
diff --git a/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_kill_primary_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_kill_primary_jscore_passthrough.yml
index bdb083aa3a8..3ab6286262f 100644
--- a/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_kill_primary_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_kill_primary_jscore_passthrough.yml
@@ -45,7 +45,6 @@ selector:
- jstests/core/json_schema/json_schema.js
- jstests/core/mr_bigobject.js
- jstests/core/not2.js
- - jstests/core/notablescan.js
- jstests/core/null_query_semantics.js
- jstests/core/or1.js
- jstests/core/or2.js
diff --git a/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_stepdown_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_stepdown_jscore_passthrough.yml
index d60b4794d51..0d2e114ab28 100644
--- a/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_stepdown_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_stepdown_jscore_passthrough.yml
@@ -45,7 +45,6 @@ selector:
- jstests/core/json_schema/json_schema.js
- jstests/core/mr_bigobject.js
- jstests/core/not2.js
- - jstests/core/notablescan.js
- jstests/core/null_query_semantics.js
- jstests/core/or1.js
- jstests/core/or2.js
diff --git a/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_terminate_primary_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_terminate_primary_jscore_passthrough.yml
index 3d994e28aae..fc2777ff204 100644
--- a/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_terminate_primary_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_terminate_primary_jscore_passthrough.yml
@@ -45,7 +45,6 @@ selector:
- jstests/core/json_schema/json_schema.js
- jstests/core/mr_bigobject.js
- jstests/core/not2.js
- - jstests/core/notablescan.js
- jstests/core/null_query_semantics.js
- jstests/core/or1.js
- jstests/core/or2.js
diff --git a/buildscripts/resmokeconfig/suites/sharded_collections_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/sharded_collections_jscore_passthrough.yml
index d46709fb2ea..1cb3c396210 100644
--- a/buildscripts/resmokeconfig/suites/sharded_collections_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/sharded_collections_jscore_passthrough.yml
@@ -31,7 +31,6 @@ selector:
- jstests/core/geo_update_btree2.js # notablescan.
- jstests/core/index_id_options.js # "local" database.
- jstests/core/index9.js # "local" database.
- - jstests/core/notablescan.js # notablescan.
- jstests/core/queryoptimizera.js # "local" database.
- jstests/core/rename*.js # renameCollection.
- jstests/core/stages*.js # stageDebug.
diff --git a/buildscripts/resmokeconfig/suites/sharded_multi_stmt_txn_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/sharded_multi_stmt_txn_jscore_passthrough.yml
index 12b6e0604f0..c8156fea5a3 100644
--- a/buildscripts/resmokeconfig/suites/sharded_multi_stmt_txn_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/sharded_multi_stmt_txn_jscore_passthrough.yml
@@ -27,7 +27,6 @@ selector:
- jstests/core/geo_update_btree2.js # notablescan.
- jstests/core/index_id_options.js # "local" database.
- jstests/core/index9.js # "local" database.
- - jstests/core/notablescan.js # notablescan.
- jstests/core/queryoptimizera.js # "local" database.
- jstests/core/stages*.js # stageDebug.
- jstests/core/startup_log.js # "local" database.
@@ -82,7 +81,6 @@ selector:
- jstests/core/json_schema/json_schema.js
- jstests/core/mr_bigobject.js
- jstests/core/not2.js
- - jstests/core/notablescan.js
- jstests/core/null_query_semantics.js
- jstests/core/or1.js
- jstests/core/or2.js
diff --git a/buildscripts/resmokeconfig/suites/sharding_jscore_op_query_passthrough.yml b/buildscripts/resmokeconfig/suites/sharding_jscore_op_query_passthrough.yml
index 5c846e4b436..e05fad6eec7 100644
--- a/buildscripts/resmokeconfig/suites/sharding_jscore_op_query_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/sharding_jscore_op_query_passthrough.yml
@@ -30,7 +30,6 @@ selector:
- jstests/core/geo_update_btree2.js # notablescan.
- jstests/core/index_id_options.js # "local" database.
- jstests/core/index9.js # "local" database.
- - jstests/core/notablescan.js # notablescan.
- jstests/core/queryoptimizera.js # "local" database.
- jstests/core/stages*.js # stageDebug.
- jstests/core/startup_log.js # "local" database.
diff --git a/buildscripts/resmokeconfig/suites/sharding_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/sharding_jscore_passthrough.yml
index 94e44143faa..d2a74fcd86f 100644
--- a/buildscripts/resmokeconfig/suites/sharding_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/sharding_jscore_passthrough.yml
@@ -31,7 +31,6 @@ selector:
- jstests/core/geo_update_btree2.js # notablescan.
- jstests/core/index_id_options.js # "local" database.
- jstests/core/index9.js # "local" database.
- - jstests/core/notablescan.js # notablescan.
- jstests/core/queryoptimizera.js # "local" database.
- jstests/core/stages*.js # stageDebug.
- jstests/core/startup_log.js # "local" database.
diff --git a/jstests/core/notablescan.js b/jstests/core/notablescan.js
index d9917af5889..2bbc300238d 100644
--- a/jstests/core/notablescan.js
+++ b/jstests/core/notablescan.js
@@ -1,6 +1,7 @@
// check notablescan mode
//
// @tags: [
+// assumes_against_mongod_not_mongos,
// # This test attempts to perform read operations after having enabled the notablescan server
// # parameter. The former operations may be routed to a secondary in the replica set, whereas the
// # latter must be routed to the primary.
@@ -21,21 +22,27 @@ try {
});
}
t.save({a: 1});
- if (0) { // SERVER-2222
- assert.throws(function() {
- t.count({a: 1});
- });
+ assert.throws(function() {
+ t.count({a: 1});
+ });
+ if (0) {
assert.throws(function() {
t.find({}).toArray();
});
}
assert.eq(1, t.find({}).itcount()); // SERVER-274
- assert.throws(function() {
+
+ let err = assert.throws(function() {
t.find({a: 1}).toArray();
});
- assert.throws(function() {
+ assert.includes(err.toString(), "No indexed plans available, and running with 'notablescan'");
+
+ err = assert.throws(function() {
t.find({a: 1}).hint({$natural: 1}).toArray();
});
+ assert.includes(err.toString(),
+ "hint $natural is not allowed, because 'notablescan' is enabled");
+
t.ensureIndex({a: 1});
assert.eq(0, t.find({a: 1, b: 1}).itcount());
assert.eq(1, t.find({a: 1, b: null}).itcount());
diff --git a/jstests/core/notablescan_capped.js b/jstests/core/notablescan_capped.js
new file mode 100644
index 00000000000..c4b8fb3474e
--- /dev/null
+++ b/jstests/core/notablescan_capped.js
@@ -0,0 +1,32 @@
+// check notablescan mode for capped collection / tailable cursor
+//
+// @tags: [
+// assumes_against_mongod_not_mongos,
+// # This test attempts to perform read operations after having enabled the notablescan server
+// # parameter. The former operations may be routed to a secondary in the replica set, whereas the
+// # latter must be routed to the primary.
+// assumes_read_preference_unchanged,
+// assumes_superuser_permissions,
+// does_not_support_stepdowns,
+// requires_capped,
+// ]
+
+t = db.test_notablescan_capped;
+t.drop();
+assert.commandWorked(db.createCollection(t.getName(), {capped: true, size: 100}));
+
+try {
+ assert.commandWorked(db._adminCommand({setParameter: 1, notablescan: true}));
+
+ err = assert.throws(function() {
+ t.find({a: 1}).tailable(true).next();
+ });
+ assert.includes(
+ err.toString(),
+ "Running with 'notablescan', so tailable cursors (which always do a table scan) are not allowed");
+
+} finally {
+ // We assume notablescan was false before this test started and restore that
+ // expected value.
+ assert.commandWorked(db._adminCommand({setParameter: 1, notablescan: false}));
+}
diff --git a/src/mongo/db/exec/cached_plan.cpp b/src/mongo/db/exec/cached_plan.cpp
index acfaafd2983..9738936f4b7 100644
--- a/src/mongo/db/exec/cached_plan.cpp
+++ b/src/mongo/db/exec/cached_plan.cpp
@@ -203,17 +203,8 @@ Status CachedPlanStage::replan(PlanYieldPolicy* yieldPolicy, bool shouldCache) {
str::stream() << "error processing query: " << _canonicalQuery->toString()
<< " planner returned error");
}
-
auto solutions = std::move(statusWithSolutions.getValue());
- // We cannot figure out how to answer the query. Perhaps it requires an index
- // we do not have?
- if (0 == solutions.size()) {
- return Status(ErrorCodes::NoQueryExecutionPlans,
- str::stream() << "error processing query: " << _canonicalQuery->toString()
- << " No query solutions");
- }
-
if (1 == solutions.size()) {
// Only one possible plan. Build the stages from the solution.
auto newRoot =
diff --git a/src/mongo/db/exec/subplan.cpp b/src/mongo/db/exec/subplan.cpp
index 5c815d87c30..c6fc9991185 100644
--- a/src/mongo/db/exec/subplan.cpp
+++ b/src/mongo/db/exec/subplan.cpp
@@ -165,13 +165,6 @@ Status SubplanStage::planSubqueries() {
branchResult->solutions = std::move(solutions.getValue());
LOG(5) << "Subplanner: got " << branchResult->solutions.size() << " solutions";
-
- if (0 == branchResult->solutions.size()) {
- // If one child doesn't have an indexed solution, bail out.
- str::stream ss;
- ss << "No solutions for subchild " << branchResult->canonicalQuery->toString();
- return Status(ErrorCodes::BadValue, ss);
- }
}
}
@@ -371,17 +364,8 @@ Status SubplanStage::choosePlanWholeQuery(PlanYieldPolicy* yieldPolicy) {
str::stream() << "error processing query: " << _query->toString()
<< " planner returned error");
}
-
auto solutions = std::move(statusWithSolutions.getValue());
- // We cannot figure out how to answer the query. Perhaps it requires an index
- // we do not have?
- if (0 == solutions.size()) {
- return Status(ErrorCodes::NoQueryExecutionPlans,
- str::stream() << "error processing query: " << _query->toString()
- << " No query solutions");
- }
-
if (1 == solutions.size()) {
// Only one possible plan. Run it. Build the stages from the solution.
auto root = StageBuilder::build(getOpCtx(), collection(), *_query, *solutions[0], _ws);
diff --git a/src/mongo/db/query/get_executor.cpp b/src/mongo/db/query/get_executor.cpp
index b236de9e8ea..070218d9efd 100644
--- a/src/mongo/db/query/get_executor.cpp
+++ b/src/mongo/db/query/get_executor.cpp
@@ -508,14 +508,8 @@ StatusWith<PrepareExecutionResult> prepareExecution(OperationContext* opCtx,
<< " planner returned error");
}
auto solutions = std::move(statusWithSolutions.getValue());
-
- // We cannot figure out how to answer the query. Perhaps it requires an index
- // we do not have?
- if (0 == solutions.size()) {
- return Status(ErrorCodes::NoQueryExecutionPlans,
- str::stream() << "error processing query: " << canonicalQuery->toString()
- << " No query solutions");
- }
+ // The planner should have returned an error status if there are no solutions.
+ invariant(solutions.size() > 0);
// See if one of our solutions is a fast count hack in disguise.
if (plannerParams.options & QueryPlannerParams::IS_COUNT) {
diff --git a/src/mongo/db/query/query_planner.cpp b/src/mongo/db/query/query_planner.cpp
index 682e27de35b..de86cf3fc32 100644
--- a/src/mongo/db/query/query_planner.cpp
+++ b/src/mongo/db/query/query_planner.cpp
@@ -532,8 +532,6 @@ StatusWith<std::vector<std::unique_ptr<QuerySolution>>> QueryPlanner::plan(
<< "Canonical query:" << endl
<< redact(query.toString()) << "=============================";
- std::vector<std::unique_ptr<QuerySolution>> out;
-
for (size_t i = 0; i < params.indices.size(); ++i) {
LOG(5) << "Index " << i << " is " << params.indices[i].toString();
}
@@ -544,12 +542,23 @@ StatusWith<std::vector<std::unique_ptr<QuerySolution>>> QueryPlanner::plan(
// If the query requests a tailable cursor, the only solution is a collscan + filter with
// tailable set on the collscan.
if (isTailable) {
- if (!QueryPlannerCommon::hasNode(query.root(), MatchExpression::GEO_NEAR) && canTableScan) {
- auto soln = buildCollscanSoln(query, isTailable, params);
- if (soln) {
- out.push_back(std::move(soln));
- }
+ if (!canTableScan) {
+ return Status(
+ ErrorCodes::NoQueryExecutionPlans,
+ "Running with 'notablescan', so tailable cursors (which always do a table "
+ "scan) are not allowed");
}
+ if (QueryPlannerCommon::hasNode(query.root(), MatchExpression::GEO_NEAR)) {
+ return Status(ErrorCodes::NoQueryExecutionPlans,
+ "Tailable cursors and geo $near cannot be used together");
+ }
+ auto soln = buildCollscanSoln(query, isTailable, params);
+ if (!soln) {
+ return Status(ErrorCodes::NoQueryExecutionPlans,
+ "Failed to build collection scan soln");
+ }
+ std::vector<std::unique_ptr<QuerySolution>> out;
+ out.push_back(std::move(soln));
return {std::move(out)};
}
@@ -567,14 +576,22 @@ StatusWith<std::vector<std::unique_ptr<QuerySolution>>> QueryPlanner::plan(
// scan if there is a $natural sort with a non-$natural hint.
if (!naturalHint.eoo() || (!naturalSort.eoo() && hintObj.isEmpty())) {
LOG(5) << "Forcing a table scan due to hinted $natural";
- // min/max are incompatible with $natural.
- if (canTableScan && query.getQueryRequest().getMin().isEmpty() &&
- query.getQueryRequest().getMax().isEmpty()) {
- auto soln = buildCollscanSoln(query, isTailable, params);
- if (soln) {
- out.push_back(std::move(soln));
- }
+ if (!canTableScan) {
+ return Status(ErrorCodes::NoQueryExecutionPlans,
+ "hint $natural is not allowed, because 'notablescan' is enabled");
+ }
+ if (!query.getQueryRequest().getMin().isEmpty() ||
+ !query.getQueryRequest().getMax().isEmpty()) {
+ return Status(ErrorCodes::NoQueryExecutionPlans,
+ "min and max are incompatible with $natural");
+ }
+ auto soln = buildCollscanSoln(query, isTailable, params);
+ if (!soln) {
+ return Status(ErrorCodes::NoQueryExecutionPlans,
+ "Failed to build collection scan soln");
}
+ std::vector<std::unique_ptr<QuerySolution>> out;
+ out.push_back(std::move(soln));
return {std::move(out)};
}
}
@@ -682,10 +699,12 @@ StatusWith<std::vector<std::unique_ptr<QuerySolution>>> QueryPlanner::plan(
invariant(solnRoot);
auto soln = QueryPlannerAnalysis::analyzeDataAccess(query, params, std::move(solnRoot));
- if (soln) {
- out.push_back(std::move(soln));
+ if (!soln) {
+ return Status(ErrorCodes::NoQueryExecutionPlans,
+ "Sort and covering analysis failed while planning hint/min/max query");
}
-
+ std::vector<std::unique_ptr<QuerySolution>> out;
+ out.push_back(std::move(soln));
return {std::move(out)};
}
@@ -767,6 +786,8 @@ StatusWith<std::vector<std::unique_ptr<QuerySolution>>> QueryPlanner::plan(
LOG(5) << "Rated tree after text processing:" << redact(query.root()->debugString());
}
+ std::vector<std::unique_ptr<QuerySolution>> out;
+
// If we have any relevant indices, we try to create indexed plans.
if (0 < relevantIndices.size()) {
// The enumerator spits out trees tagged with IndexTag(s).
@@ -845,14 +866,23 @@ StatusWith<std::vector<std::unique_ptr<QuerySolution>>> QueryPlanner::plan(
// desired behavior when an index is hinted that is not relevant to the query. In the case that
// $** index is hinted, we do not want this behavior.
if (!hintedIndex.isEmpty() && relevantIndices.size() == 1) {
- if (0 == out.size() && relevantIndices.front().type != IndexType::INDEX_WILDCARD) {
- // Push hinted index solution to output list if found.
- auto soln = buildWholeIXSoln(relevantIndices.front(), query, params);
- if (soln) {
- LOG(5) << "Planner: outputting soln that uses hinted index as scan.";
- out.push_back(std::move(soln));
- }
+ if (out.size() > 0) {
+ return {std::move(out)};
+ }
+ if (relevantIndices.front().type == IndexType::INDEX_WILDCARD) {
+ return Status(
+ ErrorCodes::NoQueryExecutionPlans,
+ "$hint: refusing to build whole-index solution, because it's a wildcard index");
}
+ // Return hinted index solution if found.
+ auto soln = buildWholeIXSoln(relevantIndices.front(), query, params);
+ if (!soln) {
+ return Status(ErrorCodes::NoQueryExecutionPlans,
+ "Failed to build whole-index solution for $hint");
+ }
+ LOG(5) << "Planner: outputting soln that uses hinted index as scan.";
+ std::vector<std::unique_ptr<QuerySolution>> out;
+ out.push_back(std::move(soln));
return {std::move(out)};
}
@@ -979,20 +1009,31 @@ StatusWith<std::vector<std::unique_ptr<QuerySolution>>> QueryPlanner::plan(
}
}
+ // The caller can explicitly ask for a collscan.
+ bool collscanRequested = (params.options & QueryPlannerParams::INCLUDE_COLLSCAN);
+
+ // No indexed plans? We must provide a collscan if possible or else we can't run the query.
+ bool collScanRequired = 0 == out.size();
+ if (collScanRequired && !canTableScan) {
+ return Status(ErrorCodes::NoQueryExecutionPlans,
+ "No indexed plans available, and running with 'notablescan'");
+ }
+
// geoNear and text queries *require* an index.
// Also, if a hint is specified it indicates that we MUST use it.
bool possibleToCollscan =
!QueryPlannerCommon::hasNode(query.root(), MatchExpression::GEO_NEAR) &&
!QueryPlannerCommon::hasNode(query.root(), MatchExpression::TEXT) && hintedIndex.isEmpty();
+ if (collScanRequired && !possibleToCollscan) {
+ return Status(ErrorCodes::NoQueryExecutionPlans, "No query solutions");
+ }
- // The caller can explicitly ask for a collscan.
- bool collscanRequested = (params.options & QueryPlannerParams::INCLUDE_COLLSCAN);
-
- // No indexed plans? We must provide a collscan if possible or else we can't run the query.
- bool collscanNeeded = (0 == out.size() && canTableScan);
-
- if (possibleToCollscan && (collscanRequested || collscanNeeded)) {
+ if (possibleToCollscan && (collscanRequested || collScanRequired)) {
auto collscan = buildCollscanSoln(query, isTailable, params);
+ if (!collscan && collScanRequired) {
+ return Status(ErrorCodes::NoQueryExecutionPlans,
+ "Failed to build collection scan soln");
+ }
if (collscan) {
LOG(5) << "Planner: outputting a collscan:" << endl << redact(collscan->toString());
SolutionCacheData* scd = new SolutionCacheData();
@@ -1002,6 +1043,7 @@ StatusWith<std::vector<std::unique_ptr<QuerySolution>>> QueryPlanner::plan(
}
}
+ invariant(out.size() > 0);
return {std::move(out)};
}
diff --git a/src/mongo/db/query/query_planner_array_test.cpp b/src/mongo/db/query/query_planner_array_test.cpp
index 6d0d4b0a11f..857ef98613b 100644
--- a/src/mongo/db/query/query_planner_array_test.cpp
+++ b/src/mongo/db/query/query_planner_array_test.cpp
@@ -1002,10 +1002,9 @@ TEST_F(QueryPlannerTest, CantIndexNegationBelowElemMatchValue) {
// true means multikey
addIndex(BSON("a" << 1), true);
- runQuery(fromjson("{a: {$elemMatch: {$not: {$mod: [2, 0]}}}}"));
-
// There are no indexed solutions, because negations of $mod are not indexable.
- assertNumSolutions(0);
+ runInvalidQuery(fromjson("{a: {$elemMatch: {$not: {$mod: [2, 0]}}}}"));
+ assertNoSolutions();
}
/**
diff --git a/src/mongo/db/query/query_planner_geo_test.cpp b/src/mongo/db/query/query_planner_geo_test.cpp
index b23c40a64fe..914cd613da5 100644
--- a/src/mongo/db/query/query_planner_geo_test.cpp
+++ b/src/mongo/db/query/query_planner_geo_test.cpp
@@ -947,9 +947,9 @@ TEST_F(QueryPlannerGeo2dsphereTest,
addIndex(BSON("a" << 1 << "b" << 1 << "geo"
<< "2dsphere"),
multikeyPaths);
- runQuery(fromjson("{a: {$ne: 3}, b: 2, geo: {$nearSphere: [0, 0]}}"));
+ runInvalidQuery(fromjson("{a: {$ne: 3}, b: 2, geo: {$nearSphere: [0, 0]}}"));
- assertNumSolutions(0U);
+ assertNoSolutions();
}
TEST_F(QueryPlannerGeo2dsphereTest,
@@ -1303,6 +1303,23 @@ TEST_F(QueryPlannerGeo2dsphereTest, CannotIntersectBoundsOn2dsphereFieldWhenItIs
// A fixture to help run tests for multiple 2dsphere index versions.
class QueryPlanner2dsphereVersionTest : public QueryPlannerTest {
public:
+ // For each 2dsphere index version in 'versions', verifies the planner returns
+ // 'errorCode' for 'predicate' given 'keyPatterns'.
+ void assertExpectedFailureFor2dsphereIndexVersions(std::vector<int> versions,
+ std::vector<BSONObj> keyPatterns,
+ BSONObj predicate,
+ ErrorCodes::Error errorCode) {
+ for (auto version : versions) {
+ params.indices.clear();
+ for (auto keyPattern : keyPatterns) {
+ addIndex(keyPattern, BSON("2dsphereIndexVersion" << version));
+ }
+
+ runInvalidQuery(predicate);
+ ASSERT_EQUALS(plannerStatus.code(), errorCode);
+ }
+ }
+
// For each 2dsphere index version in 'versions', verifies the planner generates
// 'expectedSolutions' for 'predicate' given 'keyPatterns'.
void testMultiple2dsphereIndexVersions(std::vector<int> versions,
@@ -1564,9 +1581,8 @@ TEST_F(QueryPlanner2dsphereVersionTest, NegationWithoutGeoPredCannotUseGeoIndex)
BSONObj predicate = fromjson("{a: {$ne: 3}}");
// Only a COLLSCAN is possible, but COLLSCANs are prohibited above.
- std::vector<std::string> solutions = {};
-
- testMultiple2dsphereIndexVersions(versions, keyPatterns, predicate, solutions);
+ ErrorCodes::Error err = ErrorCodes::NoQueryExecutionPlans;
+ assertExpectedFailureFor2dsphereIndexVersions(versions, keyPatterns, predicate, err);
}
TEST_F(QueryPlannerTest, 2dInexactFetchPredicateOverTrailingFieldHandledCorrectly) {
@@ -1703,9 +1719,9 @@ TEST_F(QueryPlannerTest, 2dsphereNonNearWithInternalExprEqOverLeadingFieldMultik
<< "2dsphere"),
multikey);
- runQuery(
+ runInvalidQuery(
fromjson("{a: {$_internalExprEq: 0}, b: {$geoWithin: {$centerSphere: [[0, 0], 10]}}}"));
- assertNumSolutions(0U);
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, 2dsphereNonNearWithInternalExprEqOverTrailingField) {
diff --git a/src/mongo/db/query/query_planner_index_test.cpp b/src/mongo/db/query/query_planner_index_test.cpp
index b819a3311f6..1ec1f6e19ca 100644
--- a/src/mongo/db/query/query_planner_index_test.cpp
+++ b/src/mongo/db/query/query_planner_index_test.cpp
@@ -587,13 +587,12 @@ TEST_F(QueryPlannerTest, CompoundMultikeyBoundsNoIntersect) {
TEST_F(QueryPlannerTest, NoTableScanBasic) {
params.options = QueryPlannerParams::NO_TABLE_SCAN;
- runQuery(BSONObj());
- assertNumSolutions(0U);
+ runInvalidQuery(BSONObj());
+ assertNoSolutions();
addIndex(BSON("x" << 1));
-
- runQuery(BSONObj());
- assertNumSolutions(0U);
+ runInvalidQuery(BSONObj());
+ assertNoSolutions();
runQuery(fromjson("{x: {$gte: 0}}"));
assertNumSolutions(1U);
diff --git a/src/mongo/db/query/query_planner_partialidx_test.cpp b/src/mongo/db/query/query_planner_partialidx_test.cpp
index 9cf9b66d00b..124b75c7518 100644
--- a/src/mongo/db/query/query_planner_partialidx_test.cpp
+++ b/src/mongo/db/query/query_planner_partialidx_test.cpp
@@ -49,8 +49,8 @@ TEST_F(QueryPlannerTest, PartialIndexEq) {
"{filter: null, pattern: {a: 1}, "
"bounds: {a: [[1, 1, true, true]]}}}}}");
- runQuery(fromjson("{a: -1}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{a: -1}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexNot) {
@@ -59,14 +59,14 @@ TEST_F(QueryPlannerTest, PartialIndexNot) {
std::unique_ptr<MatchExpression> filterExpr = parseMatchExpression(filterObj);
addIndex(fromjson("{a: 1}"), filterExpr.get());
- runQuery(fromjson("{a: {$ne: 1}}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{a: {$ne: 1}}"));
+ assertNoSolutions();
- runQuery(fromjson("{a: {$ne: -1}}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{a: {$ne: -1}}"));
+ assertNoSolutions();
- runQuery(fromjson("{a: {$not: {$lte: 0}}}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{a: {$not: {$lte: 0}}}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexElemMatchObj) {
@@ -76,11 +76,11 @@ TEST_F(QueryPlannerTest, PartialIndexElemMatchObj) {
addIndex(fromjson("{'a.b': 1}"), filterExpr.get());
// The following query could in theory use the partial index, but we don't support it right now.
- runQuery(fromjson("{a: {$elemMatch: {b: 1}}}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{a: {$elemMatch: {b: 1}}}"));
+ assertNoSolutions();
- runQuery(fromjson("{a: {$elemMatch: {b: -1}}}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{a: {$elemMatch: {b: -1}}}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexElemMatchObjContainingOr) {
@@ -90,8 +90,8 @@ TEST_F(QueryPlannerTest, PartialIndexElemMatchObjContainingOr) {
addIndex(fromjson("{'a.b': 1}"), filterExpr.get());
// The following query could in theory use the partial index, but we don't support it right now.
- runQuery(fromjson("{a: {$elemMatch: {$or: [{b: 1}, {b: 2}]}}}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{a: {$elemMatch: {$or: [{b: 1}, {b: 2}]}}}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexElemMatchObjWithBadSubobjectFilter) {
@@ -100,11 +100,11 @@ TEST_F(QueryPlannerTest, PartialIndexElemMatchObjWithBadSubobjectFilter) {
std::unique_ptr<MatchExpression> filterExpr = parseMatchExpression(filterObj);
addIndex(fromjson("{'a.b': 1}"), filterExpr.get());
- runQuery(fromjson("{a: {$elemMatch: {$or: [{b: 1}, {b: 2}]}}}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{a: {$elemMatch: {$or: [{b: 1}, {b: 2}]}}}"));
+ assertNoSolutions();
- runQuery(fromjson("{a: {$elemMatch: {b: {$in: [1, 2]}}}}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{a: {$elemMatch: {b: {$in: [1, 2]}}}}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexAndSingleAssignment) {
@@ -120,8 +120,8 @@ TEST_F(QueryPlannerTest, PartialIndexAndSingleAssignment) {
"{filter: null, pattern: {a: 1}, "
"bounds: {a: [[1, 1, true, true]]}}}}}");
- runQuery(fromjson("{a: 1, f: -1}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{a: 1, f: -1}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexAndMultipleAssignments) {
@@ -137,8 +137,8 @@ TEST_F(QueryPlannerTest, PartialIndexAndMultipleAssignments) {
"{filter: null, pattern: {a: 1}, "
"bounds: {a: [[0, 10, true, true]]}}}}}");
- runQuery(fromjson("{a: {$gte: 0, $lte: 10}, f: -1}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{a: {$gte: 0, $lte: 10}, f: -1}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexOr) {
@@ -156,8 +156,8 @@ TEST_F(QueryPlannerTest, PartialIndexOr) {
"{ixscan: {filter: null, pattern: {_id: 1}, "
"bounds: {_id: [[0, 0, true, true]]}}}]}}}}");
- runQuery(fromjson("{$or: [{a: -1}, {_id: 0}]}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{$or: [{a: -1}, {_id: 0}]}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexOrContainingAnd) {
@@ -176,8 +176,8 @@ TEST_F(QueryPlannerTest, PartialIndexOrContainingAnd) {
"{ixscan: {filter: null, pattern: {_id: 1}, "
"bounds: {_id: [[0, 0, true, true]]}}}]}}}}");
- runQuery(fromjson("{$or: [{a: 1, f: -1}, {_id: 0}]}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{$or: [{a: 1, f: -1}, {_id: 0}]}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexOrContainingAndMultipleAssignments) {
@@ -213,8 +213,8 @@ TEST_F(QueryPlannerTest, PartialIndexOrContainingAndMultipleAssignments) {
"{ixscan: {filter: null, pattern: {a: 1}, "
"bounds: {a: [[2, 2, true, true]]}}}]}}}}");
- runQuery(fromjson("{$or: [{_id: 0, a: -1}, {a: -2}]}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{$or: [{_id: 0, a: -1}, {a: -2}]}"));
+ assertNoSolutions();
}
@@ -234,8 +234,8 @@ TEST_F(QueryPlannerTest, PartialIndexAndContainingOrContainingAnd) {
"{ixscan: {filter: null, pattern: {_id: 1}, "
"bounds: {_id: [[0, 0, true, true]]}}}]}}}}");
- runQuery(fromjson("{x: 1, $or: [{a: 1, f: -1}, {_id: 0}]}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{x: 1, $or: [{a: 1, f: -1}, {_id: 0}]}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexAndContainingOrContainingAndSatisfyingPredOutside) {
@@ -255,8 +255,8 @@ TEST_F(QueryPlannerTest, PartialIndexAndContainingOrContainingAndSatisfyingPredO
"bounds: {_id: [[0, 0, true, true]]}}}]}}}}");
// The following query could in theory use the partial index, but we don't support it right now.
- runQuery(fromjson("{f: 1, x: 1, $or: [{a: 1, g: 1}, {_id: 0}]}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{f: 1, x: 1, $or: [{a: 1, g: 1}, {_id: 0}]}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexOrContainingAndContainingOr) {
@@ -277,8 +277,8 @@ TEST_F(QueryPlannerTest, PartialIndexOrContainingAndContainingOr) {
"{ixscan: {filter: null, pattern: {_id: 1}, "
"bounds: {_id: [[1, 1, true, true]]}}}]}}}}");
- runQuery(fromjson("{$or: [{x: 1, $or: [{a: -1}, {_id: 2}]}, {_id: 1}]}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{$or: [{x: 1, $or: [{a: -1}, {_id: 2}]}, {_id: 1}]}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexProvidingSort) {
@@ -294,8 +294,8 @@ TEST_F(QueryPlannerTest, PartialIndexProvidingSort) {
"{filter: null, pattern: {a: 1}, "
"bounds: {a: [['MinKey', 'MaxKey', true, true]]}}}}}");
- runQuerySortProj(fromjson("{f: -1}"), fromjson("{a: 1}"), BSONObj());
- assertNumSolutions(0U);
+ runInvalidQuerySortProj(fromjson("{f: -1}"), fromjson("{a: 1}"), BSONObj());
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexOrContainingNot) {
@@ -304,11 +304,11 @@ TEST_F(QueryPlannerTest, PartialIndexOrContainingNot) {
std::unique_ptr<MatchExpression> filterExpr = parseMatchExpression(filterObj);
addIndex(fromjson("{a: 1}"), filterExpr.get());
- runQuery(fromjson("{$or: [{a: {$ne: 1}}, {_id: 1}]}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{$or: [{a: {$ne: 1}}, {_id: 1}]}"));
+ assertNoSolutions();
- runQuery(fromjson("{$or: [{a: {$ne: -1}}, {_id: 1}]}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{$or: [{a: {$ne: -1}}, {_id: 1}]}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexMultipleSameAnd) {
@@ -338,8 +338,8 @@ TEST_F(QueryPlannerTest, PartialIndexMultipleSameAnd) {
"{filter: null, pattern: {a: 1}, "
"bounds: {a: [[1, 1, true, true]]}}}}}");
- runQuery(fromjson("{a: 1, b: 1, f: -1, g: -1}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{a: 1, b: 1, f: -1, g: -1}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexMultipleSameAndCompoundSharedPrefix) {
@@ -362,8 +362,8 @@ TEST_F(QueryPlannerTest, PartialIndexMultipleSameAndCompoundSharedPrefix) {
"bounds: {a: [[1, 1, true, true]], "
"c: [['MinKey', 'MaxKey', true, true]]}}}}}");
- runQuery(fromjson("{a: 1, f: -1}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{a: 1, f: -1}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexMultipleSameOr) {
@@ -386,11 +386,11 @@ TEST_F(QueryPlannerTest, PartialIndexMultipleSameOr) {
"{filter: null, pattern: {b: 1}, "
"bounds: {b: [[1, 1, true, true]]}}}}}]}}");
- runQuery(fromjson("{$or: [{a: 1, f: 1}, {b: 1, g: -1}]}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{$or: [{a: 1, f: 1}, {b: 1, g: -1}]}"));
+ assertNoSolutions();
- runQuery(fromjson("{$or: [{a: 1, f: -1}, {b: 1, g: -1}]}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{$or: [{a: 1, f: -1}, {b: 1, g: -1}]}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexAndCompoundIndex) {
@@ -407,8 +407,8 @@ TEST_F(QueryPlannerTest, PartialIndexAndCompoundIndex) {
"bounds: {a: [[1, 1, true, true]], "
"b: [[1, 1, true, true]]}}}}}");
- runQuery(fromjson("{a: 1, b: 1, f: -1}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{a: 1, b: 1, f: -1}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexOrCompoundIndex) {
@@ -427,8 +427,8 @@ TEST_F(QueryPlannerTest, PartialIndexOrCompoundIndex) {
"{ixscan: {filter: null, pattern: {_id: 1}, "
"bounds: {_id: [[1, 1, true, true]]}}}]}}}}");
- runQuery(fromjson("{$or: [{a: 1, b: 1, f: -1}, {_id: 1}]}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{$or: [{a: 1, b: 1, f: -1}, {_id: 1}]}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexNor) {
@@ -438,11 +438,11 @@ TEST_F(QueryPlannerTest, PartialIndexNor) {
addIndex(fromjson("{a: 1}"), filterExpr.get());
// The following query could in theory use the partial index, but we don't support it right now.
- runQuery(fromjson("{$nor: [{a: 1, f: 1}, {_id: 1}]}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{$nor: [{a: 1, f: 1}, {_id: 1}]}"));
+ assertNoSolutions();
- runQuery(fromjson("{$nor: [{a: 1, f: -1}, {_id: 1}]}"));
- assertNumSolutions(0U);
+ runInvalidQuery(fromjson("{$nor: [{a: 1, f: -1}, {_id: 1}]}"));
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexStringComparisonMatchingCollators) {
@@ -460,9 +460,9 @@ TEST_F(QueryPlannerTest, PartialIndexStringComparisonMatchingCollators) {
"{filter: null, pattern: {a: 1}, "
"bounds: {a: [['cba', 'cba', true, true]]}}}}}");
- runQueryAsCommand(
+ runInvalidQueryAsCommand(
fromjson("{find: 'testns', filter: {a: 'zaa'}, collation: {locale: 'reverse'}}"));
- assertNumSolutions(0U);
+ assertNoSolutions();
}
TEST_F(QueryPlannerTest, PartialIndexNoStringComparisonNonMatchingCollators) {
@@ -486,8 +486,8 @@ TEST_F(QueryPlannerTest, InternalExprEqCannotUsePartialIndex) {
auto filterExpr = parseMatchExpression(filterObj);
addIndex(fromjson("{a: 1}"), filterExpr.get());
- runQueryAsCommand(fromjson("{find: 'testns', filter: {a: {$_internalExprEq: 1}}}"));
- assertNumSolutions(0U);
+ runInvalidQueryAsCommand(fromjson("{find: 'testns', filter: {a: {$_internalExprEq: 1}}}"));
+ assertNoSolutions();
}
} // namespace
diff --git a/src/mongo/db/query/query_planner_test_fixture.cpp b/src/mongo/db/query/query_planner_test_fixture.cpp
index ff4aef1309e..db4ae678f87 100644
--- a/src/mongo/db/query/query_planner_test_fixture.cpp
+++ b/src/mongo/db/query/query_planner_test_fixture.cpp
@@ -59,6 +59,7 @@ void QueryPlannerTest::setUp() {
}
void QueryPlannerTest::clearState() {
+ plannerStatus = Status::OK();
solns.clear();
cq.reset();
relaxBoundsCheck = false;
@@ -418,7 +419,8 @@ void QueryPlannerTest::runInvalidQueryFull(const BSONObj& query,
cq = std::move(statusWithCQ.getValue());
auto statusWithSolutions = QueryPlanner::plan(*cq, params);
- ASSERT_NOT_OK(statusWithSolutions.getStatus());
+ plannerStatus = statusWithSolutions.getStatus();
+ ASSERT_NOT_OK(plannerStatus);
}
void QueryPlannerTest::runQueryAsCommand(const BSONObj& cmdObj) {
@@ -465,7 +467,8 @@ void QueryPlannerTest::runInvalidQueryAsCommand(const BSONObj& cmdObj) {
cq = std::move(statusWithCQ.getValue());
auto statusWithSolutions = QueryPlanner::plan(*cq, params);
- ASSERT_NOT_OK(statusWithSolutions.getStatus());
+ plannerStatus = statusWithSolutions.getStatus();
+ ASSERT_NOT_OK(plannerStatus);
}
size_t QueryPlannerTest::getNumSolutions() const {
@@ -537,6 +540,10 @@ void QueryPlannerTest::assertHasOneSolutionOf(const std::vector<std::string>& so
FAIL(ss);
}
+void QueryPlannerTest::assertNoSolutions() const {
+ ASSERT_EQUALS(plannerStatus.code(), ErrorCodes::NoQueryExecutionPlans);
+}
+
void QueryPlannerTest::assertHasOnlyCollscan() const {
assertNumSolutions(1U);
assertSolutionExists("{cscan: {dir: 1}}");
diff --git a/src/mongo/db/query/query_planner_test_fixture.h b/src/mongo/db/query/query_planner_test_fixture.h
index b314099057f..4743f505fa2 100644
--- a/src/mongo/db/query/query_planner_test_fixture.h
+++ b/src/mongo/db/query/query_planner_test_fixture.h
@@ -216,6 +216,11 @@ protected:
void assertHasOnlyCollscan() const;
/**
+ * Check that query planning failed with NoQueryExecutionPlans.
+ */
+ void assertNoSolutions() const;
+
+ /**
* Helper function to parse a MatchExpression.
*/
static std::unique_ptr<MatchExpression> parseMatchExpression(
@@ -232,6 +237,7 @@ protected:
BSONObj queryObj;
std::unique_ptr<CanonicalQuery> cq;
QueryPlannerParams params;
+ Status plannerStatus = Status::OK();
std::vector<std::unique_ptr<QuerySolution>> solns;
bool relaxBoundsCheck = false;
diff --git a/src/mongo/db/query/query_planner_text_test.cpp b/src/mongo/db/query/query_planner_text_test.cpp
index ed4b1e45247..624eb1b9292 100644
--- a/src/mongo/db/query/query_planner_text_test.cpp
+++ b/src/mongo/db/query/query_planner_text_test.cpp
@@ -65,10 +65,9 @@ TEST_F(QueryPlannerTest, CantUseTextUnlessHaveTextPred) {
addIndex(BSON("a" << 1 << "_fts"
<< "text"
<< "_ftsx" << 1));
- runQuery(fromjson("{a:1}"));
-
// No table scans allowed so there is no solution.
- assertNumSolutions(0);
+ runInvalidQuery(fromjson("{a:1}"));
+ assertNoSolutions();
}
// But if you create an index {a:1, b:"text"} you can use it if it has a pred on 'a'
@@ -335,9 +334,9 @@ TEST_F(QueryPlannerTest, TextInsideOrOneBranchNotIndexed) {
addIndex(BSON("_fts"
<< "text"
<< "_ftsx" << 1));
- runQuery(fromjson("{a: 1, $or: [{b: 2}, {$text: {$search: 'foo'}}]}"));
+ runInvalidQuery(fromjson("{a: 1, $or: [{b: 2}, {$text: {$search: 'foo'}}]}"));
- assertNumSolutions(0);
+ assertNoSolutions();
}
// If the unindexable $or is not the one containing the $text predicate,
diff --git a/src/mongo/db/query/query_planner_wildcard_index_test.cpp b/src/mongo/db/query/query_planner_wildcard_index_test.cpp
index 794a959fa1f..318d43728e9 100644
--- a/src/mongo/db/query/query_planner_wildcard_index_test.cpp
+++ b/src/mongo/db/query/query_planner_wildcard_index_test.cpp
@@ -1022,8 +1022,8 @@ TEST_F(QueryPlannerWildcardTest, QueryNotInWildcardIndexHint) {
addWildcardIndex(BSON("a.$**" << 1));
addIndex(BSON("x" << 1));
- runQueryHint(fromjson("{x: {$eq: 1}}"), BSON("a.$**" << 1));
- assertNumSolutions(0U);
+ runInvalidQueryHint(fromjson("{x: {$eq: 1}}"), BSON("a.$**" << 1));
+ assertNoSolutions();
}
TEST_F(QueryPlannerWildcardTest, WildcardIndexDoesNotExist) {
@@ -1037,8 +1037,8 @@ TEST_F(QueryPlannerWildcardTest, WildcardIndexHintWithPartialFilter) {
auto filterExpr = QueryPlannerTest::parseMatchExpression(filterObj);
addWildcardIndex(BSON("$**" << 1), {}, {}, filterExpr.get());
- runQueryHint(fromjson("{a: {$eq: 1}}"), BSON("$**" << 1));
- assertNumSolutions(0U);
+ runInvalidQueryHint(fromjson("{a: {$eq: 1}}"), BSON("$**" << 1));
+ assertNoSolutions();
}
TEST_F(QueryPlannerWildcardTest, MultipleWildcardIndexesHintWithPartialFilter) {
@@ -1046,8 +1046,8 @@ TEST_F(QueryPlannerWildcardTest, MultipleWildcardIndexesHintWithPartialFilter) {
auto filterExpr = QueryPlannerTest::parseMatchExpression(filterObj);
addWildcardIndex(BSON("$**" << 1), {}, {}, filterExpr.get());
- runQueryHint(fromjson("{a: {$eq: 1}, b: {$eq: 1}}"), BSON("$**" << 1));
- assertNumSolutions(0U);
+ runInvalidQueryHint(fromjson("{a: {$eq: 1}, b: {$eq: 1}}"), BSON("$**" << 1));
+ assertNoSolutions();
}
//
diff --git a/src/mongo/s/chunk_manager.cpp b/src/mongo/s/chunk_manager.cpp
index afc770e7061..3263ffa33e6 100644
--- a/src/mongo/s/chunk_manager.cpp
+++ b/src/mongo/s/chunk_manager.cpp
@@ -293,19 +293,23 @@ IndexBounds ChunkManager::getIndexBoundsForQuery(const BSONObj& key,
nullptr /* projExec */);
plannerParams.indices.push_back(std::move(indexEntry));
- auto solutions = uassertStatusOK(QueryPlanner::plan(canonicalQuery, plannerParams));
-
- IndexBounds bounds;
-
- for (auto&& soln : solutions) {
- // Try next solution if we failed to generate index bounds, i.e. bounds.size() == 0
- bounds = collapseQuerySolution(soln->root.get());
+ auto plannerResult = QueryPlanner::plan(canonicalQuery, plannerParams);
+ if (plannerResult.getStatus().code() != ErrorCodes::NoQueryExecutionPlans) {
+ auto solutions = uassertStatusOK(std::move(plannerResult));
+
+ // Pick any solution that has non-trivial IndexBounds. bounds.size() == 0 represents a
+ // trivial IndexBounds where none of the fields' values are bounded.
+ for (auto&& soln : solutions) {
+ IndexBounds bounds = collapseQuerySolution(soln->root.get());
+ if (bounds.size() > 0) {
+ return bounds;
+ }
+ }
}
- if (bounds.size() == 0) {
- // We cannot plan the query without collection scan, so target to all shards.
- IndexBoundsBuilder::allValuesBounds(key, &bounds); // [minKey, maxKey]
- }
+ // We cannot plan the query without collection scan, so target to all shards.
+ IndexBounds bounds;
+ IndexBoundsBuilder::allValuesBounds(key, &bounds); // [minKey, maxKey]
return bounds;
}