summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--buildscripts/resmokeconfig/suites/multi_shard_local_read_write_multi_stmt_txn_jscore_passthrough.yml3
-rw-r--r--buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_jscore_passthrough.yml3
-rw-r--r--buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_kill_primary_jscore_passthrough.yml3
-rw-r--r--buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_stepdown_primary_jscore_passthrough.yml3
-rw-r--r--buildscripts/resmokeconfig/suites/multi_stmt_txn_jscore_passthrough_with_migration.yml3
-rw-r--r--buildscripts/resmokeconfig/suites/replica_sets_kill_primary_jscore_passthrough.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_jscore_passthrough.yml3
-rw-r--r--buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_kill_primary_jscore_passthrough.yml3
-rw-r--r--buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_stepdown_jscore_passthrough.yml3
-rw-r--r--buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_terminate_primary_jscore_passthrough.yml3
-rw-r--r--buildscripts/resmokeconfig/suites/replica_sets_reconfig_jscore_passthrough.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/replica_sets_reconfig_jscore_stepdown_passthrough.yml2
-rw-r--r--buildscripts/resmokeconfig/suites/replica_sets_reconfig_kill_primary_jscore_passthrough.yml2
-rw-r--r--buildscripts/resmokeconfig/suites/replica_sets_terminate_primary_jscore_passthrough.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/retryable_writes_jscore_passthrough.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/retryable_writes_jscore_stepdown_passthrough.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/session_jscore_passthrough.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/sharded_multi_stmt_txn_jscore_passthrough.yml3
-rw-r--r--buildscripts/resmokeconfig/suites/tenant_migration_causally_consistent_jscore_passthrough.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/tenant_migration_kill_primary_jscore_passthrough.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/tenant_migration_multi_stmt_txn_jscore_passthrough.yml5
-rw-r--r--buildscripts/resmokeconfig/suites/tenant_migration_stepdown_jscore_passthrough.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/tenant_migration_terminate_primary_jscore_passthrough.yml1
-rw-r--r--jstests/core/exists7.js2
-rw-r--r--jstests/core/validate_cmd_ns.js24
-rw-r--r--jstests/noPassthrough/exhaust_option_disallowed_in_session.js32
-rw-r--r--jstests/noPassthrough/max_time_ms.js15
-rw-r--r--jstests/sharding/query_sharded.js25
-rw-r--r--src/mongo/client/dbclient_cursor.cpp255
-rw-r--r--src/mongo/client/dbclient_cursor.h2
-rw-r--r--src/mongo/client/query.cpp3
-rw-r--r--src/mongo/client/query.h1
-rw-r--r--src/mongo/client/query_spec.h124
-rw-r--r--src/mongo/db/query/query_request_helper.cpp35
-rw-r--r--src/mongo/db/query/query_request_helper.h10
-rw-r--r--src/mongo/db/query/query_request_test.cpp38
-rw-r--r--src/mongo/s/commands/cluster_explain_cmd.cpp9
-rw-r--r--src/mongo/shell/explain_query.js119
-rw-r--r--src/mongo/shell/mongo.js5
-rw-r--r--src/mongo/transport/service_state_machine.cpp49
40 files changed, 653 insertions, 144 deletions
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 aa9e84dbc1b..e830c47cd85 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
@@ -134,7 +134,7 @@ selector:
## Some aggregation stages aren't allowed in a transaction.
##
- # explain (requires read concern local)
+ # $explain (requires read concern local)
- jstests/core/agg_hint.js
- jstests/core/collation.js
- jstests/core/explain_shell_helpers.js
@@ -178,6 +178,7 @@ selector:
# SERVER-34868 Cannot run a legacy query on a session.
- jstests/core/exhaust.js
+ - jstests/core/validate_cmd_ns.js
# SERVER-34772 Tailable Cursors are not allowed in a transaction.
- jstests/core/awaitdata_getmore_cmd.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 a2d14686ceb..fead8bf2e92 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
@@ -136,7 +136,7 @@ selector:
## Some aggregation stages don't support snapshot readconcern.
##
- # explain (requires read concern local)
+ # $explain (requires read concern local)
- jstests/core/agg_hint.js
- jstests/core/and.js
- jstests/core/collation.js
@@ -200,6 +200,7 @@ selector:
# SERVER-34868 Cannot run a legacy query on a session.
- jstests/core/exhaust.js
+ - jstests/core/validate_cmd_ns.js
# SERVER-34772 Tailable Cursors are not allowed with snapshot readconcern.
- jstests/core/awaitdata_getmore_cmd.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 b61aed35737..5b68cfbb2b7 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
@@ -131,7 +131,7 @@ selector:
## Some aggregation stages don't support snapshot readconcern.
##
- # explain (requires read concern local)
+ # $explain (requires read concern local)
- jstests/core/agg_hint.js
- jstests/core/and.js
- jstests/core/collation.js
@@ -188,6 +188,7 @@ selector:
# SERVER-34868 Cannot run a legacy query on a session.
- jstests/core/exhaust.js
+ - jstests/core/validate_cmd_ns.js
# SERVER-34772 Tailable Cursors are not allowed with snapshot readconcern.
- jstests/core/awaitdata_getmore_cmd.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 4fe4a9b5343..f3cc72c5a3c 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
@@ -132,7 +132,7 @@ selector:
## Some aggregation stages don't support snapshot readconcern.
##
- # explain (requires read concern local)
+ # $explain (requires read concern local)
- jstests/core/agg_hint.js
- jstests/core/and.js
- jstests/core/collation.js
@@ -189,6 +189,7 @@ selector:
# SERVER-34868 Cannot run a legacy query on a session.
- jstests/core/exhaust.js
+ - jstests/core/validate_cmd_ns.js
# SERVER-34772 Tailable Cursors are not allowed with snapshot readconcern.
- jstests/core/awaitdata_getmore_cmd.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 1cb8898a421..108a850e49d 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
@@ -144,7 +144,7 @@ selector:
## Some aggregation stages don't support snapshot readconcern.
##
- # explain (requires read concern local)
+ # $explain (requires read concern local)
- jstests/core/agg_hint.js
- jstests/core/and.js
- jstests/core/collation.js
@@ -208,6 +208,7 @@ selector:
# SERVER-34868 Cannot run a legacy query on a session.
- jstests/core/exhaust.js
+ - jstests/core/validate_cmd_ns.js
# SERVER-34772 Tailable Cursors are not allowed with snapshot readconcern.
- jstests/core/awaitdata_getmore_cmd.js
diff --git a/buildscripts/resmokeconfig/suites/replica_sets_kill_primary_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/replica_sets_kill_primary_jscore_passthrough.yml
index 4d7ac98666e..2784b7ee3e6 100644
--- a/buildscripts/resmokeconfig/suites/replica_sets_kill_primary_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/replica_sets_kill_primary_jscore_passthrough.yml
@@ -47,6 +47,7 @@ selector:
- jstests/core/insert2.js # Creates new mongo connection.
- jstests/core/list_collections_filter.js # Temporary collections are dropped on failover.
- jstests/core/startup_log.js # Checks pid, which is different on each server.
+ - jstests/core/validate_cmd_ns.js # Calls _exec() directly, not retryable.
# Creates new mongo connection but won't retry connecting.
- jstests/core/shell_connection_strings.js
diff --git a/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_jscore_passthrough.yml
index f6e153321c3..b941a428ed3 100644
--- a/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_jscore_passthrough.yml
@@ -78,7 +78,7 @@ selector:
## Some aggregation stages don't support snapshot readconcern.
##
- # explain (requires read concern local)
+ # $explain (requires read concern local)
- jstests/core/agg_hint.js
- jstests/core/and.js
- jstests/core/collation.js
@@ -148,6 +148,7 @@ selector:
# SERVER-34868 Cannot run a legacy query on a session.
- jstests/core/exhaust.js
+ - jstests/core/validate_cmd_ns.js
# SERVER-34772 Tailable Cursors are not allowed with snapshot readconcern.
- jstests/core/awaitdata_getmore_cmd.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 658df1cca30..2d8f1367138 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
@@ -73,7 +73,7 @@ selector:
## Some aggregation stages don't support snapshot readconcern.
##
- # explain (requires read concern local)
+ # $explain (requires read concern local)
- jstests/core/agg_hint.js
- jstests/core/and.js
- jstests/core/collation.js
@@ -136,6 +136,7 @@ selector:
# SERVER-34868 Cannot run a legacy query on a session.
- jstests/core/exhaust.js
+ - jstests/core/validate_cmd_ns.js
# SERVER-34772 Tailable Cursors are not allowed with snapshot readconcern.
- jstests/core/awaitdata_getmore_cmd.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 13227f2f413..b75d012a07f 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
@@ -72,7 +72,7 @@ selector:
## Some aggregation stages don't support snapshot readconcern.
##
- # explain (requires read concern local)
+ # $explain (requires read concern local)
- jstests/core/agg_hint.js
- jstests/core/and.js
- jstests/core/collation.js
@@ -135,6 +135,7 @@ selector:
# SERVER-34868 Cannot run a legacy query on a session.
- jstests/core/exhaust.js
+ - jstests/core/validate_cmd_ns.js
# SERVER-34772 Tailable Cursors are not allowed with snapshot readconcern.
- jstests/core/awaitdata_getmore_cmd.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 a0a30f7a18b..c4f70cafc08 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
@@ -70,7 +70,7 @@ selector:
## Some aggregation stages don't support snapshot readconcern.
##
- # explain (requires read concern local)
+ # $explain (requires read concern local)
- jstests/core/agg_hint.js
- jstests/core/and.js
- jstests/core/collation.js
@@ -133,6 +133,7 @@ selector:
# SERVER-34868 Cannot run a legacy query on a session.
- jstests/core/exhaust.js
+ - jstests/core/validate_cmd_ns.js
# SERVER-34772 Tailable Cursors are not allowed with snapshot readconcern.
- jstests/core/awaitdata_getmore_cmd.js
diff --git a/buildscripts/resmokeconfig/suites/replica_sets_reconfig_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/replica_sets_reconfig_jscore_passthrough.yml
index a12f9273ee6..e6318e6bd0a 100644
--- a/buildscripts/resmokeconfig/suites/replica_sets_reconfig_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/replica_sets_reconfig_jscore_passthrough.yml
@@ -19,6 +19,7 @@ selector:
# These test run commands using legacy queries, which are not supported on sessions.
- jstests/core/comment_field.js
- jstests/core/exhaust.js
+ - jstests/core/validate_cmd_ns.js
# Unacknowledged writes prohibited in an explicit session.
- jstests/core/batch_write_command_w0.js
diff --git a/buildscripts/resmokeconfig/suites/replica_sets_reconfig_jscore_stepdown_passthrough.yml b/buildscripts/resmokeconfig/suites/replica_sets_reconfig_jscore_stepdown_passthrough.yml
index f0187167f88..b2f526f9170 100644
--- a/buildscripts/resmokeconfig/suites/replica_sets_reconfig_jscore_stepdown_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/replica_sets_reconfig_jscore_stepdown_passthrough.yml
@@ -34,6 +34,7 @@ selector:
# These test run commands using legacy queries, which are not supported on sessions.
- jstests/core/comment_field.js
- jstests/core/exhaust.js
+ - jstests/core/validate_cmd_ns.js
# Stepdown commands during fsync lock will fail.
- jstests/core/currentop.js
@@ -61,6 +62,7 @@ selector:
- jstests/core/list_collections_filter.js # Temporary collections are dropped on failover.
- jstests/core/top.js # Tests read commands (including getMore) against the secondary
- jstests/core/drop3.js # getMore is not causally consistent if collection is dropped
+ - jstests/core/validate_cmd_ns.js # Calls _exec() directly, not retryable.
- jstests/core/list_collections_filter.js # Temporary collections are dropped on failover.
- jstests/core/explain_large_bounds.js # Stepdown can timeout waiting for global lock.
diff --git a/buildscripts/resmokeconfig/suites/replica_sets_reconfig_kill_primary_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/replica_sets_reconfig_kill_primary_jscore_passthrough.yml
index 7462c89afb2..92db4edf639 100644
--- a/buildscripts/resmokeconfig/suites/replica_sets_reconfig_kill_primary_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/replica_sets_reconfig_kill_primary_jscore_passthrough.yml
@@ -35,6 +35,7 @@ selector:
# These test run commands using legacy queries, which are not supported on sessions.
- jstests/core/comment_field.js
- jstests/core/exhaust.js
+ - jstests/core/validate_cmd_ns.js
# Stepdown commands during fsync lock will fail.
- jstests/core/currentop.js
@@ -62,6 +63,7 @@ selector:
- jstests/core/list_collections_filter.js # Temporary collections are dropped on failover.
- jstests/core/top.js # Tests read commands (including getMore) against the secondary
- jstests/core/drop3.js # getMore is not causally consistent if collection is dropped
+ - jstests/core/validate_cmd_ns.js # Calls _exec() directly, not retryable.
- jstests/core/list_collections_filter.js # Temporary collections are dropped on failover.
- jstests/core/explain_large_bounds.js # Stepdown can timeout waiting for global lock.
diff --git a/buildscripts/resmokeconfig/suites/replica_sets_terminate_primary_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/replica_sets_terminate_primary_jscore_passthrough.yml
index 22e53534b16..0decf8530d8 100644
--- a/buildscripts/resmokeconfig/suites/replica_sets_terminate_primary_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/replica_sets_terminate_primary_jscore_passthrough.yml
@@ -47,6 +47,7 @@ selector:
- jstests/core/insert2.js # Creates new mongo connection.
- jstests/core/list_collections_filter.js # Temporary collections are dropped on failover.
- jstests/core/startup_log.js # Checks pid, which is different on each server.
+ - jstests/core/validate_cmd_ns.js # Calls _exec() directly, not retryable.
# Creates new mongo connection but won't retry connecting.
- jstests/core/shell_connection_strings.js
diff --git a/buildscripts/resmokeconfig/suites/retryable_writes_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/retryable_writes_jscore_passthrough.yml
index 6a16af8357c..101b4295dfa 100644
--- a/buildscripts/resmokeconfig/suites/retryable_writes_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/retryable_writes_jscore_passthrough.yml
@@ -19,6 +19,7 @@ selector:
# These test run commands using legacy queries, which are not supported on sessions.
- jstests/core/comment_field.js
- jstests/core/exhaust.js
+ - jstests/core/validate_cmd_ns.js
# TODO SERVER-31242: findAndModify no-op retry should respect the fields option.
- jstests/core/crud_api.js
diff --git a/buildscripts/resmokeconfig/suites/retryable_writes_jscore_stepdown_passthrough.yml b/buildscripts/resmokeconfig/suites/retryable_writes_jscore_stepdown_passthrough.yml
index fd1a3c2963a..f81f81d153f 100644
--- a/buildscripts/resmokeconfig/suites/retryable_writes_jscore_stepdown_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/retryable_writes_jscore_stepdown_passthrough.yml
@@ -51,6 +51,7 @@ selector:
- jstests/core/insert2.js # Creates new mongo connection.
- jstests/core/list_collections_filter.js # Temporary collections are dropped on failover.
- jstests/core/startup_log.js # Checks pid, which is different on each server.
+ - jstests/core/validate_cmd_ns.js # Calls _exec() directly, not retryable.
exclude_with_any_tags:
- assumes_standalone_mongod
diff --git a/buildscripts/resmokeconfig/suites/session_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/session_jscore_passthrough.yml
index 9a44de18f12..2bb5fdfb37d 100644
--- a/buildscripts/resmokeconfig/suites/session_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/session_jscore_passthrough.yml
@@ -10,6 +10,7 @@ selector:
# These test run commands using legacy queries, which are not supported on sessions.
- jstests/core/comment_field.js
- jstests/core/exhaust.js
+ - jstests/core/validate_cmd_ns.js
# Unacknowledged writes prohibited in an explicit session.
- jstests/core/crud_api.js
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 a631b980d9f..f38fc8934f6 100644
--- a/buildscripts/resmokeconfig/suites/sharded_multi_stmt_txn_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/sharded_multi_stmt_txn_jscore_passthrough.yml
@@ -104,7 +104,7 @@ selector:
## Some aggregation stages don't support snapshot readconcern.
##
- # explain (requires read concern local)
+ # $explain (requires read concern local)
- jstests/core/agg_hint.js
- jstests/core/and.js
- jstests/core/collation.js
@@ -172,6 +172,7 @@ selector:
# SERVER-34868 Cannot run a legacy query on a session.
- jstests/core/exhaust.js
+ - jstests/core/validate_cmd_ns.js
# SERVER-34772 Tailable Cursors are not allowed with snapshot readconcern.
- jstests/core/awaitdata_getmore_cmd.js
diff --git a/buildscripts/resmokeconfig/suites/tenant_migration_causally_consistent_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/tenant_migration_causally_consistent_jscore_passthrough.yml
index f5aaf2f72b5..cc0c454e223 100644
--- a/buildscripts/resmokeconfig/suites/tenant_migration_causally_consistent_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/tenant_migration_causally_consistent_jscore_passthrough.yml
@@ -144,6 +144,7 @@ selector:
# These test run commands using legacy queries, which are not supported on sessions.
- jstests/core/comment_field.js
- jstests/core/exhaust.js
+ - jstests/core/validate_cmd_ns.js
# Unacknowledged writes prohibited in an explicit session.
- jstests/core/crud_api.js
diff --git a/buildscripts/resmokeconfig/suites/tenant_migration_kill_primary_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/tenant_migration_kill_primary_jscore_passthrough.yml
index 3b00933e879..afa3606c245 100644
--- a/buildscripts/resmokeconfig/suites/tenant_migration_kill_primary_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/tenant_migration_kill_primary_jscore_passthrough.yml
@@ -144,6 +144,7 @@ selector:
- jstests/core/insert2.js # Creates new mongo connection.
- jstests/core/list_collections_filter.js # Temporary collections are dropped on failover.
- jstests/core/startup_log.js # Checks pid, which is different on each server.
+ - jstests/core/validate_cmd_ns.js # Calls _exec() directly, not retryable.
#
# Denylists specific to this suite
diff --git a/buildscripts/resmokeconfig/suites/tenant_migration_multi_stmt_txn_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/tenant_migration_multi_stmt_txn_jscore_passthrough.yml
index 5c813fd7a53..d0555fc4e6d 100644
--- a/buildscripts/resmokeconfig/suites/tenant_migration_multi_stmt_txn_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/tenant_migration_multi_stmt_txn_jscore_passthrough.yml
@@ -193,7 +193,7 @@ selector:
## Some aggregation stages don't support snapshot readconcern.
##
- # explain (requires read concern local)
+ # $explain (requires read concern local)
- jstests/core/agg_hint.js
- jstests/core/and.js
- jstests/core/collation.js
@@ -253,6 +253,9 @@ selector:
## Misc. reasons.
##
+ # SERVER-34868 Cannot run a legacy query on a session.
+ - jstests/core/validate_cmd_ns.js
+
# SERVER-34772 Tailable Cursors are not allowed with snapshot readconcern.
- jstests/core/awaitdata_getmore_cmd.js
- jstests/core/getmore_cmd_maxtimems.js
diff --git a/buildscripts/resmokeconfig/suites/tenant_migration_stepdown_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/tenant_migration_stepdown_jscore_passthrough.yml
index be354b863c4..8be100ca0f6 100644
--- a/buildscripts/resmokeconfig/suites/tenant_migration_stepdown_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/tenant_migration_stepdown_jscore_passthrough.yml
@@ -144,6 +144,7 @@ selector:
- jstests/core/insert2.js # Creates new mongo connection.
- jstests/core/list_collections_filter.js # Temporary collections are dropped on failover.
- jstests/core/startup_log.js # Checks pid, which is different on each server.
+ - jstests/core/validate_cmd_ns.js # Calls _exec() directly, not retryable.
#
# Denylists specific to this suite
diff --git a/buildscripts/resmokeconfig/suites/tenant_migration_terminate_primary_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/tenant_migration_terminate_primary_jscore_passthrough.yml
index dc26c216799..87679d6a701 100644
--- a/buildscripts/resmokeconfig/suites/tenant_migration_terminate_primary_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/tenant_migration_terminate_primary_jscore_passthrough.yml
@@ -144,6 +144,7 @@ selector:
- jstests/core/insert2.js # Creates new mongo connection.
- jstests/core/list_collections_filter.js # Temporary collections are dropped on failover.
- jstests/core/startup_log.js # Checks pid, which is different on each server.
+ - jstests/core/validate_cmd_ns.js # Calls _exec() directly, not retryable.
#
# Denylists specific to this suite
diff --git a/jstests/core/exists7.js b/jstests/core/exists7.js
index 285559e82f8..6ca55a42286 100644
--- a/jstests/core/exists7.js
+++ b/jstests/core/exists7.js
@@ -1,6 +1,6 @@
// @tags: [requires_non_retryable_writes]
-// Test that non boolean value types are allowed with $exists spec. SERVER-2322
+// Test that non boolean value types are allowed with $explain spec. SERVER-2322
t = db.jstests_exists7;
t.drop();
diff --git a/jstests/core/validate_cmd_ns.js b/jstests/core/validate_cmd_ns.js
new file mode 100644
index 00000000000..f5f706335ad
--- /dev/null
+++ b/jstests/core/validate_cmd_ns.js
@@ -0,0 +1,24 @@
+/**
+ * Tests that query against the $cmd namespace will error out when the request has
+ * a number to return value other than 1 or -1. Note that users cannot have
+ * collections named $cmd since $ is an illegal character.
+ */
+
+// Note: _exec gives you the raw response from the server.
+var res = db.$cmd.find({whatsmyuri: 1})._exec().next();
+assert.commandFailed(res);
+assert(res.errmsg.indexOf('Bad numberToReturn') > -1);
+
+res = db.$cmd.find({whatsmyuri: 1}).limit(0)._exec().next();
+assert.commandFailed(res);
+assert(res.errmsg.indexOf('Bad numberToReturn') > -1);
+
+res = db.$cmd.find({whatsmyuri: 1}).limit(-2)._exec().next();
+assert.commandFailed(res);
+assert(res.errmsg.indexOf('Bad numberToReturn') > -1);
+
+res = db.$cmd.find({whatsmyuri: 1}).limit(1).next();
+assert.commandWorked(res);
+
+res = db.$cmd.find({whatsmyuri: 1}).limit(-1).next();
+assert.commandWorked(res);
diff --git a/jstests/noPassthrough/exhaust_option_disallowed_in_session.js b/jstests/noPassthrough/exhaust_option_disallowed_in_session.js
new file mode 100644
index 00000000000..1ba1014dc2f
--- /dev/null
+++ b/jstests/noPassthrough/exhaust_option_disallowed_in_session.js
@@ -0,0 +1,32 @@
+/**
+ * Make sure the 'exhaust' query option is not able to be used in a session.
+ */
+(function() {
+"use strict";
+
+let conn = MongoRunner.runMongod();
+
+const dbName = 'test';
+const collName = 'coll';
+
+const session = conn.startSession();
+const sessionColl = session.getDatabase(dbName).getCollection(collName);
+const testColl = conn.getDB(dbName).getCollection(collName);
+
+testColl.drop();
+
+// Create a collection to query.
+assert.commandWorked(testColl.insert({_id: 1}));
+
+// Exhaust outside of session should work.
+let docs = testColl.find().addOption(DBQuery.Option.exhaust).toArray();
+assert.docEq([{_id: 1}], docs);
+
+// Exhaust in session should fail.
+assert.throws(() => {
+ sessionColl.find().addOption(DBQuery.Option.exhaust).toArray();
+});
+
+session.endSession();
+MongoRunner.stopMongod(conn);
+}());
diff --git a/jstests/noPassthrough/max_time_ms.js b/jstests/noPassthrough/max_time_ms.js
index a780b21cbbb..050b85ac648 100644
--- a/jstests/noPassthrough/max_time_ms.js
+++ b/jstests/noPassthrough/max_time_ms.js
@@ -320,9 +320,20 @@ function executeTest(db, isMongos) {
ErrorCodes.BadValue);
assert.commandFailedWithCode(db.runCommand({ping: 1, maxTimeMS: {}}), ErrorCodes.BadValue);
+ // Verify that the maxTimeMS command argument can be sent with $query-wrapped commands.
+ cursor = t.getDB().$cmd.find({ping: 1, maxTimeMS: 0}).limit(-1);
+ cursor._ensureSpecial();
+ assert.eq(1, cursor.next().ok);
+
// Verify that the server rejects invalid command argument $maxTimeMS.
- assert.commandFailed(t.getDB().runCommand({ping: 1, $maxTimeMS: 0}),
- [ErrorCodes.InvalidOptions, 40415]);
+ cursor = t.getDB().$cmd.find({ping: 1, $maxTimeMS: 0}).limit(-1);
+ cursor._ensureSpecial();
+ assert.commandFailed(cursor.next());
+
+ // Verify that the $maxTimeMS query option can't be sent with $query-wrapped commands.
+ cursor = t.getDB().$cmd.find({ping: 1}).limit(-1).maxTimeMS(0);
+ cursor._ensureSpecial();
+ assert.commandFailed(cursor.next());
})();
//
diff --git a/jstests/sharding/query_sharded.js b/jstests/sharding/query_sharded.js
new file mode 100644
index 00000000000..a043f257d6c
--- /dev/null
+++ b/jstests/sharding/query_sharded.js
@@ -0,0 +1,25 @@
+//
+// Tests mongos-only query behavior
+//
+
+var st = new ShardingTest({shards: 1, mongos: 1, verbose: 0});
+
+var mongos = st.s0;
+var coll = mongos.getCollection("foo.bar");
+
+//
+//
+// Ensure we can't trick mongos by inserting exhaust option on a command through mongos
+coll.remove({});
+assert.commandWorked(coll.insert({a: 'b'}));
+var cmdColl = mongos.getCollection(coll.getDB().toString() + ".$cmd");
+var cmdQuery = cmdColl.find({ping: 1}).limit(1);
+assert.commandWorked(cmdQuery.next());
+cmdQuery = cmdColl.find({ping: 1}).limit(1).addOption(DBQuery.Option.exhaust);
+assert.throws(function() {
+ assert.commandWorked(cmdQuery.next());
+});
+
+jsTest.log("DONE!");
+
+st.stop();
diff --git a/src/mongo/client/dbclient_cursor.cpp b/src/mongo/client/dbclient_cursor.cpp
index 96a509da122..db9cc55d6f3 100644
--- a/src/mongo/client/dbclient_cursor.cpp
+++ b/src/mongo/client/dbclient_cursor.cpp
@@ -103,72 +103,106 @@ Message DBClientCursor::_assembleInit() {
}
// If we haven't gotten a cursorId yet, we need to issue a new query or command.
- // The caller supplies a 'query' object which may have $-prefixed directives in the format
- // expected for a legacy OP_QUERY. Therefore, we use the legacy parsing code supplied by
- // query_request_helper. When actually issuing the request to the remote node, we will
- // assemble a find command.
- auto findCommand =
- query_request_helper::fromLegacyQuery(_nsOrUuid,
- query,
- fieldsToReturn ? *fieldsToReturn : BSONObj(),
- nToSkip,
- nextBatchSize(),
- opts);
- // If there was a problem building the query request, report that.
- uassertStatusOK(findCommand.getStatus());
-
- if (query.getBoolField("$readOnce")) {
- // Legacy queries don't handle readOnce.
- findCommand.getValue()->setReadOnce(true);
- }
- if (query.getBoolField(FindCommandRequest::kRequestResumeTokenFieldName)) {
- // Legacy queries don't handle requestResumeToken.
- findCommand.getValue()->setRequestResumeToken(true);
- }
- if (query.hasField(FindCommandRequest::kResumeAfterFieldName)) {
- // Legacy queries don't handle resumeAfter.
- findCommand.getValue()->setResumeAfter(
- query.getObjectField(FindCommandRequest::kResumeAfterFieldName));
- }
- if (auto replTerm = query[FindCommandRequest::kTermFieldName]) {
- // Legacy queries don't handle term.
- findCommand.getValue()->setTerm(replTerm.numberLong());
- }
- // Legacy queries don't handle readConcern.
- // We prioritize the readConcern parsed from the query object over '_readConcernObj'.
- if (auto readConcern = query[repl::ReadConcernArgs::kReadConcernFieldName]) {
- findCommand.getValue()->setReadConcern(readConcern.Obj());
- } else if (_readConcernObj) {
- findCommand.getValue()->setReadConcern(_readConcernObj);
- }
- BSONObj cmd = findCommand.getValue()->toBSON(BSONObj());
- if (auto readPref = query["$readPreference"]) {
- // FindCommandRequest doesn't handle $readPreference.
- cmd = BSONObjBuilder(std::move(cmd)).append(readPref).obj();
+ if (_isCommand) {
+ // HACK:
+ // Unfortunately, this code is used by the shell to run commands,
+ // so we need to allow the shell to send invalid options so that we can
+ // test that the server rejects them. Thus, to allow generating commands with
+ // invalid options, we validate them here, and fall back to generating an OP_QUERY
+ // through makeDeprecatedQueryMessage() if the options are invalid.
+ bool hasValidNToReturnForCommand = (nToReturn == 1 || nToReturn == -1);
+ bool hasValidFlagsForCommand = !(opts & mongo::QueryOption_Exhaust);
+ bool hasInvalidMaxTimeMs = query.hasField("$maxTimeMS");
+
+ if (hasValidNToReturnForCommand && hasValidFlagsForCommand && !hasInvalidMaxTimeMs) {
+ return assembleCommandRequest(_client, ns.db(), opts, query);
+ }
+ } else if (_useFindCommand) {
+ // The caller supplies a 'query' object which may have $-prefixed directives in the format
+ // expected for a legacy OP_QUERY. Therefore, we use the legacy parsing code supplied by
+ // query_request_helper. When actually issuing the request to the remote node, we will
+ // assemble a find command.
+ bool explain = false;
+ auto findCommand =
+ query_request_helper::fromLegacyQuery(_nsOrUuid,
+ query,
+ fieldsToReturn ? *fieldsToReturn : BSONObj(),
+ nToSkip,
+ nextBatchSize(),
+ opts,
+ &explain);
+ if (findCommand.isOK() && !explain) {
+ if (query.getBoolField("$readOnce")) {
+ // Legacy queries don't handle readOnce.
+ findCommand.getValue()->setReadOnce(true);
+ }
+ if (query.getBoolField(FindCommandRequest::kRequestResumeTokenFieldName)) {
+ // Legacy queries don't handle requestResumeToken.
+ findCommand.getValue()->setRequestResumeToken(true);
+ }
+ if (query.hasField(FindCommandRequest::kResumeAfterFieldName)) {
+ // Legacy queries don't handle resumeAfter.
+ findCommand.getValue()->setResumeAfter(
+ query.getObjectField(FindCommandRequest::kResumeAfterFieldName));
+ }
+ if (auto replTerm = query[FindCommandRequest::kTermFieldName]) {
+ // Legacy queries don't handle term.
+ findCommand.getValue()->setTerm(replTerm.numberLong());
+ }
+ // Legacy queries don't handle readConcern.
+ // We prioritize the readConcern parsed from the query object over '_readConcernObj'.
+ if (auto readConcern = query[repl::ReadConcernArgs::kReadConcernFieldName]) {
+ findCommand.getValue()->setReadConcern(readConcern.Obj());
+ } else if (_readConcernObj) {
+ findCommand.getValue()->setReadConcern(_readConcernObj);
+ }
+ BSONObj cmd = findCommand.getValue()->toBSON(BSONObj());
+
+ if (auto readPref = query["$readPreference"]) {
+ // FindCommandRequest doesn't handle $readPreference.
+ cmd = BSONObjBuilder(std::move(cmd)).append(readPref).obj();
+ }
+ return assembleCommandRequest(_client, ns.db(), opts, std::move(cmd));
+ }
+ // else use legacy OP_QUERY request.
+ // Legacy OP_QUERY request does not support UUIDs.
+ if (_nsOrUuid.uuid()) {
+ // If there was a problem building the query request, report that.
+ uassertStatusOK(findCommand.getStatus());
+ // Otherwise it must have been explain.
+ uasserted(50937, "Query by UUID is not supported for explain queries.");
+ }
}
- return assembleCommandRequest(_client, ns.db(), opts, std::move(cmd));
+ _useFindCommand = false; // Make sure we handle the reply correctly.
+ return makeDeprecatedQueryMessage(
+ ns.ns(), query, nextBatchSize(), nToSkip, fieldsToReturn, opts);
}
Message DBClientCursor::_assembleGetMore() {
invariant(cursorId);
- std::int64_t batchSize = nextBatchSize();
- auto getMoreRequest = GetMoreCommandRequest(cursorId, ns.coll().toString());
- getMoreRequest.setBatchSize(boost::make_optional(batchSize != 0, batchSize));
- getMoreRequest.setMaxTimeMS(boost::make_optional(
- tailableAwaitData(),
- static_cast<std::int64_t>(durationCount<Milliseconds>(_awaitDataTimeout))));
- if (_term) {
- getMoreRequest.setTerm(static_cast<std::int64_t>(*_term));
- }
- getMoreRequest.setLastKnownCommittedOpTime(_lastKnownCommittedOpTime);
- auto msg = assembleCommandRequest(_client, ns.db(), opts, getMoreRequest.toBSON({}));
+ if (_useFindCommand) {
+ std::int64_t batchSize = nextBatchSize();
+ auto getMoreRequest = GetMoreCommandRequest(cursorId, ns.coll().toString());
+ getMoreRequest.setBatchSize(boost::make_optional(batchSize != 0, batchSize));
+ getMoreRequest.setMaxTimeMS(boost::make_optional(
+ tailableAwaitData(),
+ static_cast<std::int64_t>(durationCount<Milliseconds>(_awaitDataTimeout))));
+ if (_term) {
+ getMoreRequest.setTerm(static_cast<std::int64_t>(*_term));
+ }
+ getMoreRequest.setLastKnownCommittedOpTime(_lastKnownCommittedOpTime);
+ auto msg = assembleCommandRequest(_client, ns.db(), opts, getMoreRequest.toBSON({}));
- // Set the exhaust flag if needed.
- if (opts & QueryOption_Exhaust && msg.operation() == dbMsg) {
- OpMsg::setFlag(&msg, OpMsg::kExhaustSupported);
+ // Set the exhaust flag if needed.
+ if (opts & QueryOption_Exhaust && msg.operation() == dbMsg) {
+ OpMsg::setFlag(&msg, OpMsg::kExhaustSupported);
+ }
+ return msg;
+ } else {
+ // Assemble a legacy getMore request.
+ return makeDeprecatedGetMoreMessage(ns.ns(), cursorId, nextBatchSize(), opts);
}
- return msg;
}
bool DBClientCursor::init() {
@@ -305,22 +339,87 @@ void DBClientCursor::dataReceived(const Message& reply, bool& retry, string& hos
batch.objs.clear();
batch.pos = 0;
- const auto replyObj = commandDataReceived(reply);
- cursorId = 0; // Don't try to kill cursor if we get back an error.
- auto cr = uassertStatusOK(CursorResponse::parseFromBSON(replyObj));
- cursorId = cr.getCursorId();
- uassert(50935,
- "Received a getMore response with a cursor id of 0 and the moreToCome flag set.",
- !(_connectionHasPendingReplies && cursorId == 0));
-
- ns = cr.getNSS(); // find command can change the ns to use for getMores.
- // Store the resume token, if we got one.
- _postBatchResumeToken = cr.getPostBatchResumeToken();
- batch.objs = cr.releaseBatch();
-
- if (replyObj.hasField(LogicalTime::kOperationTimeFieldName)) {
- _operationTime = LogicalTime::fromOperationTime(replyObj).asTimestamp();
+ // If this is a reply to our initial command request.
+ if (_isCommand && cursorId == 0) {
+ batch.objs.push_back(commandDataReceived(reply));
+ return;
+ }
+
+ if (_useFindCommand) {
+ const auto replyObj = commandDataReceived(reply);
+ cursorId = 0; // Don't try to kill cursor if we get back an error.
+ auto cr = uassertStatusOK(CursorResponse::parseFromBSON(replyObj));
+ cursorId = cr.getCursorId();
+ uassert(50935,
+ "Received a getMore response with a cursor id of 0 and the moreToCome flag set.",
+ !(_connectionHasPendingReplies && cursorId == 0));
+
+ ns = cr.getNSS(); // Unlike OP_REPLY, find command can change the ns to use for getMores.
+ // Store the resume token, if we got one.
+ _postBatchResumeToken = cr.getPostBatchResumeToken();
+ batch.objs = cr.releaseBatch();
+
+ if (replyObj.hasField(LogicalTime::kOperationTimeFieldName)) {
+ _operationTime = LogicalTime::fromOperationTime(replyObj).asTimestamp();
+ }
+ return;
+ }
+
+ QueryResult::View qr = reply.singleData().view2ptr();
+ resultFlags = qr.getResultFlags();
+
+ if (resultFlags & ResultFlag_ErrSet) {
+ wasError = true;
+ }
+
+ if (resultFlags & ResultFlag_CursorNotFound) {
+ // cursor id no longer valid at the server.
+ invariant(qr.getCursorId() == 0);
+
+ // 0 indicates no longer valid (dead).
+ cursorId = 0;
+
+ uasserted(ErrorCodes::CursorNotFound,
+ str::stream() << "cursor id " << cursorId << " didn't exist on server.");
+ }
+
+ if (cursorId == 0 || !(opts & QueryOption_CursorTailable)) {
+ // only set initially: we don't want to kill it on end of data
+ // if it's a tailable cursor
+ cursorId = qr.getCursorId();
}
+
+ if (opts & QueryOption_Exhaust) {
+ // With exhaust mode, each reply after the first claims to be a reply to the previous one
+ // rather than the initial request.
+ _connectionHasPendingReplies = (cursorId != 0);
+ _lastRequestId = reply.header().getId();
+ }
+
+ batch.objs.reserve(qr.getNReturned());
+
+ BufReader data(qr.data(), qr.dataLen());
+ while (static_cast<int>(batch.objs.size()) < qr.getNReturned()) {
+ if (serverGlobalParams.objcheck) {
+ batch.objs.push_back(data.read<Validated<BSONObj>>());
+ } else {
+ batch.objs.push_back(data.read<BSONObj>());
+ }
+ batch.objs.back().shareOwnershipWith(reply.sharedBuffer());
+ }
+ uassert(ErrorCodes::InvalidBSON,
+ "Got invalid reply from external server while reading from cursor",
+ data.atEof());
+
+ _client->checkResponse(batch.objs, false, &retry, &host); // watches for "not primary"
+
+ tassert(5262101,
+ "Deprecated ShardConfigStale flag encountered in query result",
+ !(resultFlags & ResultFlag_ShardConfigStaleDeprecated));
+
+ /* this assert would fire the way we currently work:
+ verify( nReturned || cursorId == 0 );
+ */
}
/** If true, safe to call next(). Requests more from server if necessary. */
@@ -484,6 +583,7 @@ DBClientCursor::DBClientCursor(DBClientBase* client,
_originalHost(_client->getServerAddress()),
_nsOrUuid(nsOrUuid),
ns(nsOrUuid.nss() ? *nsOrUuid.nss() : NamespaceString(nsOrUuid.dbname())),
+ _isCommand(ns.isCommand()),
query(query),
nToReturn(nToReturn),
haveLimit(nToReturn > 0 && !(queryOptions & QueryOption_CursorTailable)),
@@ -539,7 +639,14 @@ DBClientCursor::~DBClientCursor() {
void DBClientCursor::kill() {
DESTRUCTOR_GUARD({
if (cursorId && _ownCursor && !globalInShutdownDeprecated()) {
- auto killCursor = [&](auto&& conn) { conn->killCursor(ns, cursorId); };
+ auto killCursor = [&](auto&& conn) {
+ if (_useFindCommand) {
+ conn->killCursor(ns, cursorId);
+ } else {
+ auto toSend = makeDeprecatedKillCursorsMessage(cursorId);
+ conn->say(toSend);
+ }
+ };
// We only need to kill the cursor if there aren't pending replies. Pending replies
// indicates that this is an exhaust cursor, so the connection must be closed and the
diff --git a/src/mongo/client/dbclient_cursor.h b/src/mongo/client/dbclient_cursor.h
index c3555a698e6..9fd0139254c 100644
--- a/src/mongo/client/dbclient_cursor.h
+++ b/src/mongo/client/dbclient_cursor.h
@@ -299,6 +299,7 @@ private:
// After a successful 'find' command, 'ns' is updated to contain the namespace returned by that
// command.
NamespaceString ns;
+ const bool _isCommand;
BSONObj query;
int nToReturn;
bool haveLimit;
@@ -313,6 +314,7 @@ private:
std::string _scopedHost;
std::string _lazyHost;
bool wasError;
+ bool _useFindCommand = true;
bool _connectionHasPendingReplies = false;
int _lastRequestId = 0;
Milliseconds _awaitDataTimeout = Milliseconds{0};
diff --git a/src/mongo/client/query.cpp b/src/mongo/client/query.cpp
index 9aa28e92711..6f8ae2e3615 100644
--- a/src/mongo/client/query.cpp
+++ b/src/mongo/client/query.cpp
@@ -146,6 +146,9 @@ BSONObj Query::getHint() const {
return BSONObj();
return obj.getObjectField("$hint");
}
+bool Query::isExplain() const {
+ return isComplex() && obj.getBoolField("$explain");
+}
string Query::toString() const {
return obj.toString();
diff --git a/src/mongo/client/query.h b/src/mongo/client/query.h
index 6ef2b2531c0..f295873d98e 100644
--- a/src/mongo/client/query.h
+++ b/src/mongo/client/query.h
@@ -131,6 +131,7 @@ public:
BSONObj getFilter() const;
BSONObj getSort() const;
BSONObj getHint() const;
+ bool isExplain() const;
/**
* @return true if the query object contains a read preference specification object.
diff --git a/src/mongo/client/query_spec.h b/src/mongo/client/query_spec.h
new file mode 100644
index 00000000000..534a841842f
--- /dev/null
+++ b/src/mongo/client/query_spec.h
@@ -0,0 +1,124 @@
+/**
+ * Copyright (C) 2018-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#pragma once
+
+#include "mongo/bson/bsonobj.h"
+#include "mongo/client/query.h"
+#include "mongo/util/str.h"
+
+namespace mongo {
+/**
+ * Represents a full query description, including all options required for the query to be passed on
+ * to other hosts
+ */
+class QuerySpec {
+ std::string _ns;
+ int _ntoskip;
+ int _ntoreturn;
+ int _options;
+ BSONObj _query;
+ BSONObj _fields;
+ Query _queryObj;
+
+public:
+ QuerySpec(const std::string& ns,
+ const BSONObj& query,
+ const BSONObj& fields,
+ int ntoskip,
+ int ntoreturn,
+ int options)
+ : _ns(ns),
+ _ntoskip(ntoskip),
+ _ntoreturn(ntoreturn),
+ _options(options),
+ _query(query.getOwned()),
+ _fields(fields.getOwned()),
+ _queryObj(_query) {}
+
+ QuerySpec() {}
+
+ bool isEmpty() const {
+ return _ns.size() == 0;
+ }
+
+ bool isExplain() const {
+ return _queryObj.isExplain();
+ }
+ BSONObj filter() const {
+ return _queryObj.getFilter();
+ }
+
+ BSONObj hint() const {
+ return _queryObj.getHint();
+ }
+ BSONObj sort() const {
+ return _queryObj.getSort();
+ }
+ BSONObj query() const {
+ return _query;
+ }
+ BSONObj fields() const {
+ return _fields;
+ }
+ BSONObj* fieldsData() {
+ return &_fields;
+ }
+
+ // don't love this, but needed downstrem
+ const BSONObj* fieldsPtr() const {
+ return &_fields;
+ }
+
+ std::string ns() const {
+ return _ns;
+ }
+ int ntoskip() const {
+ return _ntoskip;
+ }
+ int ntoreturn() const {
+ return _ntoreturn;
+ }
+ int options() const {
+ return _options;
+ }
+
+ void setFields(BSONObj& o) {
+ _fields = o.getOwned();
+ }
+
+ std::string toString() const {
+ return str::stream() << "QSpec "
+ << BSON("ns" << _ns << "n2skip" << _ntoskip << "n2return" << _ntoreturn
+ << "options" << _options << "query" << _query << "fields"
+ << _fields);
+ }
+};
+
+} // namespace mongo
diff --git a/src/mongo/db/query/query_request_helper.cpp b/src/mongo/db/query/query_request_helper.cpp
index 5f5ce186be8..b466781d5a3 100644
--- a/src/mongo/db/query/query_request_helper.cpp
+++ b/src/mongo/db/query/query_request_helper.cpp
@@ -92,7 +92,7 @@ void addMetaProjection(FindCommandRequest* findCommand) {
}
}
-Status initFullQuery(const BSONObj& top, FindCommandRequest* findCommand) {
+Status initFullQuery(const BSONObj& top, FindCommandRequest* findCommand, bool* explain) {
BSONObjIterator i(top);
while (i.more()) {
@@ -140,8 +140,8 @@ Status initFullQuery(const BSONObj& top, FindCommandRequest* findCommand) {
} else if (name.startsWith("$")) {
name = name.substr(1); // chop first char
if (name == "explain") {
- return Status(ErrorCodes::Error(5856600),
- "the $explain OP_QUERY flag is no longer supported");
+ // Won't throw.
+ *explain = e.trueValue();
} else if (name == "min") {
if (!e.isABSONObj()) {
return Status(ErrorCodes::BadValue, "$min must be a BSONObj");
@@ -191,7 +191,8 @@ Status initFindCommandRequest(int ntoskip,
const BSONObj& queryObj,
const BSONObj& proj,
bool fromQueryMessage,
- FindCommandRequest* findCommand) {
+ FindCommandRequest* findCommand,
+ bool* explain) {
if (!proj.isEmpty()) {
findCommand->setProjection(proj.getOwned());
}
@@ -227,7 +228,7 @@ Status initFindCommandRequest(int ntoskip,
}
if (queryField.isABSONObj()) {
findCommand->setFilter(queryField.embeddedObject().getOwned());
- Status status = initFullQuery(queryObj, findCommand);
+ Status status = initFullQuery(queryObj, findCommand, explain);
if (!status.isOK()) {
return status;
}
@@ -417,16 +418,36 @@ void validateCursorResponse(const BSONObj& outputAsBson) {
// Old QueryRequest parsing code: SOON TO BE DEPRECATED.
//
+StatusWith<std::unique_ptr<FindCommandRequest>> fromLegacyQueryMessage(const QueryMessage& qm,
+ bool* explain) {
+ auto findCommand = std::make_unique<FindCommandRequest>(NamespaceString(qm.ns));
+
+ Status status = initFindCommandRequest(qm.ntoskip,
+ qm.ntoreturn,
+ qm.queryOptions,
+ qm.query,
+ qm.fields,
+ true,
+ findCommand.get(),
+ explain);
+ if (!status.isOK()) {
+ return status;
+ }
+
+ return std::move(findCommand);
+}
+
StatusWith<std::unique_ptr<FindCommandRequest>> fromLegacyQuery(NamespaceStringOrUUID nssOrUuid,
const BSONObj& queryObj,
const BSONObj& proj,
int ntoskip,
int ntoreturn,
- int queryOptions) {
+ int queryOptions,
+ bool* explain) {
auto findCommand = std::make_unique<FindCommandRequest>(std::move(nssOrUuid));
Status status = initFindCommandRequest(
- ntoskip, ntoreturn, queryOptions, queryObj, proj, true, findCommand.get());
+ ntoskip, ntoreturn, queryOptions, queryObj, proj, true, findCommand.get(), explain);
if (!status.isOK()) {
return status;
}
diff --git a/src/mongo/db/query/query_request_helper.h b/src/mongo/db/query/query_request_helper.h
index 14409c62e39..2617c9231d4 100644
--- a/src/mongo/db/query/query_request_helper.h
+++ b/src/mongo/db/query/query_request_helper.h
@@ -148,6 +148,13 @@ void validateCursorResponse(const BSONObj& outputAsBson);
//
/**
+ * Parse the provided QueryMessage and return a heap constructed FindCommandRequest, which
+ * represents it or an error.
+ */
+StatusWith<std::unique_ptr<FindCommandRequest>> fromLegacyQueryMessage(const QueryMessage& qm,
+ bool* explain);
+
+/**
* Parse the provided legacy query object and parameters to construct a FindCommandRequest.
*/
StatusWith<std::unique_ptr<FindCommandRequest>> fromLegacyQuery(NamespaceStringOrUUID nsOrUuid,
@@ -155,7 +162,8 @@ StatusWith<std::unique_ptr<FindCommandRequest>> fromLegacyQuery(NamespaceStringO
const BSONObj& proj,
int ntoskip,
int ntoreturn,
- int queryOptions);
+ int queryOptions,
+ bool* explain);
} // namespace query_request_helper
} // namespace mongo
diff --git a/src/mongo/db/query/query_request_test.cpp b/src/mongo/db/query/query_request_test.cpp
index 2e92a48f4e9..8518c0e4661 100644
--- a/src/mongo/db/query/query_request_test.cpp
+++ b/src/mongo/db/query/query_request_test.cpp
@@ -1562,14 +1562,17 @@ TEST(QueryRequestTest, ParseFromLegacyQuery) {
query: {query: 1},
orderby: {sort: 1},
$hint: {hint: 1},
+ $explain: false,
$min: {x: 'min'},
$max: {x: 'max'}
})");
+ bool explain = false;
unique_ptr<FindCommandRequest> findCommand(assertGet(query_request_helper::fromLegacyQuery(
- nss, queryObj, BSON("proj" << 1), kSkip, kNToReturn, QueryOption_Exhaust)));
+ nss, queryObj, BSON("proj" << 1), kSkip, kNToReturn, QueryOption_Exhaust, &explain)));
ASSERT_EQ(*findCommand->getNamespaceOrUUID().nss(), nss);
+ ASSERT_EQ(explain, false);
ASSERT_BSONOBJ_EQ(findCommand->getFilter(), fromjson("{query: 1}"));
ASSERT_BSONOBJ_EQ(findCommand->getProjection(), fromjson("{proj: 1}"));
ASSERT_BSONOBJ_EQ(findCommand->getSort(), fromjson("{sort: 1}"));
@@ -1594,8 +1597,9 @@ TEST(QueryRequestTest, ParseFromLegacyQueryOplogReplayFlagAllowed) {
// Test that parsing succeeds even if the oplog replay bit is set in the OP_QUERY message. This
// flag may be set by old clients.
auto options = QueryOption_OplogReplay_DEPRECATED;
+ bool explain = false;
unique_ptr<FindCommandRequest> findCommand(assertGet(query_request_helper::fromLegacyQuery(
- nss, queryObj, projectionObj, nToSkip, nToReturn, options)));
+ nss, queryObj, projectionObj, nToSkip, nToReturn, options, &explain)));
// Verify that if we reserialize the find command, the 'oplogReplay' field
// does not appear.
@@ -1615,8 +1619,9 @@ TEST(QueryRequestTest, ParseFromLegacyQueryUnwrapped) {
foo: 1
})");
const NamespaceString nss("test.testns");
+ bool explain = false;
unique_ptr<FindCommandRequest> findCommand(assertGet(query_request_helper::fromLegacyQuery(
- nss, queryObj, BSONObj(), 0, 0, QueryOption_Exhaust)));
+ nss, queryObj, BSONObj(), 0, 0, QueryOption_Exhaust, &explain)));
ASSERT_EQ(*findCommand->getNamespaceOrUUID().nss(), nss);
ASSERT_BSONOBJ_EQ(findCommand->getFilter(), fromjson("{foo: 1}"));
@@ -1642,24 +1647,15 @@ TEST(QueryRequestTest, ParseFromLegacyQueryTooNegativeNToReturn) {
})");
const NamespaceString nss("test.testns");
- ASSERT_NOT_OK(
- query_request_helper::fromLegacyQuery(
- nss, queryObj, BSONObj(), 0, std::numeric_limits<int>::min(), QueryOption_Exhaust)
- .getStatus());
-}
-
-TEST(QueryRequestTest, ParseFromLegacyQueryExplainError) {
- BSONObj queryObj = fromjson(R"({
- query: {query: 1},
- $explain: false
- })");
-
- const NamespaceString nss("test.testns");
- ASSERT_EQUALS(
- query_request_helper::fromLegacyQuery(nss, queryObj, BSONObj(), 0, -1, QueryOption_Exhaust)
- .getStatus()
- .code(),
- static_cast<ErrorCodes::Error>(5856600));
+ bool explain = false;
+ ASSERT_NOT_OK(query_request_helper::fromLegacyQuery(nss,
+ queryObj,
+ BSONObj(),
+ 0,
+ std::numeric_limits<int>::min(),
+ QueryOption_Exhaust,
+ &explain)
+ .getStatus());
}
class QueryRequestTest : public ServiceContextTest {};
diff --git a/src/mongo/s/commands/cluster_explain_cmd.cpp b/src/mongo/s/commands/cluster_explain_cmd.cpp
index 9ee65bdbc49..287ec9902d7 100644
--- a/src/mongo/s/commands/cluster_explain_cmd.cpp
+++ b/src/mongo/s/commands/cluster_explain_cmd.cpp
@@ -40,6 +40,15 @@ namespace {
/**
* Implements the explain command on mongos.
+ *
+ * "Old-style" explains (i.e. queries which have the $explain flag set), do not run
+ * through this path. Such explains will be supported for backwards compatibility,
+ * and must succeed in multiversion clusters.
+ *
+ * "New-style" explains use the explain command. When the explain command is routed
+ * through mongos, it is forwarded to all relevant shards. If *any* shard does not
+ * support a new-style explain, then the entire explain will fail (i.e. new-style
+ * explains cannot be used in multiversion clusters).
*/
class ClusterExplainCmd final : public Command {
diff --git a/src/mongo/shell/explain_query.js b/src/mongo/shell/explain_query.js
index 453751d88de..b4935fd07d4 100644
--- a/src/mongo/shell/explain_query.js
+++ b/src/mongo/shell/explain_query.js
@@ -9,6 +9,37 @@ var DBExplainQuery = (function() {
//
/**
+ * 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.
@@ -20,6 +51,28 @@ var DBExplainQuery = (function() {
};
}
+ /**
+ * Where possible, the explain query will be sent to the server as an explain command.
+ * However, if one of the nodes we are talking to (either a standalone or a shard in
+ * a sharded cluster) is of a version that doesn't have the explain command, we will
+ * use this function to fall back on the $explain query option.
+ */
+ function explainWithLegacyQueryOption(explainQuery) {
+ // 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 = explainQuery._query.clone();
+ clone._addSpecial("$explain", true);
+ var result = clone.next();
+
+ // Remove some fields from the explain if verbosity is
+ // just "queryPlanner".
+ if ("queryPlanner" === explainQuery._verbosity) {
+ removeVerboseFields(result);
+ }
+
+ return Explainable.throwOrReturn(result);
+ }
+
function constructor(query, verbosity) {
//
// Private vars.
@@ -83,35 +136,47 @@ var DBExplainQuery = (function() {
// Explain always gets pretty printed.
this._query._prettyShell = true;
- // 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);
+ 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 {
+ var canAttachReadPref = false;
+ innerCmd = this._query._convertToCommand(canAttachReadPref);
+ }
+
+ var explainCmd = {explain: innerCmd};
+ explainCmd["verbosity"] = this._verbosity;
+ // If "maxTimeMS" is set on innerCmd, it needs to be propagated to the top-level
+ // of explainCmd so that it has the intended effect.
+ if (innerCmd.hasOwnProperty("maxTimeMS")) {
+ explainCmd.maxTimeMS = innerCmd.maxTimeMS;
+ }
+
+ var explainDb = this._query._db;
+
+ if ("$readPreference" in this._query._query) {
+ var prefObj = this._query._query.$readPreference;
+ explainCmd = explainDb._attachReadPreferenceToCommand(explainCmd, prefObj);
+ }
+
+ var explainResult =
+ explainDb.runReadCommand(explainCmd, null, this._query._options);
+
+ if (!explainResult.ok && explainResult.code === ErrorCodes.CommandNotFound) {
+ // One of the shards doesn't have the explain command available. Retry using
+ // the legacy $explain format, which should be supported by all shards.
+ return explainWithLegacyQueryOption(this);
+ }
+
+ return Explainable.throwOrReturn(explainResult);
} else {
- var canAttachReadPref = false;
- innerCmd = this._query._convertToCommand(canAttachReadPref);
+ return explainWithLegacyQueryOption(this);
}
-
- var explainCmd = {explain: innerCmd};
- explainCmd["verbosity"] = this._verbosity;
- // If "maxTimeMS" is set on innerCmd, it needs to be propagated to the top-level
- // of explainCmd so that it has the intended effect.
- if (innerCmd.hasOwnProperty("maxTimeMS")) {
- explainCmd.maxTimeMS = innerCmd.maxTimeMS;
- }
-
- var explainDb = this._query._db;
-
- if ("$readPreference" in this._query._query) {
- var prefObj = this._query._query.$readPreference;
- explainCmd = explainDb._attachReadPreferenceToCommand(explainCmd, prefObj);
- }
-
- var explainResult = explainDb.runReadCommand(explainCmd, null, this._query._options);
-
- return Explainable.throwOrReturn(explainResult);
};
this.next = function() {
diff --git a/src/mongo/shell/mongo.js b/src/mongo/shell/mongo.js
index 7146fd8670a..cf05337eead 100644
--- a/src/mongo/shell/mongo.js
+++ b/src/mongo/shell/mongo.js
@@ -418,6 +418,11 @@ connect = function(url, user, pass, apiParameters) {
return db;
};
+Mongo.prototype.hasExplainCommand = function() {
+ var hasExplain = (this.getMinWireVersion() <= 3 && 3 <= this.getMaxWireVersion());
+ return hasExplain;
+};
+
//
// Write Concern can be set at the connection level, and is used for all write operations unless
// overridden at the collection level.
diff --git a/src/mongo/transport/service_state_machine.cpp b/src/mongo/transport/service_state_machine.cpp
index bd96331bd50..f1deac76b13 100644
--- a/src/mongo/transport/service_state_machine.cpp
+++ b/src/mongo/transport/service_state_machine.cpp
@@ -68,12 +68,61 @@ namespace {
MONGO_FAIL_POINT_DEFINE(doNotSetMoreToCome);
MONGO_FAIL_POINT_DEFINE(beforeCompressingExhaustResponse);
/**
+ * Creates and returns a legacy exhaust message, if exhaust is allowed. The returned message is to
+ * be used as the subsequent 'synthetic' exhaust request. Returns an empty message if exhaust is not
+ * allowed. Any messages that do not have an opcode of OP_MSG are considered legacy.
+ */
+Message makeLegacyExhaustMessage(Message* m, const DbResponse& dbresponse) {
+ // OP_QUERY responses are always of type OP_REPLY.
+ invariant(dbresponse.response.operation() == opReply);
+
+ if (!dbresponse.shouldRunAgainForExhaust) {
+ return Message();
+ }
+
+ // Legacy find operations via the OP_QUERY/OP_GET_MORE network protocol never provide the next
+ // invocation for exhaust.
+ invariant(!dbresponse.nextInvocation);
+
+ DbMessage dbmsg(*m);
+ invariant(dbmsg.messageShouldHaveNs());
+ const char* ns = dbmsg.getns();
+
+ MsgData::View header = dbresponse.response.header();
+ QueryResult::View qr = header.view2ptr();
+ long long cursorid = qr.getCursorId();
+
+ if (cursorid == 0) {
+ return Message();
+ }
+
+ // Generate a message that will act as the subsequent 'synthetic' exhaust request.
+ BufBuilder b(512);
+ b.appendNum(static_cast<int>(0)); // size set later in setLen()
+ b.appendNum(header.getId()); // message id
+ b.appendNum(header.getResponseToMsgId()); // in response to
+ b.appendNum(static_cast<int>(dbGetMore)); // opCode is OP_GET_MORE
+ b.appendNum(static_cast<int>(0)); // Must be ZERO (reserved)
+ b.appendStr(StringData(ns)); // Namespace
+ b.appendNum(static_cast<int>(0)); // ntoreturn
+ b.appendNum(cursorid); // cursor id from the OP_REPLY
+
+ MsgData::View(b.buf()).setLen(b.len());
+
+ return Message(b.release());
+}
+
+/**
* Given a request and its already generated response, checks for exhaust flags. If exhaust is
* allowed, produces the subsequent request message, and modifies the response message to indicate
* it is part of an exhaust stream. Returns the subsequent request message, which is known as a
* 'synthetic' exhaust request. Returns an empty message if exhaust is not allowed.
*/
Message makeExhaustMessage(Message requestMsg, DbResponse* dbresponse) {
+ if (requestMsg.operation() == dbQuery) {
+ return makeLegacyExhaustMessage(&requestMsg, *dbresponse);
+ }
+
if (!OpMsgRequest::isFlagSet(requestMsg, OpMsg::kExhaustSupported)) {
return Message();
}