mongo/src/mongo/db/query/query_request_test.cpp
Matt Kneiser 5e886ad953 SERVER-90645 Enable tidy check misc-unused-using-decls (#22430)
Co-authored-by: Benety Goh <benety@mongodb.com>
GitOrigin-RevId: e97b100882e14cca504952d321cedfb135321240
2024-05-23 18:13:58 +00:00

1694 lines
65 KiB
C++

/**
* 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 <algorithm>
#include <boost/cstdint.hpp>
#include <boost/move/utility_core.hpp>
#include <boost/none.hpp>
#include <boost/optional/optional.hpp>
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "mongo/base/error_codes.h"
#include "mongo/base/status.h"
#include "mongo/base/status_with.h"
#include "mongo/base/string_data.h"
#include "mongo/bson/bsonelement.h"
#include "mongo/bson/bsonmisc.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/bson/json.h"
#include "mongo/bson/simple_bsonobj_comparator.h"
#include "mongo/bson/timestamp.h"
#include "mongo/db/basic_types.h"
#include "mongo/db/basic_types_gen.h"
#include "mongo/db/cursor_id.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/pipeline/aggregate_command_gen.h"
#include "mongo/db/pipeline/aggregation_request_helper.h"
#include "mongo/db/pipeline/legacy_runtime_constants_gen.h"
#include "mongo/db/pipeline/query_request_conversion.h"
#include "mongo/db/query/cursor_response_gen.h"
#include "mongo/db/query/find_command.h"
#include "mongo/db/query/find_command_gen.h"
#include "mongo/db/query/max_time_ms_parser.h"
#include "mongo/db/query/query_request_helper.h"
#include "mongo/db/query/query_test_service_context.h"
#include "mongo/db/query/tailable_mode_gen.h"
#include "mongo/db/repl/replication_coordinator.h"
#include "mongo/db/repl/replication_coordinator_mock.h"
#include "mongo/db/service_context_test_fixture.h"
#include "mongo/db/tenant_id.h"
#include "mongo/idl/idl_parser.h"
#include "mongo/idl/server_parameter_test_util.h"
#include "mongo/rpc/op_msg.h"
#include "mongo/unittest/assert.h"
#include "mongo/unittest/bson_test_util.h"
#include "mongo/unittest/framework.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/serialization_context.h"
#include "mongo/util/time_support.h"
#include "mongo/util/uuid.h"
namespace mongo {
namespace {
using std::unique_ptr;
static const NamespaceString testns =
NamespaceString::createNamespaceString_forTest("testdb.testcoll");
TEST(QueryRequestTest, NegativeSkip) {
FindCommandRequest findCommand(testns);
ASSERT_THROWS_CODE(findCommand.setSkip(-1), DBException, ErrorCodes::BadValue);
}
TEST(QueryRequestTest, ZeroSkip) {
FindCommandRequest findCommand(testns);
findCommand.setSkip(0);
ASSERT_OK(query_request_helper::validateFindCommandRequest(findCommand));
}
TEST(QueryRequestTest, PositiveSkip) {
FindCommandRequest findCommand(testns);
findCommand.setSkip(1);
ASSERT_OK(query_request_helper::validateFindCommandRequest(findCommand));
}
TEST(QueryRequestTest, NegativeLimit) {
FindCommandRequest findCommand(testns);
ASSERT_THROWS_CODE(findCommand.setLimit(-1), DBException, ErrorCodes::BadValue);
}
TEST(QueryRequestTest, ZeroLimit) {
FindCommandRequest findCommand(testns);
findCommand.setLimit(0);
ASSERT_OK(query_request_helper::validateFindCommandRequest(findCommand));
}
TEST(QueryRequestTest, PositiveLimit) {
FindCommandRequest findCommand(testns);
findCommand.setLimit(1);
ASSERT_OK(query_request_helper::validateFindCommandRequest(findCommand));
}
TEST(QueryRequestTest, NegativeBatchSize) {
FindCommandRequest findCommand(testns);
ASSERT_THROWS_CODE(findCommand.setBatchSize(-1), DBException, ErrorCodes::BadValue);
}
TEST(QueryRequestTest, ZeroBatchSize) {
FindCommandRequest findCommand(testns);
findCommand.setBatchSize(0);
ASSERT_OK(query_request_helper::validateFindCommandRequest(findCommand));
}
TEST(QueryRequestTest, PositiveBatchSize) {
FindCommandRequest findCommand(testns);
findCommand.setBatchSize(1);
ASSERT_OK(query_request_helper::validateFindCommandRequest(findCommand));
}
TEST(QueryRequestTest, NegativeMaxTimeMS) {
FindCommandRequest findCommand(testns);
ASSERT_THROWS_CODE(findCommand.setMaxTimeMS(-1), DBException, ErrorCodes::BadValue);
}
TEST(QueryRequestTest, ZeroMaxTimeMS) {
FindCommandRequest findCommand(testns);
findCommand.setMaxTimeMS(0);
ASSERT_OK(query_request_helper::validateFindCommandRequest(findCommand));
}
TEST(QueryRequestTest, PositiveMaxTimeMS) {
FindCommandRequest findCommand(testns);
findCommand.setMaxTimeMS(1);
ASSERT_OK(query_request_helper::validateFindCommandRequest(findCommand));
}
TEST(QueryRequestTest, ValidSortOrder) {
FindCommandRequest findCommand(testns);
findCommand.setSort(fromjson("{a: 1}"));
ASSERT_OK(query_request_helper::validateFindCommandRequest(findCommand));
}
TEST(QueryRequestTest, DoesNotErrorOnInvalidSortPattern) {
FindCommandRequest findCommand(testns);
findCommand.setSort(fromjson("{a: \"\"}"));
// FindCommandRequest isn't responsible for validating the sort pattern, so it is considered
// valid even though the sort pattern {a: ""} is not well-formed.
ASSERT_OK(query_request_helper::validateFindCommandRequest(findCommand));
}
TEST(QueryRequestTest, MinFieldsNotPrefixOfMax) {
FindCommandRequest findCommand(testns);
findCommand.setMin(fromjson("{a: 1}"));
findCommand.setMax(fromjson("{b: 1}"));
ASSERT_NOT_OK(query_request_helper::validateFindCommandRequest(findCommand));
}
TEST(QueryRequestTest, MinFieldsMoreThanMax) {
FindCommandRequest findCommand(testns);
findCommand.setMin(fromjson("{a: 1, b: 1}"));
findCommand.setMax(fromjson("{a: 1}"));
ASSERT_NOT_OK(query_request_helper::validateFindCommandRequest(findCommand));
}
TEST(QueryRequestTest, MinFieldsLessThanMax) {
FindCommandRequest findCommand(testns);
findCommand.setMin(fromjson("{a: 1}"));
findCommand.setMax(fromjson("{a: 1, b: 1}"));
ASSERT_NOT_OK(query_request_helper::validateFindCommandRequest(findCommand));
}
TEST(QueryRequestTest, ForbidTailableWithNonNaturalSort) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"tailable: true,"
"sort: {a: 1}, '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::BadValue);
}
TEST(QueryRequestTest, ForbidTailableWithSingleBatch) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"tailable: true,"
"singleBatch: true, '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::BadValue);
}
TEST(QueryRequestTest, AllowTailableWithNaturalSort) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"tailable: true,"
"sort: {$natural: 1}, '$db': 'test'}");
auto findCommand = query_request_helper::makeFromFindCommandForTests(cmdObj);
ASSERT_TRUE(findCommand->getTailable());
ASSERT_BSONOBJ_EQ(findCommand->getSort(), BSON("$natural" << 1));
}
//
// Test compatibility of various projection and sort objects.
//
TEST(QueryRequestTest, ValidSortProj) {
FindCommandRequest findCommand(testns);
findCommand.setProjection(fromjson("{a: 1}"));
findCommand.setSort(fromjson("{a: 1}"));
ASSERT_OK(query_request_helper::validateFindCommandRequest(findCommand));
FindCommandRequest metaFC(testns);
metaFC.setProjection(fromjson("{a: {$meta: \"textScore\"}}"));
metaFC.setSort(fromjson("{a: {$meta: \"textScore\"}}"));
ASSERT_OK(query_request_helper::validateFindCommandRequest(metaFC));
}
TEST(QueryRequestTest, TextScoreMetaSortOnFieldDoesNotRequireMetaProjection) {
FindCommandRequest findCommand(testns);
findCommand.setProjection(fromjson("{b: 1}"));
findCommand.setSort(fromjson("{a: {$meta: 'textScore'}}"));
ASSERT_OK(query_request_helper::validateFindCommandRequest(findCommand));
}
TEST(QueryRequestTest, TextScoreMetaProjectionDoesNotRequireTextScoreMetaSort) {
FindCommandRequest findCommand(testns);
findCommand.setProjection(fromjson("{a: {$meta: \"textScore\"}}"));
findCommand.setSort(fromjson("{b: 1}"));
ASSERT_OK(query_request_helper::validateFindCommandRequest(findCommand));
}
TEST(QueryRequestTest, RequestResumeTokenWithHint) {
FindCommandRequest findCommand(testns);
findCommand.setRequestResumeToken(true);
ASSERT_NOT_OK(query_request_helper::validateFindCommandRequest(findCommand));
findCommand.setHint(fromjson("{a: 1}"));
ASSERT_NOT_OK(query_request_helper::validateFindCommandRequest(findCommand));
findCommand.setHint(fromjson("{$natural: 1}"));
ASSERT_OK(query_request_helper::validateFindCommandRequest(findCommand));
}
TEST(QueryRequestTest, RequestResumeTokenWithSort) {
FindCommandRequest findCommand(testns);
findCommand.setRequestResumeToken(true);
// Hint must be explicitly set for the query request to validate.
findCommand.setHint(fromjson("{$natural: 1}"));
ASSERT_OK(query_request_helper::validateFindCommandRequest(findCommand));
findCommand.setSort(fromjson("{a: 1}"));
ASSERT_NOT_OK(query_request_helper::validateFindCommandRequest(findCommand));
findCommand.setSort(fromjson("{$natural: 1}"));
ASSERT_OK(query_request_helper::validateFindCommandRequest(findCommand));
}
TEST(QueryRequestTest, InvalidResumeAfterWrongRecordIdType) {
QueryTestServiceContext serviceContext;
auto uniqueTxn = serviceContext.makeOperationContext();
OperationContext* opCtx = uniqueTxn.get();
FindCommandRequest findCommand(testns);
BSONObj resumeAfter = BSON("$recordId" << 1);
findCommand.setResumeAfter(resumeAfter);
findCommand.setRequestResumeToken(true);
// Hint must be explicitly set for the query request to validate.
findCommand.setHint(fromjson("{$natural: 1}"));
ASSERT_NOT_OK(query_request_helper::validateResumeAfter(
opCtx, findCommand.getResumeAfter(), false /* isClusteredCollection */));
resumeAfter = BSON("$recordId" << 1LL);
findCommand.setResumeAfter(resumeAfter);
ASSERT_OK(query_request_helper::validateFindCommandRequest(findCommand));
ASSERT_OK(query_request_helper::validateResumeAfter(
opCtx, findCommand.getResumeAfter(), false /* isClusteredCollection */));
}
TEST(QueryRequestTest, InvalidResumeAfterExtraField) {
QueryTestServiceContext serviceContext;
auto uniqueTxn = serviceContext.makeOperationContext();
OperationContext* opCtx = uniqueTxn.get();
FindCommandRequest findCommand(testns);
BSONObj resumeAfter = BSON("$recordId" << 1LL << "$extra" << 1);
findCommand.setResumeAfter(resumeAfter);
findCommand.setRequestResumeToken(true);
// Hint must be explicitly set for the query request to validate.
findCommand.setHint(fromjson("{$natural: 1}"));
ASSERT_NOT_OK(query_request_helper::validateResumeAfter(
opCtx, findCommand.getResumeAfter(), false /* isClusteredCollection */));
}
TEST(QueryRequestTest, ResumeAfterWithHint) {
FindCommandRequest findCommand(testns);
BSONObj resumeAfter = BSON("$recordId" << 1LL);
findCommand.setResumeAfter(resumeAfter);
findCommand.setRequestResumeToken(true);
ASSERT_NOT_OK(query_request_helper::validateFindCommandRequest(findCommand));
findCommand.setHint(fromjson("{a: 1}"));
ASSERT_NOT_OK(query_request_helper::validateFindCommandRequest(findCommand));
findCommand.setHint(fromjson("{$natural: 1}"));
ASSERT_OK(query_request_helper::validateFindCommandRequest(findCommand));
}
TEST(QueryRequestTest, ResumeAfterWithSort) {
QueryTestServiceContext serviceContext;
auto uniqueTxn = serviceContext.makeOperationContext();
OperationContext* opCtx = uniqueTxn.get();
FindCommandRequest findCommand(testns);
BSONObj resumeAfter = BSON("$recordId" << 1LL);
findCommand.setResumeAfter(resumeAfter);
findCommand.setRequestResumeToken(true);
// Hint must be explicitly set for the query request to validate.
findCommand.setHint(fromjson("{$natural: 1}"));
ASSERT_OK(query_request_helper::validateFindCommandRequest(findCommand));
ASSERT_OK(query_request_helper::validateResumeAfter(
opCtx, findCommand.getResumeAfter(), false /* isClusteredCollection */));
findCommand.setSort(fromjson("{a: 1}"));
ASSERT_NOT_OK(query_request_helper::validateFindCommandRequest(findCommand));
findCommand.setSort(fromjson("{$natural: 1}"));
ASSERT_OK(query_request_helper::validateFindCommandRequest(findCommand));
}
TEST(QueryRequestTest, ResumeNoSpecifiedRequestResumeToken) {
QueryTestServiceContext serviceContext;
auto uniqueTxn = serviceContext.makeOperationContext();
OperationContext* opCtx = uniqueTxn.get();
FindCommandRequest findCommand(testns);
BSONObj resumeAfter = BSON("$recordId" << 1LL);
findCommand.setResumeAfter(resumeAfter);
// Hint must be explicitly set for the query request to validate.
findCommand.setHint(fromjson("{$natural: 1}"));
ASSERT_NOT_OK(query_request_helper::validateFindCommandRequest(findCommand));
findCommand.setRequestResumeToken(true);
ASSERT_OK(query_request_helper::validateFindCommandRequest(findCommand));
ASSERT_OK(query_request_helper::validateResumeAfter(
opCtx, findCommand.getResumeAfter(), false /* isClusteredCollection */));
}
TEST(QueryRequestTest, ExplicitEmptyResumeAfter) {
QueryTestServiceContext serviceContext;
auto uniqueTxn = serviceContext.makeOperationContext();
OperationContext* opCtx = uniqueTxn.get();
FindCommandRequest findCommand(NamespaceString::kRsOplogNamespace);
BSONObj resumeAfter = fromjson("{}");
// Hint must be explicitly set for the query request to validate.
findCommand.setHint(fromjson("{$natural: 1}"));
findCommand.setResumeAfter(resumeAfter);
ASSERT_OK(query_request_helper::validateFindCommandRequest(findCommand));
findCommand.setRequestResumeToken(true);
ASSERT_OK(query_request_helper::validateFindCommandRequest(findCommand));
ASSERT_OK(query_request_helper::validateResumeAfter(
opCtx, findCommand.getResumeAfter(), false /* isClusteredCollection */));
}
TEST(QueryRequestTest, ResumeAfterMismatchInitialSyncId) {
QueryTestServiceContext serviceContext;
auto uniqueTxn = serviceContext.makeOperationContext();
OperationContext* opCtx = uniqueTxn.get();
auto replCoord =
std::make_unique<repl::ReplicationCoordinatorMock>(serviceContext.getServiceContext());
repl::ReplicationCoordinator::set(serviceContext.getServiceContext(), std::move(replCoord));
FindCommandRequest findCommand(testns);
BSONObj resumeAfter =
BSON("$recordId" << 1LL << "$initialSyncId"
<< uassertStatusOK(UUID::parse("12345678-1234-9876-1234-000000000000")));
findCommand.setResumeAfter(resumeAfter);
// Hint must be explicitly set for the query request to validate.
findCommand.setHint(fromjson("{$natural: 1}"));
ASSERT_NOT_OK(query_request_helper::validateFindCommandRequest(findCommand));
findCommand.setRequestResumeToken(true);
ASSERT_OK(query_request_helper::validateFindCommandRequest(findCommand));
ASSERT_NOT_OK(query_request_helper::validateResumeAfter(
opCtx, findCommand.getResumeAfter(), false /* isClusteredCollection */));
}
//
// Text meta BSON element validation
//
bool isFirstElementTextScoreMeta(const char* sortStr) {
BSONObj sortObj = fromjson(sortStr);
BSONElement elt = sortObj.firstElement();
bool result = query_request_helper::isTextScoreMeta(elt);
return result;
}
// Check validation of $meta expressions
TEST(QueryRequestTest, IsTextScoreMeta) {
// Valid textScore meta sort
ASSERT(isFirstElementTextScoreMeta("{a: {$meta: \"textScore\"}}"));
// Invalid textScore meta sorts
ASSERT_FALSE(isFirstElementTextScoreMeta("{a: {$meta: 1}}"));
ASSERT_FALSE(isFirstElementTextScoreMeta("{a: {$meta: \"image\"}}"));
ASSERT_FALSE(isFirstElementTextScoreMeta("{a: {$world: \"textScore\"}}"));
ASSERT_FALSE(isFirstElementTextScoreMeta("{a: {$meta: \"textScore\", b: 1}}"));
}
//
// Tests for parsing a query request from a command BSON object.
//
TEST(QueryRequestTest, ParseFromCommandBasic) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"filter: {a: 3},"
"sort: {a: 1},"
"projection: {_id: 0, a: 1}, '$db': 'test'}");
query_request_helper::makeFromFindCommandForTests(cmdObj);
}
TEST(QueryRequestTest, ParseFromCommandWithOptions) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"filter: {a: 3},"
"sort: {a: 1},"
"projection: {_id: 0, a: 1},"
"showRecordId: true, '$db': 'test'}");
unique_ptr<FindCommandRequest> findCommand(
query_request_helper::makeFromFindCommandForTests(cmdObj));
// Make sure the values from the command BSON are reflected in the QR.
ASSERT(findCommand->getShowRecordId());
}
TEST(QueryRequestTest, ParseFromCommandHintAsString) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"filter: {a: 1},"
"hint: 'foo_1', '$db': 'test'}");
unique_ptr<FindCommandRequest> findCommand(
query_request_helper::makeFromFindCommandForTests(cmdObj));
BSONObj hintObj = findCommand->getHint();
ASSERT_BSONOBJ_EQ(BSON("$hint"
<< "foo_1"),
hintObj);
}
TEST(QueryRequestTest, ParseFromCommandValidSortProj) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"projection: {a: 1},"
"sort: {a: 1}, '$db': 'test'}");
query_request_helper::makeFromFindCommandForTests(cmdObj);
}
TEST(QueryRequestTest, ParseFromCommandValidSortProjMeta) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"projection: {a: {$meta: 'textScore'}},"
"sort: {a: {$meta: 'textScore'}}, '$db': 'test'}");
query_request_helper::makeFromFindCommandForTests(cmdObj);
}
TEST(QueryRequestTest, ParseFromCommandAllFlagsTrue) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"tailable: true,"
"noCursorTimeout: true,"
"awaitData: true,"
"allowPartialResults: true,"
"readOnce: true,"
"includeQueryStatsMetrics: true,"
"allowSpeculativeMajorityRead: true, '$db': 'test'}");
unique_ptr<FindCommandRequest> findCommand(
query_request_helper::makeFromFindCommandForTests(cmdObj));
// Test that all the flags got set to true.
ASSERT(findCommand->getTailable());
ASSERT(findCommand->getNoCursorTimeout());
ASSERT(findCommand->getTailable() && findCommand->getAwaitData());
ASSERT(findCommand->getAllowPartialResults());
ASSERT(findCommand->getReadOnce());
ASSERT(findCommand->getIncludeQueryStatsMetrics());
ASSERT(findCommand->getAllowSpeculativeMajorityRead());
}
TEST(QueryRequestTest, OplogReplayFlagIsAllowedButIgnored) {
auto cmdObj = BSON("find"
<< "testns"
<< "oplogReplay" << true << "tailable" << true << "$db"
<< "test");
const NamespaceString nss = NamespaceString::createNamespaceString_forTest("test.testns");
auto findCommand = query_request_helper::makeFromFindCommandForTests(cmdObj);
// Verify that the 'oplogReplay' flag does not appear if we reserialize the request.
auto reserialized = findCommand->toBSON(BSONObj());
ASSERT_BSONOBJ_EQ(reserialized,
BSON("find"
<< "testns"
<< "tailable" << true));
}
TEST(QueryRequestTest, ParseFromCommandReadOnceDefaultsToFalse) {
BSONObj cmdObj = fromjson("{find: 'testns', '$db': 'test'}");
unique_ptr<FindCommandRequest> findCommand(
query_request_helper::makeFromFindCommandForTests(cmdObj));
ASSERT(!findCommand->getReadOnce());
}
TEST(QueryRequestTest, ParseFromCommandIncludeQueryStatsMetricsDefaultsToFalse) {
BSONObj cmdObj = fromjson("{find: 'testns', '$db': 'test'}");
unique_ptr<FindCommandRequest> findCommand(
query_request_helper::makeFromFindCommandForTests(cmdObj));
ASSERT(!findCommand->getIncludeQueryStatsMetrics());
}
TEST(QueryRequestTest, ParseFromCommandValidMinMax) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"comment: 'the comment',"
"min: {a: 1},"
"max: {a: 2}, '$db': 'test'}");
unique_ptr<FindCommandRequest> findCommand(
query_request_helper::makeFromFindCommandForTests(cmdObj));
BSONObj expectedMin = BSON("a" << 1);
ASSERT_EQUALS(0, expectedMin.woCompare(findCommand->getMin()));
BSONObj expectedMax = BSON("a" << 2);
ASSERT_EQUALS(0, expectedMax.woCompare(findCommand->getMax()));
}
TEST(QueryRequestTest, ParseFromCommandAllNonOptionFields) {
LegacyRuntimeConstants 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, '$db': 'test'}")
.addField(rtcObj["runtimeConstants"]);
unique_ptr<FindCommandRequest> findCommand(
query_request_helper::makeFromFindCommandForTests(cmdObj));
// Check the values inside the QR.
BSONObj expectedQuery = BSON("a" << 1);
ASSERT_EQUALS(0, expectedQuery.woCompare(findCommand->getFilter()));
BSONObj expectedSort = BSON("b" << 1);
ASSERT_EQUALS(0, expectedSort.woCompare(findCommand->getSort()));
BSONObj expectedProj = BSON("c" << 1);
ASSERT_EQUALS(0, expectedProj.woCompare(findCommand->getProjection()));
BSONObj expectedHint = BSON("d" << 1);
ASSERT_EQUALS(0, expectedHint.woCompare(findCommand->getHint()));
BSONObj expectedReadConcern = BSON("e" << 1);
ASSERT(findCommand->getReadConcern());
ASSERT_BSONOBJ_EQ(expectedReadConcern, *findCommand->getReadConcern());
BSONObj expectedUnwrappedReadPref = BSON("$readPreference"
<< "secondary");
ASSERT_EQUALS(0, expectedUnwrappedReadPref.woCompare(findCommand->getUnwrappedReadPref()));
BSONObj expectedCollation = BSON("f" << 1);
ASSERT_EQUALS(0, expectedCollation.woCompare(findCommand->getCollation()));
ASSERT_EQUALS(3, *findCommand->getLimit());
ASSERT_EQUALS(5, *findCommand->getSkip());
ASSERT_EQUALS(90, *findCommand->getBatchSize());
ASSERT(findCommand->getLegacyRuntimeConstants().has_value());
ASSERT_EQUALS(findCommand->getLegacyRuntimeConstants()->getLocalNow(), rtc.getLocalNow());
ASSERT_EQUALS(findCommand->getLegacyRuntimeConstants()->getClusterTime(), rtc.getClusterTime());
ASSERT(!findCommand->getSingleBatch());
}
TEST(QueryRequestTest, ParseFromCommandLargeLimit) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"filter: {a: 1},"
"limit: 8000000000, '$db': 'test'}"); // 8 * 1000 * 1000 * 1000
unique_ptr<FindCommandRequest> findCommand(
query_request_helper::makeFromFindCommandForTests(cmdObj));
ASSERT_EQUALS(8LL * 1000 * 1000 * 1000, *findCommand->getLimit());
}
TEST(QueryRequestTest, ParseFromCommandLargeBatchSize) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"filter: {a: 1},"
"batchSize: 8000000000, '$db': 'test'}"); // 8 * 1000 * 1000 * 1000
unique_ptr<FindCommandRequest> findCommand(
query_request_helper::makeFromFindCommandForTests(cmdObj));
ASSERT_EQUALS(8LL * 1000 * 1000 * 1000, *findCommand->getBatchSize());
}
TEST(QueryRequestTest, ParseFromCommandLargeSkip) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"filter: {a: 1},"
"skip: 8000000000, '$db': 'test'}"); // 8 * 1000 * 1000 * 1000
unique_ptr<FindCommandRequest> findCommand(
query_request_helper::makeFromFindCommandForTests(cmdObj));
ASSERT_EQUALS(8LL * 1000 * 1000 * 1000, *findCommand->getSkip());
}
//
// Parsing errors where a field has the wrong type.
//
TEST(QueryRequestTest, ParseFromCommandQueryWrongType) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"filter: 3, '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::TypeMismatch);
}
TEST(QueryRequestTest, ParseFromCommandSortWrongType) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"filter: {a: 1},"
"sort: 3, '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::TypeMismatch);
}
TEST(QueryRequestTest, ParseFromCommandProjWrongType) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"filter: {a: 1},"
"projection: 'foo', '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::TypeMismatch);
}
TEST(QueryRequestTest, ParseFromCommandSkipWrongType) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"filter: {a: 1},"
"skip: '5',"
"projection: {a: 1}, '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::TypeMismatch);
}
TEST(QueryRequestTest, ParseFromCommandLimitWrongType) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"filter: {a: 1},"
"limit: '5',"
"projection: {a: 1}, '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::TypeMismatch);
}
TEST(QueryRequestTest, ParseFromCommandSingleBatchWrongType) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"filter: {a: 1},"
"singleBatch: 'false',"
"projection: {a: 1}, '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::TypeMismatch);
}
TEST(QueryRequestTest, ParseFromCommandUnwrappedReadPrefWrongType) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"filter: {a: 1},"
"$queryOptions: 1, '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::TypeMismatch);
}
TEST(QueryRequestTest, ParseFromCommandMaxTimeMSWrongType) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"filter: {a: 1},"
"maxTimeMS: true, '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::TypeMismatch);
}
TEST(QueryRequestTest, ParseFromCommandMaxWrongType) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"filter: {a: 1},"
"max: 3, '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::TypeMismatch);
}
TEST(QueryRequestTest, ParseFromCommandMinWrongType) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"filter: {a: 1},"
"min: 3, '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::TypeMismatch);
}
TEST(QueryRequestTest, ParseFromCommandReturnKeyWrongType) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"filter: {a: 1},"
"returnKey: 3, '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::TypeMismatch);
}
TEST(QueryRequestTest, ParseFromCommandShowRecordIdWrongType) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"filter: {a: 1},"
"showRecordId: 3, '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::TypeMismatch);
}
TEST(QueryRequestTest, ParseFromCommandTailableWrongType) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"filter: {a: 1},"
"tailable: 3, '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::TypeMismatch);
}
TEST(QueryRequestTest, ParseFromCommandSlaveOkWrongType) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"filter: {a: 1},"
"slaveOk: 3, '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::IDLUnknownField);
}
TEST(QueryRequestTest, ParseFromCommandOplogReplayWrongType) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"filter: {a: 1},"
"oplogReplay: 3, '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::TypeMismatch);
}
TEST(QueryRequestTest, ParseFromCommandNoCursorTimeoutWrongType) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"filter: {a: 1},"
"noCursorTimeout: 3, '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::TypeMismatch);
}
TEST(QueryRequestTest, ParseFromCommandAwaitDataWrongType) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"filter: {a: 1},"
"tailable: true,"
"awaitData: 3, '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::TypeMismatch);
}
TEST(QueryRequestTest, ParseFromCommandExhaustWrongType) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"filter: {a: 1},"
"exhaust: 3, '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::IDLUnknownField);
}
TEST(QueryRequestTest, ParseFromCommandPartialWrongType) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"filter: {a: 1},"
"allowPartialResults: 3, '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::TypeMismatch);
}
TEST(QueryRequestTest, ParseFromCommandReadConcernWrongType) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"filter: {a: 1},"
"readConcern: 'foo', '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::TypeMismatch);
}
TEST(QueryRequestTest, ParseFromCommandCollationWrongType) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"filter: {a: 1},"
"collation: 'foo', '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::TypeMismatch);
}
TEST(QueryRequestTest, ParseFromCommandReadOnceWrongType) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"readOnce: 1, '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::TypeMismatch);
}
TEST(QueryRequestTest, ParseFromCommandLegacyRuntimeConstantsWrongType) {
BSONObj cmdObj = BSON("find"
<< "testns"
<< "runtimeConstants"
<< "shouldNotBeString"
<< "$db"
<< "test");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::TypeMismatch);
}
TEST(QueryRequestTest, ParseFromCommandLegacyRuntimeConstantsSubfieldsWrongType) {
BSONObj cmdObj = BSON("find"
<< "testns"
<< "runtimeConstants"
<< BSON("localNow"
<< "shouldBeDate"
<< "clusterTime"
<< "shouldBeTimestamp")
<< "$db"
<< "test");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
AssertionException,
ErrorCodes::TypeMismatch);
}
TEST(QueryRequestTest, ParseFromCommandIncludeQueryStatsMetricsWrongType) {
BSONObj cmdObj = fromjson("{find: 'testns', '$db': 'test', 'includeQueryStatsMetrics': 42}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::TypeMismatch);
}
//
// Parsing errors where a field has the right type but a bad value.
//
TEST(QueryRequestTest, ParseFromCommandNegativeSkipError) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"skip: -3,"
"filter: {a: 3}, '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::BadValue);
}
TEST(QueryRequestTest, ParseFromCommandSkipIsZero) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"skip: 0,"
"filter: {a: 3}, '$db': 'test'}");
unique_ptr<FindCommandRequest> findCommand(
query_request_helper::makeFromFindCommandForTests(cmdObj));
ASSERT_BSONOBJ_EQ(BSON("a" << 3), findCommand->getFilter());
ASSERT_FALSE(findCommand->getSkip());
}
TEST(QueryRequestTest, ParseFromCommandNegativeLimitError) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"limit: -3,"
"filter: {a: 3}, '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::BadValue);
}
TEST(QueryRequestTest, ParseFromCommandLimitIsZero) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"limit: 0,"
"filter: {a: 3}, '$db': 'test'}");
unique_ptr<FindCommandRequest> findCommand(
query_request_helper::makeFromFindCommandForTests(cmdObj));
ASSERT_BSONOBJ_EQ(BSON("a" << 3), findCommand->getFilter());
ASSERT_FALSE(findCommand->getLimit());
}
TEST(QueryRequestTest, ParseFromCommandNegativeBatchSizeError) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"batchSize: -10,"
"filter: {a: 3}, '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::BadValue);
}
TEST(QueryRequestTest, ParseFromCommandBatchSizeZero) {
BSONObj cmdObj = fromjson("{find: 'testns', batchSize: 0, '$db': 'test'}");
unique_ptr<FindCommandRequest> findCommand(
query_request_helper::makeFromFindCommandForTests(cmdObj));
ASSERT(findCommand->getBatchSize());
ASSERT_EQ(0, *findCommand->getBatchSize());
ASSERT(!findCommand->getLimit());
}
TEST(QueryRequestTest, ParseFromCommandDefaultBatchSize) {
BSONObj cmdObj = fromjson("{find: 'testns', '$db': 'test'}");
unique_ptr<FindCommandRequest> findCommand(
query_request_helper::makeFromFindCommandForTests(cmdObj));
ASSERT(!findCommand->getBatchSize());
ASSERT(!findCommand->getLimit());
}
TEST(QueryRequestTest, ParseFromCommandRequestResumeToken) {
BSONObj cmdObj = BSON("find"
<< "testns"
<< "hint" << BSON("$natural" << 1) << "sort" << BSON("$natural" << 1)
<< "$_requestResumeToken" << true << "$db"
<< "test");
unique_ptr<FindCommandRequest> findCommand(
query_request_helper::makeFromFindCommandForTests(cmdObj));
ASSERT(findCommand->getRequestResumeToken());
}
TEST(QueryRequestTest, ParseFromCommandResumeToken) {
BSONObj cmdObj = BSON("find"
<< "testns"
<< "hint" << BSON("$natural" << 1) << "sort" << BSON("$natural" << 1)
<< "$_requestResumeToken" << true << "$_resumeAfter"
<< BSON("$recordId" << 1LL) << "$db"
<< "test");
unique_ptr<FindCommandRequest> findCommand(
query_request_helper::makeFromFindCommandForTests(cmdObj));
ASSERT(!findCommand->getResumeAfter().isEmpty());
ASSERT(findCommand->getRequestResumeToken());
}
TEST(QueryRequestTest, ParseFromCommandEmptyResumeToken) {
BSONObj resumeAfter = fromjson("{}");
BSONObj cmdObj =
BSON("find"
<< "testns"
<< "hint" << BSON("$natural" << 1) << "sort" << BSON("$natural" << 1)
<< "$_requestResumeToken" << true << "$_resumeAfter" << resumeAfter << "$db"
<< "test");
unique_ptr<FindCommandRequest> findCommand(
query_request_helper::makeFromFindCommandForTests(cmdObj));
ASSERT(findCommand->getRequestResumeToken());
ASSERT(findCommand->getResumeAfter().isEmpty());
}
//
// Test FindCommandRequest object ns and uuid variants.
//
TEST(QueryRequestTest, AsFindCommandRequestAllNonOptionFields) {
BSONObj storage = BSON("runtimeConstants"
<< (LegacyRuntimeConstants{Date_t::now(), Timestamp(1, 1)}.toBSON()));
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"filter: {a: 1},"
"projection: {c: 1},"
"sort: {b: 1},"
"hint: {d: 1},"
"collation: {f: 1},"
"skip: 5,"
"limit: 3,"
"batchSize: 90,"
"singleBatch: true, "
"readConcern: {e: 1}, '$db': 'test'}")
.addField(storage["runtimeConstants"]);
unique_ptr<FindCommandRequest> findCommand(
query_request_helper::makeFromFindCommandForTests(cmdObj));
ASSERT_BSONOBJ_EQ(cmdObj.removeField("$db"), findCommand->toBSON(BSONObj()));
}
TEST(QueryRequestTest, AsFindCommandRequestWithUuidAllNonOptionFields) {
BSONObj storage = BSON("runtimeConstants"
<< (LegacyRuntimeConstants{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},"
"collation: {f: 1},"
"skip: 5,"
"limit: 3,"
"batchSize: 90,"
"singleBatch: true,"
"readConcern: {e: 1}, '$db': 'test'}")
.addField(storage["runtimeConstants"]);
unique_ptr<FindCommandRequest> findCommand(
query_request_helper::makeFromFindCommandForTests(cmdObj));
ASSERT_BSONOBJ_EQ(cmdObj.removeField("$db"), findCommand->toBSON(BSONObj()));
}
TEST(QueryRequestTest, AsFindCommandRequestWithUuidNoAvailableNamespace) {
BSONObj cmdObj =
fromjson("{find: { \"$binary\" : \"ASNFZ4mrze/ty6mHZUMhAQ==\", \"$type\" : \"04\" }}");
FindCommandRequest findCommand(
NamespaceStringOrUUID(DatabaseName::createDatabaseName_forTest(boost::none, "test"),
UUID::parse("01234567-89ab-cdef-edcb-a98765432101").getValue()));
ASSERT_BSONOBJ_EQ(cmdObj.removeField("$db"), findCommand.toBSON(BSONObj()));
}
TEST(QueryRequestTest, AsFindCommandRequestWithResumeToken) {
BSONObj cmdObj = BSON("find"
<< "testns"
<< "sort" << BSON("$natural" << 1) << "hint" << BSON("$natural" << 1)
<< "$_requestResumeToken" << true << "$_resumeAfter"
<< BSON("$recordId" << 1LL) << "$db"
<< "test");
unique_ptr<FindCommandRequest> findCommand(
query_request_helper::makeFromFindCommandForTests(cmdObj));
ASSERT_BSONOBJ_EQ(cmdObj.removeField("$db"), findCommand->toBSON(BSONObj()));
}
TEST(QueryRequestTest, AsFindCommandRequestWithEmptyResumeToken) {
BSONObj resumeAfter = fromjson("{}");
BSONObj cmdObj =
BSON("find"
<< "testns"
<< "hint" << BSON("$natural" << 1) << "sort" << BSON("$natural" << 1)
<< "$_requestResumeToken" << true << "$_resumeAfter" << resumeAfter << "$db"
<< "test");
unique_ptr<FindCommandRequest> findCommand(
query_request_helper::makeFromFindCommandForTests(cmdObj));
ASSERT(findCommand->toBSON(BSONObj()).getField("$_resumeAftr").eoo());
}
//
//
// Errors checked in query_request_helper::validateFindCommandRequest().
//
TEST(QueryRequestTest, ParseFromCommandMinMaxDifferentFieldsError) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"min: {a: 3},"
"max: {b: 4}, '$db': 'test'}");
ASSERT_THROWS_CODE(
query_request_helper::makeFromFindCommandForTests(cmdObj), DBException, 51176);
}
TEST(QueryRequestTest, ParseCommandAllowNonMetaSortOnFieldWithMetaProject) {
BSONObj cmdObj;
cmdObj = fromjson(
"{find: 'testns',"
"projection: {a: {$meta: 'textScore'}},"
"sort: {a: 1}, '$db': 'test'}");
query_request_helper::makeFromFindCommandForTests(cmdObj);
cmdObj = fromjson(
"{find: 'testns',"
"projection: {a: {$meta: 'textScore'}},"
"sort: {b: 1}, '$db': 'test'}");
query_request_helper::makeFromFindCommandForTests(cmdObj);
}
TEST(QueryRequestTest, ParseCommandAllowMetaSortOnFieldWithoutMetaProject) {
BSONObj cmdObj;
cmdObj = fromjson(
"{find: 'testns',"
"projection: {a: 1},"
"sort: {a: {$meta: 'textScore'}}, '$db': 'test'}");
auto findCommand = query_request_helper::makeFromFindCommandForTests(cmdObj);
cmdObj = fromjson(
"{find: 'testns',"
"projection: {b: 1},"
"sort: {a: {$meta: 'textScore'}}, '$db': 'test'}");
findCommand = query_request_helper::makeFromFindCommandForTests(cmdObj);
}
TEST(QueryRequestTest, ParseCommandForbidExhaust) {
BSONObj cmdObj = fromjson("{find: 'testns', exhaust: true, '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::IDLUnknownField);
}
// Older versions of the server supported an "ntoreturn" parameter to the find command, which was
// used for upconversion of legacy OP_QUERY to a find command. Now that OP_QUERY is no longer
// supported, the "ntoreturn" parameter is not supported either, and should be rejected during
// parsing.
TEST(QueryRequestTest, NToReturnParamFailsToParse) {
BSONObj cmdObj = fromjson("{find: 'testns', '$db': 'test', ntoreturn: 5}");
ASSERT_THROWS_CODE_AND_WHAT(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::IDLUnknownField,
"BSON field 'FindCommandRequest.ntoreturn' is an unknown field.");
}
TEST(QueryRequestTest, ParseCommandAwaitDataButNotTailable) {
BSONObj cmdObj = fromjson("{find: 'testns', awaitData: true, '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::FailedToParse);
}
TEST(QueryRequestTest, ParseCommandFirstFieldNotString) {
BSONObj cmdObj = fromjson("{find: 1, '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::TypeMismatch);
}
TEST(QueryRequestTest, ParseCommandIgnoreShardVersionField) {
BSONObj cmdObj = fromjson("{find: 'test.testns', shardVersion: 'foo', '$db': 'test'}");
query_request_helper::makeFromFindCommandForTests(cmdObj);
}
TEST(QueryRequestTest, DefaultQueryParametersCorrect) {
BSONObj cmdObj = fromjson("{find: 'testns', '$db': 'test'}");
std::unique_ptr<FindCommandRequest> findCommand(
query_request_helper::makeFromFindCommandForTests(cmdObj));
ASSERT_FALSE(findCommand->getSkip());
ASSERT_FALSE(findCommand->getLimit());
ASSERT_FALSE(findCommand->getSingleBatch());
ASSERT_EQUALS(0, findCommand->getMaxTimeMS().value_or(0));
ASSERT_EQUALS(false, findCommand->getReturnKey());
ASSERT_EQUALS(false, findCommand->getShowRecordId());
ASSERT_EQUALS(false, findCommand->getTailable());
ASSERT_EQUALS(false, findCommand->getNoCursorTimeout());
ASSERT_EQUALS(false, findCommand->getTailable() && findCommand->getAwaitData());
ASSERT_EQUALS(false, findCommand->getAllowPartialResults());
ASSERT_EQUALS(false, findCommand->getLegacyRuntimeConstants().has_value());
ASSERT_EQUALS(false, findCommand->getAllowDiskUse());
}
TEST(QueryRequestTest, ParseCommandAllowDiskUseTrue) {
BSONObj cmdObj = fromjson("{find: 'testns', allowDiskUse: true, '$db': 'test'}");
auto result = query_request_helper::makeFromFindCommandForTests(cmdObj);
ASSERT_EQ(true, result->getAllowDiskUse());
}
TEST(QueryRequestTest, ParseCommandAllowDiskUseFalse) {
BSONObj cmdObj = fromjson("{find: 'testns', allowDiskUse: false, '$db': 'test'}");
auto result = query_request_helper::makeFromFindCommandForTests(cmdObj);
ASSERT_EQ(false, result->getAllowDiskUse());
}
//
// Extra fields cause the parse to fail.
//
TEST(QueryRequestTest, ParseFromCommandForbidExtraField) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"foo: {a: 1}, '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::IDLUnknownField);
}
TEST(QueryRequestTest, ParseFromCommandForbidExtraOption) {
BSONObj cmdObj = fromjson(
"{find: 'testns',"
"foo: true, '$db': 'test'}");
ASSERT_THROWS_CODE(query_request_helper::makeFromFindCommandForTests(cmdObj),
DBException,
ErrorCodes::IDLUnknownField);
}
TEST(QueryRequestTest, ParseMaxTimeMSStringValueFails) {
BSONObj maxTimeObj = BSON(query_request_helper::cmdOptionMaxTimeMS << "foo");
ASSERT_EQ(parseMaxTimeMS(maxTimeObj[query_request_helper::cmdOptionMaxTimeMS]).getStatus(),
ErrorCodes::BadValue);
}
TEST(QueryRequestTest, ParseMaxTimeMSBoolValueFails) {
BSONObj maxTimeObj = BSON(query_request_helper::cmdOptionMaxTimeMS << true);
ASSERT_EQ(parseMaxTimeMS(maxTimeObj[query_request_helper::cmdOptionMaxTimeMS]).getStatus(),
ErrorCodes::BadValue);
}
TEST(QueryRequestTest, ParseMaxTimeMSNullValueFails) {
BSONObj maxTimeObj = BSON(query_request_helper::cmdOptionMaxTimeMS << BSONNULL);
ASSERT_EQ(parseMaxTimeMS(maxTimeObj[query_request_helper::cmdOptionMaxTimeMS]).getStatus(),
ErrorCodes::BadValue);
}
TEST(QueryRequestTest, ParseMaxTimeMSUndefinedValueFails) {
BSONObj maxTimeObj = BSON(query_request_helper::cmdOptionMaxTimeMS << BSONUndefined);
ASSERT_EQ(parseMaxTimeMS(maxTimeObj[query_request_helper::cmdOptionMaxTimeMS]).getStatus(),
ErrorCodes::BadValue);
}
TEST(QueryRequestTest, ParseMaxTimeMSEmptyObjectValueFails) {
const auto emptyObj = BSONObjBuilder().obj();
BSONObj maxTimeObj = BSON(query_request_helper::cmdOptionMaxTimeMS << emptyObj);
ASSERT_EQ(parseMaxTimeMS(maxTimeObj[query_request_helper::cmdOptionMaxTimeMS]).getStatus(),
ErrorCodes::BadValue);
}
TEST(QueryRequestTest, ParseMaxTimeMSNonIntegralValueFails) {
BSONObj maxTimeObj = BSON(query_request_helper::cmdOptionMaxTimeMS << 100.3);
ASSERT_EQ(parseMaxTimeMS(maxTimeObj[query_request_helper::cmdOptionMaxTimeMS]).getStatus(),
ErrorCodes::BadValue);
}
TEST(QueryRequestTest, ParseMaxTimeMSOutOfRangeDoubleFails) {
BSONObj maxTimeObj = BSON(query_request_helper::cmdOptionMaxTimeMS << 1e200);
ASSERT_EQ(parseMaxTimeMS(maxTimeObj[query_request_helper::cmdOptionMaxTimeMS]).getStatus(),
ErrorCodes::BadValue);
}
TEST(QueryRequestTest, ParseMaxTimeMSNegativeValueFails) {
BSONObj maxTimeObj = BSON(query_request_helper::cmdOptionMaxTimeMS << -400);
ASSERT_EQ(parseMaxTimeMS(maxTimeObj[query_request_helper::cmdOptionMaxTimeMS]).getStatus(),
ErrorCodes::BadValue);
}
TEST(QueryRequestTest, ParseMaxTimeMSZeroSucceeds) {
BSONObj maxTimeObj = BSON(query_request_helper::cmdOptionMaxTimeMS << 0);
ASSERT_EQ(parseMaxTimeMS(maxTimeObj[query_request_helper::cmdOptionMaxTimeMS]), 0);
}
TEST(QueryRequestTest, ParseMaxTimeMSEmptyElementSucceeds) {
const auto emptyElem = BSONElement();
ASSERT_EQ(parseMaxTimeMS(emptyElem), 0);
}
TEST(QueryRequestTest, ParseMaxTimeMSPositiveInRangeSucceeds) {
BSONObj maxTimeObj = BSON(query_request_helper::cmdOptionMaxTimeMS << 300);
ASSERT_EQ(parseMaxTimeMS(maxTimeObj[query_request_helper::cmdOptionMaxTimeMS]), 300);
}
TEST(QueryRequestTest, ConvertToAggregationSucceeds) {
FindCommandRequest findCommand(testns);
auto ar = query_request_conversion::asAggregateCommandRequest(findCommand);
ASSERT(!ar.getExplain());
ASSERT(!ar.getAllowDiskUse().has_value());
ASSERT(ar.getPipeline().empty());
ASSERT_EQ(ar.getCursor().getBatchSize().value_or(aggregation_request_helper::kDefaultBatchSize),
aggregation_request_helper::kDefaultBatchSize);
ASSERT_EQ(ar.getNamespace(), testns);
ASSERT_BSONOBJ_EQ(ar.getCollation().value_or(BSONObj()), BSONObj());
}
TEST(QueryRequestTest, ConvertToAggregationOmitsExplain) {
FindCommandRequest findCommand(testns);
auto ar = query_request_conversion::asAggregateCommandRequest(findCommand);
ASSERT_FALSE(ar.getExplain());
ASSERT(ar.getPipeline().empty());
ASSERT_EQ(ar.getNamespace(), testns);
ASSERT_BSONOBJ_EQ(ar.getCollation().value_or(BSONObj()), BSONObj());
}
TEST(QueryRequestTest, ConvertToAggregationWithHintSucceeds) {
FindCommandRequest findCommand(testns);
findCommand.setHint(fromjson("{a_1: -1}"));
auto ar = query_request_conversion::asAggregateCommandRequest(findCommand);
ASSERT_BSONOBJ_EQ(findCommand.getHint(), ar.getHint().value_or(BSONObj()));
}
TEST(QueryRequestTest, ConvertToAggregationWithMinFails) {
FindCommandRequest findCommand(testns);
findCommand.setMin(fromjson("{a: 1}"));
ASSERT_THROWS_CODE(query_request_conversion::asAggregateCommandRequest(findCommand),
DBException,
ErrorCodes::InvalidPipelineOperator);
}
TEST(QueryRequestTest, ConvertToAggregationWithMaxFails) {
FindCommandRequest findCommand(testns);
findCommand.setMax(fromjson("{a: 1}"));
ASSERT_THROWS_CODE(query_request_conversion::asAggregateCommandRequest(findCommand),
DBException,
ErrorCodes::InvalidPipelineOperator);
}
TEST(QueryRequestTest, ConvertToAggregationWithSingleBatchFieldFails) {
FindCommandRequest findCommand(testns);
findCommand.setSingleBatch(true);
ASSERT_THROWS_CODE(query_request_conversion::asAggregateCommandRequest(findCommand),
DBException,
ErrorCodes::InvalidPipelineOperator);
}
TEST(QueryRequestTest, ConvertToAggregationWithSingleBatchFieldAndLimitFails) {
FindCommandRequest findCommand(testns);
findCommand.setSingleBatch(true);
findCommand.setLimit(7);
ASSERT_THROWS_CODE(query_request_conversion::asAggregateCommandRequest(findCommand),
DBException,
ErrorCodes::InvalidPipelineOperator);
}
TEST(QueryRequestTest, ConvertToAggregationWithSingleBatchFieldLimitOneSucceeds) {
FindCommandRequest findCommand(testns);
findCommand.setSingleBatch(true);
findCommand.setLimit(1);
ASSERT_DOES_NOT_THROW(query_request_conversion::asAggregateCommandRequest(findCommand));
}
TEST(QueryRequestTest, ConvertToAggregationWithReturnKeyFails) {
FindCommandRequest findCommand(testns);
findCommand.setReturnKey(true);
ASSERT_THROWS_CODE(query_request_conversion::asAggregateCommandRequest(findCommand),
DBException,
ErrorCodes::InvalidPipelineOperator);
}
TEST(QueryRequestTest, ConvertToAggregationWithShowRecordIdFails) {
FindCommandRequest findCommand(testns);
findCommand.setShowRecordId(true);
ASSERT_THROWS_CODE(query_request_conversion::asAggregateCommandRequest(findCommand),
DBException,
ErrorCodes::InvalidPipelineOperator);
}
TEST(QueryRequestTest, ConvertToAggregationWithTailableFails) {
FindCommandRequest findCommand(testns);
query_request_helper::setTailableMode(TailableModeEnum::kTailable, &findCommand);
ASSERT_THROWS_CODE(query_request_conversion::asAggregateCommandRequest(findCommand),
DBException,
ErrorCodes::InvalidPipelineOperator);
}
TEST(QueryRequestTest, ConvertToAggregationWithNoCursorTimeoutFails) {
FindCommandRequest findCommand(testns);
findCommand.setNoCursorTimeout(true);
ASSERT_THROWS_CODE(query_request_conversion::asAggregateCommandRequest(findCommand),
DBException,
ErrorCodes::InvalidPipelineOperator);
}
TEST(QueryRequestTest, ConvertToAggregationWithAwaitDataFails) {
FindCommandRequest findCommand(testns);
query_request_helper::setTailableMode(TailableModeEnum::kTailableAndAwaitData, &findCommand);
ASSERT_THROWS_CODE(query_request_conversion::asAggregateCommandRequest(findCommand),
DBException,
ErrorCodes::InvalidPipelineOperator);
}
TEST(QueryRequestTest, ConvertToAggregationWithAllowPartialResultsFails) {
FindCommandRequest findCommand(testns);
findCommand.setAllowPartialResults(true);
ASSERT_THROWS_CODE(query_request_conversion::asAggregateCommandRequest(findCommand),
DBException,
ErrorCodes::InvalidPipelineOperator);
}
TEST(QueryRequestTest, ConvertToAggregationWithRequestResumeTokenSucceeds) {
RAIIServerParameterControllerForTest featureFlagController("featureFlagReshardingImprovements",
true);
FindCommandRequest findCommand(testns);
findCommand.setRequestResumeToken(true);
findCommand.setHint(BSON("$natural" << 1));
auto ar = query_request_conversion::asAggregateCommandRequest(findCommand);
ASSERT(ar.getRequestResumeToken());
}
TEST(QueryRequestTest, ConvertToAggregationWithResumeAfterFails) {
RAIIServerParameterControllerForTest featureFlagController("featureFlagReshardingImprovements",
false);
FindCommandRequest findCommand(testns);
BSONObj resumeAfter = BSON("$recordId" << 1LL);
findCommand.setResumeAfter(resumeAfter);
ASSERT_THROWS_CODE(query_request_conversion::asAggregateCommandRequest(findCommand),
DBException,
ErrorCodes::InvalidPipelineOperator);
}
TEST(QueryRequestTest, ConvertToAggregationWithPipeline) {
FindCommandRequest findCommand(testns);
findCommand.setFilter(BSON("x" << 1));
findCommand.setSort(BSON("y" << -1));
findCommand.setLimit(3);
findCommand.setSkip(7);
findCommand.setProjection(BSON("z" << 0));
auto ar = query_request_conversion::asAggregateCommandRequest(findCommand);
ASSERT(!ar.getExplain());
ASSERT_EQ(ar.getCursor().getBatchSize().value_or(aggregation_request_helper::kDefaultBatchSize),
aggregation_request_helper::kDefaultBatchSize);
ASSERT_EQ(ar.getNamespace(), testns);
ASSERT_BSONOBJ_EQ(ar.getCollation().value_or(BSONObj()), BSONObj());
std::vector<BSONObj> expectedPipeline{BSON("$match" << BSON("x" << 1)),
BSON("$sort" << BSON("y" << -1)),
BSON("$skip" << 7),
BSON("$limit" << 3),
BSON("$project" << BSON("z" << 0))};
ASSERT(std::equal(expectedPipeline.begin(),
expectedPipeline.end(),
ar.getPipeline().begin(),
SimpleBSONObjComparator::kInstance.makeEqualTo()));
}
TEST(QueryRequestTest, ConvertToAggregationWithBatchSize) {
FindCommandRequest findCommand(testns);
findCommand.setBatchSize(4);
auto ar = query_request_conversion::asAggregateCommandRequest(findCommand);
ASSERT(!ar.getExplain());
ASSERT_EQ(ar.getNamespace(), testns);
ASSERT_EQ(ar.getCursor().getBatchSize().value_or(aggregation_request_helper::kDefaultBatchSize),
4LL);
ASSERT_BSONOBJ_EQ(ar.getCollation().value_or(BSONObj()), BSONObj());
}
TEST(QueryRequestTest, ConvertToAggregationWithMaxTimeMS) {
FindCommandRequest findCommand(testns);
findCommand.setMaxTimeMS(9);
auto ar = query_request_conversion::asAggregateCommandRequest(findCommand);
ASSERT_TRUE(ar.getMaxTimeMS().has_value());
ASSERT_EQ(ar.getMaxTimeMS().get(), 9);
ASSERT(!ar.getExplain());
ASSERT_EQ(ar.getCursor().getBatchSize().value_or(aggregation_request_helper::kDefaultBatchSize),
aggregation_request_helper::kDefaultBatchSize);
ASSERT_EQ(ar.getNamespace(), testns);
ASSERT_BSONOBJ_EQ(ar.getCollation().value_or(BSONObj()), BSONObj());
}
TEST(QueryRequestTest, ConvertToAggregationWithCollationSucceeds) {
FindCommandRequest findCommand(testns);
findCommand.setCollation(BSON("f" << 1));
auto ar = query_request_conversion::asAggregateCommandRequest(findCommand);
ASSERT(!ar.getExplain());
ASSERT(ar.getPipeline().empty());
ASSERT_EQ(ar.getCursor().getBatchSize().value_or(aggregation_request_helper::kDefaultBatchSize),
aggregation_request_helper::kDefaultBatchSize);
ASSERT_EQ(ar.getNamespace(), testns);
ASSERT_BSONOBJ_EQ(ar.getCollation().value_or(BSONObj()), BSON("f" << 1));
}
TEST(QueryRequestTest, ConvertToAggregationWithReadOnceFails) {
FindCommandRequest findCommand(testns);
findCommand.setReadOnce(true);
ASSERT_THROWS_CODE(query_request_conversion::asAggregateCommandRequest(findCommand),
DBException,
ErrorCodes::InvalidPipelineOperator);
}
TEST(QueryRequestTest, ConvertToAggregationWithAllowSpeculativeMajorityReadFails) {
FindCommandRequest findCommand(testns);
findCommand.setAllowSpeculativeMajorityRead(true);
ASSERT_THROWS_CODE(query_request_conversion::asAggregateCommandRequest(findCommand),
DBException,
ErrorCodes::InvalidPipelineOperator);
}
TEST(QueryRequestTest, ConvertToAggregationWithLegacyRuntimeConstantsSucceeds) {
LegacyRuntimeConstants rtc{Date_t::now(), Timestamp(1, 1)};
FindCommandRequest findCommand(testns);
findCommand.setLegacyRuntimeConstants(rtc);
auto ar = query_request_conversion::asAggregateCommandRequest(findCommand);
ASSERT(ar.getLegacyRuntimeConstants().has_value());
ASSERT_EQ(ar.getLegacyRuntimeConstants()->getLocalNow(), rtc.getLocalNow());
ASSERT_EQ(ar.getLegacyRuntimeConstants()->getClusterTime(), rtc.getClusterTime());
}
TEST(QueryRequestTest, ConvertToAggregationWithAllowDiskUseTrueSucceeds) {
FindCommandRequest findCommand(testns);
findCommand.setAllowDiskUse(true);
auto ar = query_request_conversion::asAggregateCommandRequest(findCommand);
ASSERT(ar.getAllowDiskUse().has_value());
ASSERT_EQ(true, ar.getAllowDiskUse());
}
TEST(QueryRequestTest, ConvertToAggregationWithAllowDiskUseFalseSucceeds) {
FindCommandRequest findCommand(testns);
findCommand.setAllowDiskUse(false);
auto ar = query_request_conversion::asAggregateCommandRequest(findCommand);
ASSERT(ar.getAllowDiskUse().has_value());
ASSERT_EQ(false, ar.getAllowDiskUse());
}
TEST(QueryRequestTest, ConvertToAggregationWithIncludeQueryStatsMetricsTrueSucceeds) {
FindCommandRequest findCommand(testns);
findCommand.setIncludeQueryStatsMetrics(true);
auto ar = query_request_conversion::asAggregateCommandRequest(findCommand);
ASSERT_TRUE(ar.getIncludeQueryStatsMetrics());
}
TEST(QueryRequestTest, ConvertToAggregationWithIncludeQueryStatsMetricsFalseSucceeds) {
FindCommandRequest findCommand(testns);
findCommand.setIncludeQueryStatsMetrics(false);
auto ar = query_request_conversion::asAggregateCommandRequest(findCommand);
ASSERT_FALSE(ar.getIncludeQueryStatsMetrics());
}
TEST(QueryRequestTest, ConvertToFindWithAllowDiskUseTrueSucceeds) {
FindCommandRequest findCommand(testns);
findCommand.setAllowDiskUse(true);
const auto findCmd = findCommand.toBSON(BSONObj());
BSONElement elem = findCmd[FindCommandRequest::kAllowDiskUseFieldName];
ASSERT_EQ(true, elem.isBoolean());
ASSERT_EQ(true, elem.Bool());
}
TEST(QueryRequestTest, ConvertToFindWithAllowDiskUseFalseSucceeds) {
FindCommandRequest findCommand(testns);
findCommand.setAllowDiskUse(false);
const auto findCmd = findCommand.toBSON(BSONObj());
ASSERT_FALSE(findCmd[FindCommandRequest::kAllowDiskUseFieldName].booleanSafe());
}
TEST(QueryRequestHelperTest, ValidateResponseMissingFields) {
BSONObjBuilder builder;
QueryTestServiceContext serviceContext;
auto uniqueTxn = serviceContext.makeOperationContext();
OperationContext* opCtx = uniqueTxn.get();
ASSERT_THROWS_CODE(
query_request_helper::validateCursorResponse(builder.asTempObj(),
auth::ValidatedTenancyScope::get(opCtx),
boost::none,
SerializationContext()),
DBException,
6253507);
}
TEST(QueryRequestHelperTest, ValidateResponseWrongDataType) {
BSONObjBuilder builder;
builder.append("cursor", 1);
QueryTestServiceContext serviceContext;
auto uniqueTxn = serviceContext.makeOperationContext();
OperationContext* opCtx = uniqueTxn.get();
ASSERT_THROWS_CODE(
query_request_helper::validateCursorResponse(builder.asTempObj(),
auth::ValidatedTenancyScope::get(opCtx),
boost::none,
SerializationContext()),
DBException,
ErrorCodes::TypeMismatch);
}
TEST(QueryRequestHelperTest, ParsedCursorRemainsValidAfterBSONDestroyed) {
std::vector<BSONObj> batch = {BSON("_id" << 1), BSON("_id" << 2)};
CursorInitialReply cir;
{
BSONObj cursorObj =
BSON("cursor" << BSON("id" << CursorId(123) << "ns"
<< "testdb.testcoll"
<< "firstBatch"
<< BSON_ARRAY(BSON("_id" << 1) << BSON("_id" << 2))));
cir = CursorInitialReply::parseOwned(
IDLParserContext("QueryRequestHelperTest::ParsedCursorRemainsValidAFterBSONDestroyed"),
std::move(cursorObj));
cursorObj = BSONObj();
}
ASSERT_EQ(cir.getCursor()->getFirstBatch().size(), batch.size());
for (std::vector<BSONObj>::size_type i = 0; i < batch.size(); ++i) {
ASSERT_BSONOBJ_EQ(batch[i], cir.getCursor()->getFirstBatch()[i]);
}
}
class QueryRequestTest : public ServiceContextTest {};
TEST_F(QueryRequestTest, ParseFromUUID) {
const UUID uuid = UUID::gen();
NamespaceStringOrUUID nssOrUUID(DatabaseName::createDatabaseName_forTest(boost::none, "test"),
uuid);
FindCommandRequest findCommand(nssOrUUID);
const NamespaceString nss = NamespaceString::createNamespaceString_forTest("test.testns");
findCommand.setNss(nss);
ASSERT_EQ(nss, findCommand.getNamespaceOrUUID().nss());
}
} // namespace
} // namespace mongo