summaryrefslogtreecommitdiff
path: root/src/mongo/db/query/query_request_helper.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/query/query_request_helper.cpp')
-rw-r--r--src/mongo/db/query/query_request_helper.cpp622
1 files changed, 622 insertions, 0 deletions
diff --git a/src/mongo/db/query/query_request_helper.cpp b/src/mongo/db/query/query_request_helper.cpp
new file mode 100644
index 00000000000..ec12a47c821
--- /dev/null
+++ b/src/mongo/db/query/query_request_helper.cpp
@@ -0,0 +1,622 @@
+/**
+ * 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/db/query/query_request_helper.h"
+
+#include <memory>
+
+#include "mongo/base/status.h"
+#include "mongo/base/status_with.h"
+#include "mongo/bson/simple_bsonobj_comparator.h"
+#include "mongo/db/dbmessage.h"
+
+namespace mongo {
+
+namespace query_request_helper {
+namespace {
+
+/**
+ * Initializes options based on the value of the 'options' bit vector.
+ *
+ * This contains flags such as tailable, exhaust, and noCursorTimeout.
+ */
+void initFromInt(int options, FindCommand* findCommand) {
+ bool tailable = (options & QueryOption_CursorTailable) != 0;
+ bool awaitData = (options & QueryOption_AwaitData) != 0;
+ if (awaitData) {
+ findCommand->setAwaitData(true);
+ }
+ if (tailable) {
+ findCommand->setTailable(true);
+ }
+
+ if ((options & QueryOption_NoCursorTimeout) != 0) {
+ findCommand->setNoCursorTimeout(true);
+ }
+ if ((options & QueryOption_PartialResults) != 0) {
+ findCommand->setAllowPartialResults(true);
+ }
+}
+
+/**
+ * Updates the projection object with a $meta projection for the showRecordId option.
+ */
+void addShowRecordIdMetaProj(FindCommand* findCommand) {
+ if (findCommand->getProjection()["$recordId"]) {
+ // There's already some projection on $recordId. Don't overwrite it.
+ return;
+ }
+
+ BSONObjBuilder projBob;
+ projBob.appendElements(findCommand->getProjection());
+ BSONObj metaRecordId = BSON("$recordId" << BSON("$meta" << query_request_helper::metaRecordId));
+ projBob.append(metaRecordId.firstElement());
+ findCommand->setProjection(projBob.obj());
+}
+
+/**
+ * Add the meta projection to this object if needed.
+ */
+void addMetaProjection(FindCommand* findCommand) {
+ if (findCommand->getShowRecordId()) {
+ addShowRecordIdMetaProj(findCommand);
+ }
+}
+
+Status initFullQuery(const BSONObj& top, FindCommand* findCommand, bool* explain) {
+ BSONObjIterator i(top);
+
+ while (i.more()) {
+ BSONElement e = i.next();
+ StringData name = e.fieldNameStringData();
+
+ if (name == "$orderby" || name == "orderby") {
+ if (Object == e.type()) {
+ findCommand->setSort(e.embeddedObject().getOwned());
+ } else if (Array == e.type()) {
+ findCommand->setSort(e.embeddedObject());
+
+ // TODO: Is this ever used? I don't think so.
+ // Quote:
+ // This is for languages whose "objects" are not well ordered (JSON is well
+ // ordered).
+ // [ { a : ... } , { b : ... } ] -> { a : ..., b : ... }
+ // note: this is slow, but that is ok as order will have very few pieces
+ BSONObjBuilder b;
+ char p[2] = "0";
+
+ while (1) {
+ BSONObj j = findCommand->getSort().getObjectField(p);
+ if (j.isEmpty()) {
+ break;
+ }
+ BSONElement e = j.firstElement();
+ if (e.eoo()) {
+ return Status(ErrorCodes::BadValue, "bad order array");
+ }
+ if (!e.isNumber()) {
+ return Status(ErrorCodes::BadValue, "bad order array [2]");
+ }
+ b.append(e);
+ (*p)++;
+ if (!(*p <= '9')) {
+ return Status(ErrorCodes::BadValue, "too many ordering elements");
+ }
+ }
+
+ findCommand->setSort(b.obj());
+ } else {
+ return Status(ErrorCodes::BadValue, "sort must be object or array");
+ }
+ } else if (name.startsWith("$")) {
+ name = name.substr(1); // chop first char
+ if (name == "explain") {
+ // Won't throw.
+ *explain = e.trueValue();
+ } else if (name == "min") {
+ if (!e.isABSONObj()) {
+ return Status(ErrorCodes::BadValue, "$min must be a BSONObj");
+ }
+ findCommand->setMin(e.embeddedObject().getOwned());
+ } else if (name == "max") {
+ if (!e.isABSONObj()) {
+ return Status(ErrorCodes::BadValue, "$max must be a BSONObj");
+ }
+ findCommand->setMax(e.embeddedObject().getOwned());
+ } else if (name == "hint") {
+ if (e.isABSONObj()) {
+ findCommand->setHint(e.embeddedObject().getOwned());
+ } else if (String == e.type()) {
+ findCommand->setHint(e.wrap());
+ } else {
+ return Status(ErrorCodes::BadValue,
+ "$hint must be either a string or nested object");
+ }
+ } else if (name == "returnKey") {
+ // Won't throw.
+ if (e.trueValue()) {
+ findCommand->setReturnKey(true);
+ }
+ } else if (name == "showDiskLoc") {
+ // Won't throw.
+ if (e.trueValue()) {
+ findCommand->setShowRecordId(true);
+ addShowRecordIdMetaProj(findCommand);
+ }
+ } else if (name == "maxTimeMS") {
+ StatusWith<int> maxTimeMS = parseMaxTimeMS(e);
+ if (!maxTimeMS.isOK()) {
+ return maxTimeMS.getStatus();
+ }
+ findCommand->setMaxTimeMS(maxTimeMS.getValue());
+ }
+ }
+ }
+
+ return Status::OK();
+}
+
+Status initFindCommand(int ntoskip,
+ int ntoreturn,
+ int queryOptions,
+ const BSONObj& queryObj,
+ const BSONObj& proj,
+ bool fromQueryMessage,
+ FindCommand* findCommand,
+ bool* explain) {
+ if (!proj.isEmpty()) {
+ findCommand->setProjection(proj.getOwned());
+ }
+ if (ntoskip) {
+ findCommand->setSkip(ntoskip);
+ }
+
+ if (ntoreturn) {
+ if (ntoreturn < 0) {
+ if (ntoreturn == std::numeric_limits<int>::min()) {
+ // ntoreturn is negative but can't be negated.
+ return Status(ErrorCodes::BadValue, "bad ntoreturn value in query");
+ }
+ findCommand->setNtoreturn(-ntoreturn);
+ findCommand->setSingleBatch(true);
+ } else {
+ findCommand->setNtoreturn(ntoreturn);
+ }
+ }
+
+ // An ntoreturn of 1 is special because it also means to return at most one batch.
+ if (findCommand->getNtoreturn().value_or(0) == 1) {
+ findCommand->setSingleBatch(true);
+ }
+
+ // Initialize flags passed as 'queryOptions' bit vector.
+ initFromInt(queryOptions, findCommand);
+
+ if (fromQueryMessage) {
+ BSONElement queryField = queryObj["query"];
+ if (!queryField.isABSONObj()) {
+ queryField = queryObj["$query"];
+ }
+ if (queryField.isABSONObj()) {
+ findCommand->setFilter(queryField.embeddedObject().getOwned());
+ Status status = initFullQuery(queryObj, findCommand, explain);
+ if (!status.isOK()) {
+ return status;
+ }
+ } else {
+ findCommand->setFilter(queryObj.getOwned());
+ }
+ // It's not possible to specify readConcern in a legacy query message, so initialize it to
+ // an empty readConcern object, ie. equivalent to `readConcern: {}`. This ensures that
+ // mongos passes this empty readConcern to shards.
+ findCommand->setReadConcern(BSONObj());
+ } else {
+ // This is the debugging code path.
+ findCommand->setFilter(queryObj.getOwned());
+ }
+
+ return validateFindCommand(*findCommand);
+}
+
+} // namespace
+
+Status validateFindCommand(const FindCommand& findCommand) {
+ // Min and Max objects must have the same fields.
+ if (!findCommand.getMin().isEmpty() && !findCommand.getMax().isEmpty()) {
+ if (!findCommand.getMin().isFieldNamePrefixOf(findCommand.getMax()) ||
+ (findCommand.getMin().nFields() != findCommand.getMax().nFields())) {
+ return Status(ErrorCodes::Error(51176), "min and max must have the same field names");
+ }
+ }
+
+ if ((findCommand.getLimit() || findCommand.getBatchSize()) && findCommand.getNtoreturn()) {
+ return Status(ErrorCodes::BadValue,
+ "'limit' or 'batchSize' fields can not be set with 'ntoreturn' field.");
+ }
+
+ // TODO SERVER-53060: When legacy query request is seperated, these validations can be moved to
+ // IDL.
+ if (findCommand.getSkip() && *findCommand.getSkip() < 0) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "Skip value must be non-negative, but received: "
+ << *findCommand.getSkip());
+ }
+
+ if (findCommand.getLimit() && *findCommand.getLimit() < 0) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "Limit value must be non-negative, but received: "
+ << *findCommand.getLimit());
+ }
+
+ if (findCommand.getBatchSize() && *findCommand.getBatchSize() < 0) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "BatchSize value must be non-negative, but received: "
+ << *findCommand.getBatchSize());
+ }
+
+ if (findCommand.getNtoreturn() && *findCommand.getNtoreturn() < 0) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "NToReturn value must be non-negative, but received: "
+ << *findCommand.getNtoreturn());
+ }
+
+ int maxTimeMS = findCommand.getMaxTimeMS() ? static_cast<int>(*findCommand.getMaxTimeMS()) : 0;
+ if (maxTimeMS < 0) {
+ return Status(ErrorCodes::BadValue,
+ str::stream()
+ << "MaxTimeMS value must be non-negative, but received: " << maxTimeMS);
+ }
+
+ if (query_request_helper::getTailableMode(findCommand) != TailableModeEnum::kNormal) {
+ // Tailable cursors cannot have any sort other than {$natural: 1}.
+ const BSONObj expectedSort = BSON(query_request_helper::kNaturalSortField << 1);
+ if (!findCommand.getSort().isEmpty() &&
+ SimpleBSONObjComparator::kInstance.evaluate(findCommand.getSort() != expectedSort)) {
+ return Status(ErrorCodes::BadValue,
+ "cannot use tailable option with a sort other than {$natural: 1}");
+ }
+
+ // Cannot indicate that you want a 'singleBatch' if the cursor is tailable.
+ if (findCommand.getSingleBatch()) {
+ return Status(ErrorCodes::BadValue,
+ "cannot use tailable option with the 'singleBatch' option");
+ }
+ }
+
+ if (findCommand.getRequestResumeToken()) {
+ if (SimpleBSONObjComparator::kInstance.evaluate(
+ findCommand.getHint() != BSON(query_request_helper::kNaturalSortField << 1))) {
+ return Status(ErrorCodes::BadValue,
+ "hint must be {$natural:1} if 'requestResumeToken' is enabled");
+ }
+ if (!findCommand.getSort().isEmpty() &&
+ SimpleBSONObjComparator::kInstance.evaluate(
+ findCommand.getSort() != BSON(query_request_helper::kNaturalSortField << 1))) {
+ return Status(ErrorCodes::BadValue,
+ "sort must be unset or {$natural:1} if 'requestResumeToken' is enabled");
+ }
+ if (!findCommand.getResumeAfter().isEmpty()) {
+ if (findCommand.getResumeAfter().nFields() != 1 ||
+ (findCommand.getResumeAfter()["$recordId"].type() != BSONType::NumberLong &&
+ findCommand.getResumeAfter()["$recordId"].type() != BSONType::jstOID &&
+ findCommand.getResumeAfter()["$recordId"].type() != BSONType::jstNULL)) {
+ return Status(
+ ErrorCodes::BadValue,
+ "Malformed resume token: the '_resumeAfter' object must contain"
+ " exactly one field named '$recordId', of type NumberLong, jstOID or jstNULL.");
+ }
+ }
+ } else if (!findCommand.getResumeAfter().isEmpty()) {
+ return Status(ErrorCodes::BadValue,
+ "'requestResumeToken' must be true if 'resumeAfter' is"
+ " specified");
+ }
+
+ return Status::OK();
+}
+
+void refreshNSS(const NamespaceString& nss, FindCommand* findCommand) {
+ if (findCommand->getNamespaceOrUUID().uuid()) {
+ auto& nssOrUUID = findCommand->getNamespaceOrUUID();
+ nssOrUUID.setNss(nss);
+ }
+ invariant(findCommand->getNamespaceOrUUID().nss());
+}
+
+std::unique_ptr<FindCommand> makeFromFindCommand(const BSONObj& cmdObj,
+ boost::optional<NamespaceString> nss,
+ bool apiStrict) {
+
+ auto findCommand = std::make_unique<FindCommand>(
+ FindCommand::parse(IDLParserErrorContext("FindCommand", apiStrict), cmdObj));
+
+ // If there is an explicit namespace specified overwite it.
+ if (nss) {
+ auto& nssOrUuid = findCommand->getNamespaceOrUUID();
+ nssOrUuid.setNss(*nss);
+ }
+
+ addMetaProjection(findCommand.get());
+
+ if (findCommand->getSkip() && *findCommand->getSkip() == 0) {
+ findCommand->setSkip(boost::none);
+ }
+ if (findCommand->getLimit() && *findCommand->getLimit() == 0) {
+ findCommand->setLimit(boost::none);
+ }
+ uassertStatusOK(validateFindCommand(*findCommand));
+
+ return findCommand;
+}
+
+std::unique_ptr<FindCommand> makeFromFindCommandForTests(const BSONObj& cmdObj,
+ boost::optional<NamespaceString> nss,
+ bool apiStrict) {
+ return makeFromFindCommand(cmdObj, nss, apiStrict);
+}
+
+bool isTextScoreMeta(BSONElement elt) {
+ // elt must be foo: {$meta: "textScore"}
+ if (mongo::Object != elt.type()) {
+ return false;
+ }
+ BSONObj metaObj = elt.Obj();
+ BSONObjIterator metaIt(metaObj);
+ // must have exactly 1 element
+ if (!metaIt.more()) {
+ return false;
+ }
+ BSONElement metaElt = metaIt.next();
+ if (metaElt.fieldNameStringData() != "$meta") {
+ return false;
+ }
+ if (mongo::String != metaElt.type()) {
+ return false;
+ }
+ if (StringData{metaElt.valuestr()} != metaTextScore) {
+ return false;
+ }
+ // must have exactly 1 element
+ if (metaIt.more()) {
+ return false;
+ }
+ return true;
+}
+
+void setTailableMode(TailableModeEnum tailableMode, FindCommand* findCommand) {
+ if (tailableMode == TailableModeEnum::kTailableAndAwaitData) {
+ findCommand->setAwaitData(true);
+ findCommand->setTailable(true);
+ } else if (tailableMode == TailableModeEnum::kTailable) {
+ findCommand->setTailable(true);
+ }
+}
+
+TailableModeEnum getTailableMode(const FindCommand& findCommand) {
+ return uassertStatusOK(
+ tailableModeFromBools(findCommand.getTailable(), findCommand.getAwaitData()));
+}
+
+//
+// Old QueryRequest parsing code: SOON TO BE DEPRECATED.
+//
+
+StatusWith<std::unique_ptr<FindCommand>> fromLegacyQueryMessage(const QueryMessage& qm,
+ bool* explain) {
+ auto findCommand = std::make_unique<FindCommand>(NamespaceString(qm.ns));
+
+ Status status = initFindCommand(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<FindCommand>> fromLegacyQuery(NamespaceStringOrUUID nssOrUuid,
+ const BSONObj& queryObj,
+ const BSONObj& proj,
+ int ntoskip,
+ int ntoreturn,
+ int queryOptions,
+ bool* explain) {
+ auto findCommand = std::make_unique<FindCommand>(std::move(nssOrUuid));
+
+ Status status = initFindCommand(
+ ntoskip, ntoreturn, queryOptions, queryObj, proj, true, findCommand.get(), explain);
+ if (!status.isOK()) {
+ return status;
+ }
+
+ return std::move(findCommand);
+}
+
+StatusWith<BSONObj> asAggregationCommand(const FindCommand& findCommand) {
+ BSONObjBuilder aggregationBuilder;
+
+ // First, check if this query has options that are not supported in aggregation.
+ if (!findCommand.getMin().isEmpty()) {
+ return {ErrorCodes::InvalidPipelineOperator,
+ str::stream() << "Option " << FindCommand::kMinFieldName
+ << " not supported in aggregation."};
+ }
+ if (!findCommand.getMax().isEmpty()) {
+ return {ErrorCodes::InvalidPipelineOperator,
+ str::stream() << "Option " << FindCommand::kMaxFieldName
+ << " not supported in aggregation."};
+ }
+ if (findCommand.getReturnKey()) {
+ return {ErrorCodes::InvalidPipelineOperator,
+ str::stream() << "Option " << FindCommand::kReturnKeyFieldName
+ << " not supported in aggregation."};
+ }
+ if (findCommand.getShowRecordId()) {
+ return {ErrorCodes::InvalidPipelineOperator,
+ str::stream() << "Option " << FindCommand::kShowRecordIdFieldName
+ << " not supported in aggregation."};
+ }
+ if (findCommand.getTailable()) {
+ return {ErrorCodes::InvalidPipelineOperator,
+ "Tailable cursors are not supported in aggregation."};
+ }
+ if (findCommand.getNoCursorTimeout()) {
+ return {ErrorCodes::InvalidPipelineOperator,
+ str::stream() << "Option " << FindCommand::kNoCursorTimeoutFieldName
+ << " not supported in aggregation."};
+ }
+ if (findCommand.getAllowPartialResults()) {
+ return {ErrorCodes::InvalidPipelineOperator,
+ str::stream() << "Option " << FindCommand::kAllowPartialResultsFieldName
+ << " not supported in aggregation."};
+ }
+ if (findCommand.getNtoreturn()) {
+ return {ErrorCodes::BadValue,
+ str::stream() << "Cannot convert to an aggregation if ntoreturn is set."};
+ }
+ if (findCommand.getSort()[query_request_helper::kNaturalSortField]) {
+ return {ErrorCodes::InvalidPipelineOperator,
+ str::stream() << "Sort option " << query_request_helper::kNaturalSortField
+ << " not supported in aggregation."};
+ }
+ // The aggregation command normally does not support the 'singleBatch' option, but we make a
+ // special exception if 'limit' is set to 1.
+ if (findCommand.getSingleBatch() && findCommand.getLimit().value_or(0) != 1LL) {
+ return {ErrorCodes::InvalidPipelineOperator,
+ str::stream() << "Option " << FindCommand::kSingleBatchFieldName
+ << " not supported in aggregation."};
+ }
+ if (findCommand.getReadOnce()) {
+ return {ErrorCodes::InvalidPipelineOperator,
+ str::stream() << "Option " << FindCommand::kReadOnceFieldName
+ << " not supported in aggregation."};
+ }
+
+ if (findCommand.getAllowSpeculativeMajorityRead()) {
+ return {ErrorCodes::InvalidPipelineOperator,
+ str::stream() << "Option " << FindCommand::kAllowSpeculativeMajorityReadFieldName
+ << " not supported in aggregation."};
+ }
+
+ if (findCommand.getRequestResumeToken()) {
+ return {ErrorCodes::InvalidPipelineOperator,
+ str::stream() << "Option " << FindCommand::kRequestResumeTokenFieldName
+ << " not supported in aggregation."};
+ }
+
+ if (!findCommand.getResumeAfter().isEmpty()) {
+ return {ErrorCodes::InvalidPipelineOperator,
+ str::stream() << "Option " << FindCommand::kResumeAfterFieldName
+ << " not supported in aggregation."};
+ }
+
+ // Now that we've successfully validated this QR, begin building the aggregation command.
+ aggregationBuilder.append("aggregate",
+ findCommand.getNamespaceOrUUID().nss()
+ ? findCommand.getNamespaceOrUUID().nss()->coll()
+ : "");
+
+ // Construct an aggregation pipeline that finds the equivalent documents to this query request.
+ BSONArrayBuilder pipelineBuilder(aggregationBuilder.subarrayStart("pipeline"));
+ if (!findCommand.getFilter().isEmpty()) {
+ BSONObjBuilder matchBuilder(pipelineBuilder.subobjStart());
+ matchBuilder.append("$match", findCommand.getFilter());
+ matchBuilder.doneFast();
+ }
+ if (!findCommand.getSort().isEmpty()) {
+ BSONObjBuilder sortBuilder(pipelineBuilder.subobjStart());
+ sortBuilder.append("$sort", findCommand.getSort());
+ sortBuilder.doneFast();
+ }
+ if (findCommand.getSkip()) {
+ BSONObjBuilder skipBuilder(pipelineBuilder.subobjStart());
+ skipBuilder.append("$skip", *findCommand.getSkip());
+ skipBuilder.doneFast();
+ }
+ if (findCommand.getLimit()) {
+ BSONObjBuilder limitBuilder(pipelineBuilder.subobjStart());
+ limitBuilder.append("$limit", *findCommand.getLimit());
+ limitBuilder.doneFast();
+ }
+ if (!findCommand.getProjection().isEmpty()) {
+ BSONObjBuilder projectBuilder(pipelineBuilder.subobjStart());
+ projectBuilder.append("$project", findCommand.getProjection());
+ projectBuilder.doneFast();
+ }
+ pipelineBuilder.doneFast();
+
+ // The aggregation 'cursor' option is always set, regardless of the presence of batchSize.
+ BSONObjBuilder batchSizeBuilder(aggregationBuilder.subobjStart("cursor"));
+ if (findCommand.getBatchSize()) {
+ batchSizeBuilder.append(FindCommand::kBatchSizeFieldName, *findCommand.getBatchSize());
+ }
+ batchSizeBuilder.doneFast();
+
+ // Other options.
+ aggregationBuilder.append("collation", findCommand.getCollation());
+ int maxTimeMS = findCommand.getMaxTimeMS() ? static_cast<int>(*findCommand.getMaxTimeMS()) : 0;
+ if (maxTimeMS > 0) {
+ aggregationBuilder.append(cmdOptionMaxTimeMS, maxTimeMS);
+ }
+ if (!findCommand.getHint().isEmpty()) {
+ aggregationBuilder.append(FindCommand::kHintFieldName, findCommand.getHint());
+ }
+ if (findCommand.getReadConcern()) {
+ aggregationBuilder.append("readConcern", *findCommand.getReadConcern());
+ }
+ if (!findCommand.getUnwrappedReadPref().isEmpty()) {
+ aggregationBuilder.append(FindCommand::kUnwrappedReadPrefFieldName,
+ findCommand.getUnwrappedReadPref());
+ }
+ if (findCommand.getAllowDiskUse()) {
+ aggregationBuilder.append(FindCommand::kAllowDiskUseFieldName,
+ static_cast<bool>(findCommand.getAllowDiskUse()));
+ }
+ if (findCommand.getLegacyRuntimeConstants()) {
+ BSONObjBuilder rtcBuilder(
+ aggregationBuilder.subobjStart(FindCommand::kLegacyRuntimeConstantsFieldName));
+ findCommand.getLegacyRuntimeConstants()->serialize(&rtcBuilder);
+ rtcBuilder.doneFast();
+ }
+ if (findCommand.getLet()) {
+ aggregationBuilder.append(FindCommand::kLetFieldName, *findCommand.getLet());
+ }
+ return StatusWith<BSONObj>(aggregationBuilder.obj());
+}
+
+} // namespace query_request_helper
+} // namespace mongo