summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jstests/noPassthroughWithMongod/now_variable.js46
-rw-r--r--jstests/sharding/now_variable_replset.js61
-rw-r--r--jstests/sharding/now_variable_sharding.js98
-rw-r--r--src/mongo/db/commands/find_cmd.cpp30
-rw-r--r--src/mongo/db/ops/delete_request.h4
-rw-r--r--src/mongo/db/ops/parsed_delete.cpp20
-rw-r--r--src/mongo/db/ops/parsed_delete.h3
-rw-r--r--src/mongo/db/ops/parsed_update.cpp8
-rw-r--r--src/mongo/db/ops/update_request.h4
-rw-r--r--src/mongo/db/pipeline/variables.cpp2
-rw-r--r--src/mongo/db/query/SConscript3
-rw-r--r--src/mongo/db/query/canonical_query.cpp2
-rw-r--r--src/mongo/db/query/find_and_modify_request.h4
-rw-r--r--src/mongo/db/query/query_request.cpp20
-rw-r--r--src/mongo/db/query/query_request.h12
-rw-r--r--src/mongo/db/query/query_request_test.cpp127
-rw-r--r--src/mongo/s/commands/cluster_find_cmd.cpp25
-rw-r--r--src/mongo/s/write_ops/batched_command_request.h4
18 files changed, 352 insertions, 121 deletions
diff --git a/jstests/noPassthroughWithMongod/now_variable.js b/jstests/noPassthroughWithMongod/now_variable.js
index 327ae94bead..b64d029b436 100644
--- a/jstests/noPassthroughWithMongod/now_variable.js
+++ b/jstests/noPassthroughWithMongod/now_variable.js
@@ -5,16 +5,28 @@
"use strict";
const coll = db[jsTest.name()];
+ const otherColl = db[coll.getName() + "_other"];
+ otherColl.drop();
coll.drop();
db["viewWithNow"].drop();
db["viewWithClusterTime"].drop();
+ // Insert simple documents into the main test collection. Aggregation and view pipelines will
+ // augment these docs with time-based fields.
const numdocs = 1000;
- const bulk = coll.initializeUnorderedBulkOp();
+ let bulk = coll.initializeUnorderedBulkOp();
for (let i = 0; i < numdocs; ++i) {
bulk.insert({_id: i});
}
- assert.writeOK(bulk.execute());
+ assert.commandWorked(bulk.execute());
+
+ // Insert into another collection with pre-made fields for testing the find() command.
+ bulk = otherColl.initializeUnorderedBulkOp();
+ const timeFieldValue = new Date();
+ for (let i = 0; i < numdocs; ++i) {
+ bulk.insert({_id: i, timeField: timeFieldValue, clusterTimeField: new Timestamp(0, 1)});
+ }
+ assert.commandWorked(bulk.execute());
assert.commandWorked(
db.createView("viewWithNow", coll.getName(), [{$addFields: {timeField: "$$NOW"}}]));
@@ -45,15 +57,26 @@
function runTestsExpectFailure(query) {
const results = query();
- // Expect to see "Buildin variable '$$CLUSTER_TIME' is not available" error.
+ // Expect to see "Builtin variable '$$CLUSTER_TIME' is not available" error.
assert.commandFailedWithCode(results, 51144);
}
- function baseCollectionNow() {
+ function baseCollectionNowFind() {
+ return otherColl.find({$expr: {$lte: ["$timeField", "$$NOW"]}});
+ }
+
+ function baseCollectionClusterTimeFind() {
+ return db.runCommand({
+ find: otherColl.getName(),
+ filter: {$expr: {$lt: ["$clusterTimeField", "$$CLUSTER_TIME"]}}
+ });
+ }
+
+ function baseCollectionNowAgg() {
return coll.aggregate([{$addFields: {timeField: "$$NOW"}}]);
}
- function baseCollectionClusterTime() {
+ function baseCollectionClusterTimeAgg() {
return db.runCommand({
aggregate: coll.getName(),
pipeline: [{$addFields: {timeField: "$$CLUSTER_TIME"}}],
@@ -80,12 +103,21 @@
});
}
- runTests(baseCollectionNow);
+ // Test that $$NOW is usable in all contexts.
+ runTests(baseCollectionNowFind);
+ runTests(baseCollectionNowAgg);
runTests(fromViewWithNow);
runTests(withExprNow);
+ // Test that $$NOW can be used in explain for both find and aggregate.
+ assert.commandWorked(coll.explain().find({$expr: {$lte: ["$timeField", "$$NOW"]}}).finish());
+ assert.commandWorked(
+ viewWithNow.explain().find({$expr: {$eq: ["$timeField", "$$NOW"]}}).finish());
+ assert.commandWorked(coll.explain().aggregate([{$addFields: {timeField: "$$NOW"}}]));
+
// $$CLUSTER_TIME is not available on a standalone mongod.
- runTestsExpectFailure(baseCollectionClusterTime);
+ runTestsExpectFailure(baseCollectionClusterTimeFind);
+ runTestsExpectFailure(baseCollectionClusterTimeAgg);
runTestsExpectFailure(fromViewWithClusterTime);
runTestsExpectFailure(withExprClusterTime);
}());
diff --git a/jstests/sharding/now_variable_replset.js b/jstests/sharding/now_variable_replset.js
index 9cd185b748f..ad5104a0695 100644
--- a/jstests/sharding/now_variable_replset.js
+++ b/jstests/sharding/now_variable_replset.js
@@ -12,16 +12,28 @@
var db = replTest.getPrimary().getDB("test");
const coll = db[jsTest.name()];
+ const otherColl = db[coll.getName() + "_other"];
+ otherColl.drop();
coll.drop();
db["viewWithNow"].drop();
db["viewWithClusterTime"].drop();
+ // Insert simple documents into the main test collection. Aggregation and view pipelines will
+ // augment these docs with time-based fields.
const numdocs = 1000;
- const bulk = coll.initializeUnorderedBulkOp();
+ let bulk = coll.initializeUnorderedBulkOp();
for (let i = 0; i < numdocs; ++i) {
bulk.insert({_id: i});
}
- assert.writeOK(bulk.execute());
+ assert.commandWorked(bulk.execute());
+
+ // Insert into another collection with pre-made fields for testing the find() command.
+ bulk = otherColl.initializeUnorderedBulkOp();
+ const timeFieldValue = new Date();
+ for (let i = 0; i < numdocs; ++i) {
+ bulk.insert({_id: i, timeField: timeFieldValue, clusterTimeField: new Timestamp(0, 1)});
+ }
+ assert.commandWorked(bulk.execute());
assert.commandWorked(
db.createView("viewWithNow", coll.getName(), [{$addFields: {timeField: "$$NOW"}}]));
@@ -31,8 +43,12 @@
"viewWithClusterTime", coll.getName(), [{$addFields: {timeField: "$$CLUSTER_TIME"}}]));
const viewWithClusterTime = db["viewWithClusterTime"];
+ function toResultsArray(queryRes) {
+ return Array.isArray(queryRes) ? queryRes : queryRes.toArray();
+ }
+
function runTests(query) {
- const results = query().toArray();
+ const results = toResultsArray(query());
assert.eq(results.length, numdocs);
// Make sure the values are the same for all documents
@@ -43,18 +59,32 @@
// Sleep for a while and then rerun.
sleep(3000);
- const resultsLater = query().toArray();
+ const resultsLater = toResultsArray(query());
assert.eq(resultsLater.length, numdocs);
// Later results should be later in time.
assert.lte(results[0].timeField, resultsLater[0].timeField);
}
- function baseCollectionNow() {
+ function baseCollectionNowFind() {
+ return otherColl.find({$expr: {$lte: ["$timeField", "$$NOW"]}});
+ }
+
+ function baseCollectionClusterTimeFind() {
+ // The test validator examines 'timeField', so we copy clusterTimeField into timeField here.
+ const results =
+ otherColl.find({$expr: {$lt: ["$clusterTimeField", "$$CLUSTER_TIME"]}}).toArray();
+ results.forEach((val, idx) => {
+ results[idx].timeField = results[idx].clusterTimeField;
+ });
+ return results;
+ }
+
+ function baseCollectionNowAgg() {
return coll.aggregate([{$addFields: {timeField: "$$NOW"}}]);
}
- function baseCollectionClusterTime() {
+ function baseCollectionClusterTimeAgg() {
return coll.aggregate([{$addFields: {timeField: "$$CLUSTER_TIME"}}]);
}
@@ -75,14 +105,29 @@
}
// $$NOW
- runTests(baseCollectionNow);
+ runTests(baseCollectionNowFind);
+ runTests(baseCollectionNowAgg);
runTests(fromViewWithNow);
runTests(withExprNow);
+ // Test that $$NOW can be used in explain for both find and aggregate.
+ assert.commandWorked(coll.explain().find({$expr: {$lte: ["$timeField", "$$NOW"]}}).finish());
+ assert.commandWorked(
+ viewWithNow.explain().find({$expr: {$eq: ["$timeField", "$$NOW"]}}).finish());
+ assert.commandWorked(coll.explain().aggregate([{$addFields: {timeField: "$$NOW"}}]));
+
// $$CLUSTER_TIME
- runTests(baseCollectionClusterTime);
+ runTests(baseCollectionClusterTimeFind);
+ runTests(baseCollectionClusterTimeAgg);
runTests(fromViewWithClusterTime);
runTests(withExprClusterTime);
+ // Test that $$CLUSTER_TIME can be used in explain for both find and aggregate.
+ assert.commandWorked(
+ coll.explain().find({$expr: {$lte: ["$timeField", "$$CLUSTER_TIME"]}}).finish());
+ assert.commandWorked(
+ viewWithNow.explain().find({$expr: {$eq: ["$timeField", "$$CLUSTER_TIME"]}}).finish());
+ assert.commandWorked(coll.explain().aggregate([{$addFields: {timeField: "$$CLUSTER_TIME"}}]));
+
replTest.stopSet();
}());
diff --git a/jstests/sharding/now_variable_sharding.js b/jstests/sharding/now_variable_sharding.js
index 18ba0e3e684..49e2833b46f 100644
--- a/jstests/sharding/now_variable_sharding.js
+++ b/jstests/sharding/now_variable_sharding.js
@@ -16,30 +16,43 @@
const numdocs = 1000;
const coll = db[jsTest.name()];
+ const otherColl = db[coll.getName() + "_other"];
+
+ for (let testColl of[coll, otherColl]) {
+ testColl.createIndex({_id: 1}, {unique: true});
+
+ st.adminCommand({shardcollection: testColl.getFullName(), key: {_id: 1}});
+ st.adminCommand({split: testColl.getFullName(), middle: {_id: numdocs / 2}});
+
+ st.adminCommand({
+ moveChunk: testColl.getFullName(),
+ find: {_id: 0},
+ to: st.shard1.shardName,
+ _waitForDelete: true
+ });
+ st.adminCommand({
+ moveChunk: testColl.getFullName(),
+ find: {_id: numdocs / 2},
+ to: st.shard0.shardName,
+ _waitForDelete: true
+ });
+ }
- coll.createIndex({_id: 1}, {unique: true});
-
- st.adminCommand({shardcollection: coll.getFullName(), key: {_id: 1}});
- st.adminCommand({split: coll.getFullName(), middle: {_id: numdocs / 2}});
-
- st.adminCommand({
- moveChunk: coll.getFullName(),
- find: {_id: 0},
- to: st.shard1.shardName,
- _waitForDelete: true
- });
- st.adminCommand({
- moveChunk: coll.getFullName(),
- find: {_id: numdocs / 2},
- to: st.shard0.shardName,
- _waitForDelete: true
- });
-
- const bulk = coll.initializeUnorderedBulkOp();
+ // Insert simple documents into the main test collection. Aggregation and view pipelines will
+ // augment these docs with time-based fields.
+ let bulk = coll.initializeUnorderedBulkOp();
for (let i = 0; i < numdocs; ++i) {
bulk.insert({_id: i});
}
- assert.writeOK(bulk.execute());
+ assert.commandWorked(bulk.execute());
+
+ // Insert into another collection with pre-made fields for testing the find() command.
+ bulk = otherColl.initializeUnorderedBulkOp();
+ const timeFieldValue = new Date();
+ for (let i = 0; i < numdocs; ++i) {
+ bulk.insert({_id: i, timeField: timeFieldValue, clusterTimeField: new Timestamp(0, 1)});
+ }
+ assert.commandWorked(bulk.execute());
assert.commandWorked(
db.createView("viewWithNow", coll.getName(), [{$addFields: {timeField: "$$NOW"}}]));
@@ -49,8 +62,12 @@
"viewWithClusterTime", coll.getName(), [{$addFields: {timeField: "$$CLUSTER_TIME"}}]));
const viewWithClusterTime = db["viewWithClusterTime"];
+ function toResultsArray(queryRes) {
+ return Array.isArray(queryRes) ? queryRes : queryRes.toArray();
+ }
+
function runTests(query) {
- const results = query().toArray();
+ const results = toResultsArray(query());
assert.eq(results.length, numdocs);
// Make sure the values are the same for all documents
@@ -61,18 +78,32 @@
// Sleep for a while and then rerun.
sleep(3000);
- const resultsLater = query().toArray();
+ const resultsLater = toResultsArray(query());
assert.eq(resultsLater.length, numdocs);
// Later results should be later in time.
assert.lte(results[0].timeField, resultsLater[0].timeField);
}
- function baseCollectionNow() {
+ function baseCollectionNowFind() {
+ return otherColl.find({$expr: {$lte: ["$timeField", "$$NOW"]}});
+ }
+
+ function baseCollectionClusterTimeFind() {
+ // The test validator examines 'timeField', so we copy clusterTimeField into timeField here.
+ const results =
+ otherColl.find({$expr: {$lt: ["$clusterTimeField", "$$CLUSTER_TIME"]}}).toArray();
+ results.forEach((val, idx) => {
+ results[idx].timeField = results[idx].clusterTimeField;
+ });
+ return results;
+ }
+
+ function baseCollectionNowAgg() {
return coll.aggregate([{$addFields: {timeField: "$$NOW"}}]);
}
- function baseCollectionClusterTime() {
+ function baseCollectionClusterTimeAgg() {
return coll.aggregate([{$addFields: {timeField: "$$CLUSTER_TIME"}}]);
}
@@ -93,14 +124,29 @@
}
// $$NOW
- runTests(baseCollectionNow);
+ runTests(baseCollectionNowFind);
+ runTests(baseCollectionNowAgg);
runTests(fromViewWithNow);
runTests(withExprNow);
+ // Test that $$NOW can be used in explain for both find and aggregate.
+ assert.commandWorked(coll.explain().find({$expr: {$lte: ["$timeField", "$$NOW"]}}).finish());
+ assert.commandWorked(
+ viewWithNow.explain().find({$expr: {$eq: ["$timeField", "$$NOW"]}}).finish());
+ assert.commandWorked(coll.explain().aggregate([{$addFields: {timeField: "$$NOW"}}]));
+
// $$CLUSTER_TIME
- runTests(baseCollectionClusterTime);
+ runTests(baseCollectionClusterTimeFind);
+ runTests(baseCollectionClusterTimeAgg);
runTests(fromViewWithClusterTime);
runTests(withExprClusterTime);
+ // Test that $$CLUSTER_TIME can be used in explain for both find and aggregate.
+ assert.commandWorked(
+ coll.explain().find({$expr: {$lte: ["$timeField", "$$CLUSTER_TIME"]}}).finish());
+ assert.commandWorked(
+ viewWithNow.explain().find({$expr: {$eq: ["$timeField", "$$CLUSTER_TIME"]}}).finish());
+ assert.commandWorked(coll.explain().aggregate([{$addFields: {timeField: "$$CLUSTER_TIME"}}]));
+
st.stop();
}());
diff --git a/src/mongo/db/commands/find_cmd.cpp b/src/mongo/db/commands/find_cmd.cpp
index 67f73d6e6ee..4d79ab118cd 100644
--- a/src/mongo/db/commands/find_cmd.cpp
+++ b/src/mongo/db/commands/find_cmd.cpp
@@ -42,6 +42,7 @@
#include "mongo/db/db_raii.h"
#include "mongo/db/exec/working_set_common.h"
#include "mongo/db/matcher/extensions_callback_real.h"
+#include "mongo/db/pipeline/variables.h"
#include "mongo/db/query/cursor_response.h"
#include "mongo/db/query/explain.h"
#include "mongo/db/query/find.h"
@@ -62,6 +63,20 @@ namespace {
const auto kTermField = "term"_sd;
+// Parses the command object to a QueryRequest. If the client request did not specify any runtime
+// constants, make them available to the query here.
+std::unique_ptr<QueryRequest> parseCmdObjectToQueryRequest(OperationContext* opCtx,
+ NamespaceString nss,
+ BSONObj cmdObj,
+ bool isExplain) {
+ auto qr = uassertStatusOK(
+ QueryRequest::makeFromFindCommand(std::move(nss), std::move(cmdObj), isExplain));
+ if (!qr->getRuntimeConstants()) {
+ qr->setRuntimeConstants(Variables::generateRuntimeConstants(opCtx));
+ }
+ return qr;
+}
+
/**
* A command for running .find() queries.
*/
@@ -168,8 +183,7 @@ public:
// Parse the command BSON to a QueryRequest.
const bool isExplain = true;
- auto qr =
- uassertStatusOK(QueryRequest::makeFromFindCommand(nss, _request.body, isExplain));
+ auto qr = parseCmdObjectToQueryRequest(opCtx, nss, _request.body, isExplain);
// Finish the parsing step by using the QueryRequest to create a CanonicalQuery.
const ExtensionsCallbackReal extensionsCallback(opCtx, &nss);
@@ -239,13 +253,13 @@ public:
ServerReadConcernMetrics::get(opCtx)->recordReadConcern(
repl::ReadConcernArgs::get(opCtx));
- // Parse the command BSON to a QueryRequest.
+ // Parse the command BSON to a QueryRequest. Pass in the parsedNss in case _request.body
+ // does not have a UUID.
+ auto parsedNss =
+ NamespaceString{CommandHelpers::parseNsFromCommand(_dbName, _request.body)};
const bool isExplain = false;
- // Pass parseNs to makeFromFindCommand in case _request.body does not have a UUID.
- auto qr = uassertStatusOK(QueryRequest::makeFromFindCommand(
- NamespaceString(CommandHelpers::parseNsFromCommand(_dbName, _request.body)),
- _request.body,
- isExplain));
+ auto qr =
+ parseCmdObjectToQueryRequest(opCtx, std::move(parsedNss), _request.body, isExplain);
// Only allow speculative majority for internal commands that specify the correct flag.
uassert(ErrorCodes::ReadConcernMajorityNotEnabled,
diff --git a/src/mongo/db/ops/delete_request.h b/src/mongo/db/ops/delete_request.h
index 937297b95ae..a49e6eb37b9 100644
--- a/src/mongo/db/ops/delete_request.h
+++ b/src/mongo/db/ops/delete_request.h
@@ -61,8 +61,8 @@ public:
void setSort(const BSONObj& sort) {
_sort = sort;
}
- void setRuntimeConstants(const RuntimeConstants& runtimeConstants) {
- _runtimeConstants = runtimeConstants;
+ void setRuntimeConstants(RuntimeConstants runtimeConstants) {
+ _runtimeConstants = std::move(runtimeConstants);
}
void setCollation(const BSONObj& collation) {
_collation = collation;
diff --git a/src/mongo/db/ops/parsed_delete.cpp b/src/mongo/db/ops/parsed_delete.cpp
index 0ce2cbe557a..c0b757fe69e 100644
--- a/src/mongo/db/ops/parsed_delete.cpp
+++ b/src/mongo/db/ops/parsed_delete.cpp
@@ -39,7 +39,6 @@
#include "mongo/db/matcher/extensions_callback_real.h"
#include "mongo/db/ops/delete_request.h"
#include "mongo/db/query/canonical_query.h"
-#include "mongo/db/query/collation/collator_factory_interface.h"
#include "mongo/db/query/get_executor.h"
#include "mongo/db/query/query_planner_common.h"
#include "mongo/util/assert_util.h"
@@ -61,17 +60,6 @@ Status ParsedDelete::parseRequest() {
// DeleteStage would not return the deleted document.
invariant(_request->getProj().isEmpty() || _request->shouldReturnDeleted());
- // Parse the delete request's collation, if present. This will subsequently be used to
- // initialize an ExpressionContext for the query.
- if (!_request->getCollation().isEmpty()) {
- auto collator = CollatorFactoryInterface::get(_opCtx->getServiceContext())
- ->makeFromBSON(_request->getCollation());
- if (!collator.isOK()) {
- return collator.getStatus();
- }
- _collator = std::move(collator.getValue());
- }
-
if (CanonicalQuery::isSimpleIdQuery(_request->getQuery())) {
return Status::OK();
}
@@ -102,8 +90,12 @@ Status ParsedDelete::parseQueryToCQ() {
qr->setLimit(1);
}
- auto expCtx =
- make_intrusive<ExpressionContext>(_opCtx, _collator.get(), _request->getRuntimeConstants());
+ // If the delete request has runtime constants attached to it, pass them to the QueryRequest.
+ if (auto& runtimeConstants = _request->getRuntimeConstants()) {
+ qr->setRuntimeConstants(*runtimeConstants);
+ }
+
+ const boost::intrusive_ptr<ExpressionContext> expCtx;
auto statusWithCQ =
CanonicalQuery::canonicalize(_opCtx,
std::move(qr),
diff --git a/src/mongo/db/ops/parsed_delete.h b/src/mongo/db/ops/parsed_delete.h
index 724c4a48656..73f4bef19e4 100644
--- a/src/mongo/db/ops/parsed_delete.h
+++ b/src/mongo/db/ops/parsed_delete.h
@@ -107,9 +107,6 @@ private:
// Unowned pointer to the request object that this executor will process.
const DeleteRequest* const _request;
- // The collator for the parsed delete's expression context.
- std::unique_ptr<CollatorInterface> _collator;
-
// Parsed query object, or NULL if the query proves to be an id hack query.
std::unique_ptr<CanonicalQuery> _canonicalQuery;
};
diff --git a/src/mongo/db/ops/parsed_update.cpp b/src/mongo/db/ops/parsed_update.cpp
index 79e1d9ed692..67215128ce0 100644
--- a/src/mongo/db/ops/parsed_update.cpp
+++ b/src/mongo/db/ops/parsed_update.cpp
@@ -123,8 +123,12 @@ Status ParsedUpdate::parseQueryToCQ() {
allowedMatcherFeatures &= ~MatchExpressionParser::AllowedFeatures::kExpr;
}
- auto expCtx =
- make_intrusive<ExpressionContext>(_opCtx, _collator.get(), _request->getRuntimeConstants());
+ // If the update request has runtime constants attached to it, pass them to the QueryRequest.
+ if (auto& runtimeConstants = _request->getRuntimeConstants()) {
+ qr->setRuntimeConstants(*runtimeConstants);
+ }
+
+ boost::intrusive_ptr<ExpressionContext> expCtx;
auto statusWithCQ = CanonicalQuery::canonicalize(
_opCtx, std::move(qr), std::move(expCtx), _extensionsCallback, allowedMatcherFeatures);
if (statusWithCQ.isOK()) {
diff --git a/src/mongo/db/ops/update_request.h b/src/mongo/db/ops/update_request.h
index e0f99abf1d4..ee30cc6a827 100644
--- a/src/mongo/db/ops/update_request.h
+++ b/src/mongo/db/ops/update_request.h
@@ -119,8 +119,8 @@ public:
return _updateConstants;
}
- inline void setRuntimeConstants(const RuntimeConstants& runtimeConstants) {
- _runtimeConstants = runtimeConstants;
+ inline void setRuntimeConstants(RuntimeConstants runtimeConstants) {
+ _runtimeConstants = std::move(runtimeConstants);
}
inline const boost::optional<RuntimeConstants>& getRuntimeConstants() const {
diff --git a/src/mongo/db/pipeline/variables.cpp b/src/mongo/db/pipeline/variables.cpp
index 9e7e2c9256e..cf6b81e9605 100644
--- a/src/mongo/db/pipeline/variables.cpp
+++ b/src/mongo/db/pipeline/variables.cpp
@@ -151,7 +151,7 @@ Value Variables::getValue(Id id, const Document& root) const {
}
uasserted(51144,
- str::stream() << "Buildin variable '$$" << getBuiltinVariableName(id)
+ str::stream() << "Builtin variable '$$" << getBuiltinVariableName(id)
<< "' is not available");
MONGO_UNREACHABLE;
default:
diff --git a/src/mongo/db/query/SConscript b/src/mongo/db/query/SConscript
index b520db9b29a..0ae841ab6de 100644
--- a/src/mongo/db/query/SConscript
+++ b/src/mongo/db/query/SConscript
@@ -209,7 +209,8 @@ env.Library(
LIBDEPS=[
"$BUILD_DIR/mongo/base",
"$BUILD_DIR/mongo/db/repl/read_concern_args",
- "$BUILD_DIR/mongo/db/catalog/collection_catalog"
+ "$BUILD_DIR/mongo/db/catalog/collection_catalog",
+ "$BUILD_DIR/mongo/db/pipeline/runtime_constants_idl"
],
)
diff --git a/src/mongo/db/query/canonical_query.cpp b/src/mongo/db/query/canonical_query.cpp
index 0be2f524798..ff77f314fa2 100644
--- a/src/mongo/db/query/canonical_query.cpp
+++ b/src/mongo/db/query/canonical_query.cpp
@@ -151,7 +151,7 @@ StatusWith<std::unique_ptr<CanonicalQuery>> CanonicalQuery::canonicalize(
// Make MatchExpression.
boost::intrusive_ptr<ExpressionContext> newExpCtx;
if (!expCtx.get()) {
- newExpCtx.reset(new ExpressionContext(opCtx, collator.get()));
+ newExpCtx.reset(new ExpressionContext(opCtx, collator.get(), qr->getRuntimeConstants()));
} else {
newExpCtx = expCtx;
invariant(CollatorInterface::collatorsMatch(collator.get(), expCtx->getCollator()));
diff --git a/src/mongo/db/query/find_and_modify_request.h b/src/mongo/db/query/find_and_modify_request.h
index 2ee73859d1b..a8b350e691f 100644
--- a/src/mongo/db/query/find_and_modify_request.h
+++ b/src/mongo/db/query/find_and_modify_request.h
@@ -167,8 +167,8 @@ public:
/**
* Sets any constant values which may be required by the query and/or update.
*/
- void setRuntimeConstants(const RuntimeConstants& runtimeConstants) {
- _runtimeConstants = runtimeConstants;
+ void setRuntimeConstants(RuntimeConstants runtimeConstants) {
+ _runtimeConstants = std::move(runtimeConstants);
}
/**
diff --git a/src/mongo/db/query/query_request.cpp b/src/mongo/db/query/query_request.cpp
index a8698404ea8..c43317b584b 100644
--- a/src/mongo/db/query/query_request.cpp
+++ b/src/mongo/db/query/query_request.cpp
@@ -98,6 +98,7 @@ const char kOplogReplayField[] = "oplogReplay";
const char kNoCursorTimeoutField[] = "noCursorTimeout";
const char kAwaitDataField[] = "awaitData";
const char kPartialResultsField[] = "allowPartialResults";
+const char kRuntimeConstantsField[] = "runtimeConstants";
const char kTermField[] = "term";
const char kOptionsField[] = "options";
const char kReadOnceField[] = "readOnce";
@@ -337,6 +338,14 @@ StatusWith<unique_ptr<QueryRequest>> QueryRequest::parseFromFindCommand(unique_p
}
qr->_allowPartialResults = el.boolean();
+ } else if (fieldName == kRuntimeConstantsField) {
+ Status status = checkFieldType(el, Object);
+ if (!status.isOK()) {
+ return status;
+ }
+ qr->_runtimeConstants =
+ RuntimeConstants::parse(IDLParserErrorContext(kRuntimeConstantsField),
+ cmdObj.getObjectField(kRuntimeConstantsField));
} else if (fieldName == kOptionsField) {
// 3.0.x versions of the shell may generate an explain of a find command with an
// 'options' field. We accept this only if the 'options' field is empty so that
@@ -544,6 +553,12 @@ void QueryRequest::asFindCommandInternal(BSONObjBuilder* cmdBuilder) const {
cmdBuilder->append(kPartialResultsField, true);
}
+ if (_runtimeConstants) {
+ BSONObjBuilder rtcBuilder(cmdBuilder->subobjStart(kRuntimeConstantsField));
+ _runtimeConstants->serialize(&rtcBuilder);
+ rtcBuilder.doneFast();
+ }
+
if (_replicationTerm) {
cmdBuilder->append(kTermField, *_replicationTerm);
}
@@ -1116,6 +1131,11 @@ StatusWith<BSONObj> QueryRequest::asAggregationCommand() const {
if (!_unwrappedReadPref.isEmpty()) {
aggregationBuilder.append(QueryRequest::kUnwrappedReadPrefField, _unwrappedReadPref);
}
+ if (_runtimeConstants) {
+ BSONObjBuilder rtcBuilder(aggregationBuilder.subobjStart(kRuntimeConstantsField));
+ _runtimeConstants->serialize(&rtcBuilder);
+ rtcBuilder.doneFast();
+ }
return StatusWith<BSONObj>(aggregationBuilder.obj());
}
} // namespace mongo
diff --git a/src/mongo/db/query/query_request.h b/src/mongo/db/query/query_request.h
index c87d9f951e3..ade62c52ddf 100644
--- a/src/mongo/db/query/query_request.h
+++ b/src/mongo/db/query/query_request.h
@@ -36,6 +36,7 @@
#include "mongo/db/jsobj.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/operation_context.h"
+#include "mongo/db/pipeline/runtime_constants_gen.h"
#include "mongo/db/query/tailable_mode.h"
namespace mongo {
@@ -329,6 +330,14 @@ public:
return _tailableMode;
}
+ void setRuntimeConstants(RuntimeConstants runtimeConstants) {
+ _runtimeConstants = std::move(runtimeConstants);
+ }
+
+ const boost::optional<RuntimeConstants>& getRuntimeConstants() const {
+ return _runtimeConstants;
+ }
+
bool isSlaveOk() const {
return _slaveOk;
}
@@ -514,6 +523,9 @@ private:
bool _showRecordId = false;
bool _hasReadPref = false;
+ // Runtime constants which may be referenced by $expr, if present.
+ boost::optional<RuntimeConstants> _runtimeConstants;
+
// Options that can be specified in the OP_QUERY 'flags' header.
TailableModeEnum _tailableMode = TailableModeEnum::kNormal;
bool _slaveOk = false;
diff --git a/src/mongo/db/query/query_request_test.cpp b/src/mongo/db/query/query_request_test.cpp
index 3af9d96921e..0870bf245da 100644
--- a/src/mongo/db/query/query_request_test.cpp
+++ b/src/mongo/db/query/query_request_test.cpp
@@ -449,19 +449,22 @@ TEST(QueryRequestTest, ParseFromCommandCommentWithValidMinMax) {
}
TEST(QueryRequestTest, ParseFromCommandAllNonOptionFields) {
+ RuntimeConstants rtc{Date_t::now(), Timestamp(1, 1)};
+ BSONObj rtcObj = BSON("runtimeConstants" << rtc.toBSON());
BSONObj cmdObj = fromjson(
- "{find: 'testns',"
- "filter: {a: 1},"
- "sort: {b: 1},"
- "projection: {c: 1},"
- "hint: {d: 1},"
- "readConcern: {e: 1},"
- "$queryOptions: {$readPreference: 'secondary'},"
- "collation: {f: 1},"
- "limit: 3,"
- "skip: 5,"
- "batchSize: 90,"
- "singleBatch: false}");
+ "{find: 'testns',"
+ "filter: {a: 1},"
+ "sort: {b: 1},"
+ "projection: {c: 1},"
+ "hint: {d: 1},"
+ "readConcern: {e: 1},"
+ "$queryOptions: {$readPreference: 'secondary'},"
+ "collation: {f: 1},"
+ "limit: 3,"
+ "skip: 5,"
+ "batchSize: 90,"
+ "singleBatch: false}")
+ .addField(rtcObj["runtimeConstants"]);
const NamespaceString nss("test.testns");
bool isExplain = false;
unique_ptr<QueryRequest> qr(
@@ -486,6 +489,9 @@ TEST(QueryRequestTest, ParseFromCommandAllNonOptionFields) {
ASSERT_EQUALS(3, *qr->getLimit());
ASSERT_EQUALS(5, *qr->getSkip());
ASSERT_EQUALS(90, *qr->getBatchSize());
+ ASSERT(qr->getRuntimeConstants().has_value());
+ ASSERT_EQUALS(qr->getRuntimeConstants()->getLocalNow(), rtc.getLocalNow());
+ ASSERT_EQUALS(qr->getRuntimeConstants()->getClusterTime(), rtc.getClusterTime());
ASSERT(qr->wantMore());
}
@@ -788,6 +794,33 @@ TEST(QueryRequestTest, ParseFromCommandReadOnceWrongType) {
auto result = QueryRequest::makeFromFindCommand(nss, cmdObj, isExplain);
ASSERT_EQ(ErrorCodes::FailedToParse, result.getStatus());
}
+
+TEST(QueryRequestTest, ParseFromCommandRuntimeConstantsWrongType) {
+ BSONObj cmdObj = BSON("find"
+ << "testns"
+ << "runtimeConstants"
+ << "shouldNotBeString");
+ const NamespaceString nss("test.testns");
+ bool isExplain = false;
+ auto result = QueryRequest::makeFromFindCommand(nss, cmdObj, isExplain);
+ ASSERT_EQ(ErrorCodes::FailedToParse, result.getStatus());
+}
+
+TEST(QueryRequestTest, ParseFromCommandRuntimeConstantsSubfieldsWrongType) {
+ BSONObj cmdObj = BSON("find"
+ << "testns"
+ << "runtimeConstants"
+ << BSON("localNow"
+ << "shouldBeDate"
+ << "clusterTime"
+ << "shouldBeTimestamp"));
+ const NamespaceString nss("test.testns");
+ bool isExplain = false;
+ ASSERT_THROWS_CODE(QueryRequest::makeFromFindCommand(nss, cmdObj, isExplain),
+ AssertionException,
+ ErrorCodes::TypeMismatch);
+}
+
//
// Parsing errors where a field has the right type but a bad value.
//
@@ -880,18 +913,21 @@ TEST(QueryRequestTest, ParseFromCommandDefaultBatchSize) {
//
TEST(QueryRequestTest, AsFindCommandAllNonOptionFields) {
+ BSONObj rtcObj =
+ BSON("runtimeConstants" << (RuntimeConstants{Date_t::now(), Timestamp(1, 1)}.toBSON()));
BSONObj cmdObj = fromjson(
- "{find: 'testns',"
- "filter: {a: 1},"
- "projection: {c: 1},"
- "sort: {b: 1},"
- "hint: {d: 1},"
- "readConcern: {e: 1},"
- "collation: {f: 1},"
- "skip: 5,"
- "limit: 3,"
- "batchSize: 90,"
- "singleBatch: true}");
+ "{find: 'testns',"
+ "filter: {a: 1},"
+ "projection: {c: 1},"
+ "sort: {b: 1},"
+ "hint: {d: 1},"
+ "readConcern: {e: 1},"
+ "collation: {f: 1},"
+ "skip: 5,"
+ "limit: 3,"
+ "batchSize: 90,"
+ "singleBatch: true}")
+ .addField(rtcObj["runtimeConstants"]);
const NamespaceString nss("test.testns");
bool isExplain = false;
unique_ptr<QueryRequest> qr(
@@ -900,19 +936,23 @@ TEST(QueryRequestTest, AsFindCommandAllNonOptionFields) {
}
TEST(QueryRequestTest, AsFindCommandWithUuidAllNonOptionFields) {
- BSONObj cmdObj = fromjson(
- // This binary value is UUID("01234567-89ab-cdef-edcb-a98765432101")
- "{find: { \"$binary\" : \"ASNFZ4mrze/ty6mHZUMhAQ==\", \"$type\" : \"04\" },"
- "filter: {a: 1},"
- "projection: {c: 1},"
- "sort: {b: 1},"
- "hint: {d: 1},"
- "readConcern: {e: 1},"
- "collation: {f: 1},"
- "skip: 5,"
- "limit: 3,"
- "batchSize: 90,"
- "singleBatch: true}");
+ BSONObj rtcObj =
+ BSON("runtimeConstants" << (RuntimeConstants{Date_t::now(), Timestamp(1, 1)}.toBSON()));
+ BSONObj cmdObj =
+ fromjson(
+ // This binary value is UUID("01234567-89ab-cdef-edcb-a98765432101")
+ "{find: { \"$binary\" : \"ASNFZ4mrze/ty6mHZUMhAQ==\", \"$type\" : \"04\" },"
+ "filter: {a: 1},"
+ "projection: {c: 1},"
+ "sort: {b: 1},"
+ "hint: {d: 1},"
+ "readConcern: {e: 1},"
+ "collation: {f: 1},"
+ "skip: 5,"
+ "limit: 3,"
+ "batchSize: 90,"
+ "singleBatch: true}")
+ .addField(rtcObj["runtimeConstants"]);
const NamespaceString nss("test.testns");
bool isExplain = false;
unique_ptr<QueryRequest> qr(
@@ -1049,6 +1089,7 @@ TEST(QueryRequestTest, DefaultQueryParametersCorrect) {
ASSERT_EQUALS(false, qr->isTailableAndAwaitData());
ASSERT_EQUALS(false, qr->isExhaust());
ASSERT_EQUALS(false, qr->isAllowPartialResults());
+ ASSERT_EQUALS(false, qr->getRuntimeConstants().has_value());
}
//
@@ -1330,6 +1371,20 @@ TEST(QueryRequestTest, ConvertToAggregationWithAllowSpeculativeMajorityReadFails
ASSERT_EQ(ErrorCodes::InvalidPipelineOperator, aggCmd.getStatus().code());
}
+TEST(QueryRequestTest, ConvertToAggregationWithRuntimeConstantsSucceeds) {
+ RuntimeConstants rtc{Date_t::now(), Timestamp(1, 1)};
+ QueryRequest qr(testns);
+ qr.setRuntimeConstants(rtc);
+ auto agg = qr.asAggregationCommand();
+ ASSERT_OK(agg);
+
+ auto ar = AggregationRequest::parseFromBSON(testns, agg.getValue());
+ ASSERT_OK(ar.getStatus());
+ ASSERT(ar.getValue().getRuntimeConstants().has_value());
+ ASSERT_EQ(ar.getValue().getRuntimeConstants()->getLocalNow(), rtc.getLocalNow());
+ ASSERT_EQ(ar.getValue().getRuntimeConstants()->getClusterTime(), rtc.getClusterTime());
+}
+
TEST(QueryRequestTest, ParseFromLegacyObjMetaOpComment) {
BSONObj queryObj = fromjson(
"{$query: {a: 1},"
diff --git a/src/mongo/s/commands/cluster_find_cmd.cpp b/src/mongo/s/commands/cluster_find_cmd.cpp
index e3c0461931f..dd726a64cc6 100644
--- a/src/mongo/s/commands/cluster_find_cmd.cpp
+++ b/src/mongo/s/commands/cluster_find_cmd.cpp
@@ -55,6 +55,20 @@ using std::vector;
const char kTermField[] = "term";
+// Parses the command object to a QueryRequest, validates that no runtime constants were supplied
+// with the command, and sets the constant runtime values that will be forwarded to each shard.
+std::unique_ptr<QueryRequest> parseCmdObjectToQueryRequest(OperationContext* opCtx,
+ NamespaceString nss,
+ BSONObj cmdObj,
+ bool isExplain) {
+ auto qr = uassertStatusOK(
+ QueryRequest::makeFromFindCommand(std::move(nss), std::move(cmdObj), isExplain));
+ uassert(
+ 51202, "Cannot specify runtime constants option to a mongos", !qr->getRuntimeConstants());
+ qr->setRuntimeConstants(Variables::generateRuntimeConstants(opCtx));
+ return qr;
+}
+
/**
* Implements the find command on mongos.
*/
@@ -122,12 +136,12 @@ public:
ExplainOptions::Verbosity verbosity,
rpc::ReplyBuilderInterface* result) override {
// Parse the command BSON to a QueryRequest.
- bool isExplain = true;
- auto qr =
- uassertStatusOK(QueryRequest::makeFromFindCommand(ns(), _request.body, isExplain));
+ const bool isExplain = true;
+ auto qr = parseCmdObjectToQueryRequest(opCtx, ns(), _request.body, isExplain);
try {
- const auto explainCmd = ClusterExplain::wrapAsExplain(_request.body, verbosity);
+ const auto explainCmd =
+ ClusterExplain::wrapAsExplain(qr->asFindCommand(), verbosity);
long long millisElapsed;
std::vector<AsyncRequestsSender::Response> shardResponses;
@@ -185,8 +199,7 @@ public:
globalOpCounters.gotQuery();
const bool isExplain = false;
- auto qr =
- uassertStatusOK(QueryRequest::makeFromFindCommand(ns(), _request.body, isExplain));
+ auto qr = parseCmdObjectToQueryRequest(opCtx, ns(), _request.body, isExplain);
const boost::intrusive_ptr<ExpressionContext> expCtx;
auto cq = uassertStatusOK(
diff --git a/src/mongo/s/write_ops/batched_command_request.h b/src/mongo/s/write_ops/batched_command_request.h
index 1c454adaecc..b49e893e5aa 100644
--- a/src/mongo/s/write_ops/batched_command_request.h
+++ b/src/mongo/s/write_ops/batched_command_request.h
@@ -115,9 +115,9 @@ public:
return *_shardVersion;
}
- void setRuntimeConstants(const RuntimeConstants& runtimeConstants) {
+ void setRuntimeConstants(RuntimeConstants runtimeConstants) {
invariant(_updateReq);
- _updateReq->setRuntimeConstants(runtimeConstants);
+ _updateReq->setRuntimeConstants(std::move(runtimeConstants));
}
bool hasRuntimeConstants() const {