diff options
author | A. Jesse Jiryu Davis <jesse@mongodb.com> | 2020-11-13 22:19:37 -0500 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-11-19 01:26:57 +0000 |
commit | 73d1a6f368b04161dce7c0afbcea23efb52e2070 (patch) | |
tree | 122e4b1f12cf13cd48e7b1e5bc0da29a06030c96 | |
parent | dff0aa11f53e6fdf104167f36cc0ab89cdd8f6d2 (diff) | |
download | mongo-73d1a6f368b04161dce7c0afbcea23efb52e2070.tar.gz |
SERVER-52543 Define listCollections in IDL
-rw-r--r-- | buildscripts/idl/idl/ast.py | 2 | ||||
-rw-r--r-- | buildscripts/idl/idl/binder.py | 2 | ||||
-rw-r--r-- | buildscripts/idl/idl/errors.py | 2 | ||||
-rw-r--r-- | buildscripts/idl/idl/parser.py | 10 | ||||
-rw-r--r-- | buildscripts/idl/idl/syntax.py | 2 | ||||
-rw-r--r-- | buildscripts/idl/tests/test_parser.py | 94 | ||||
-rw-r--r-- | buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_jscore_passthrough.yml | 1 | ||||
-rw-r--r-- | buildscripts/resmokeconfig/suites/sharded_multi_stmt_txn_jscore_passthrough.yml | 1 | ||||
-rw-r--r-- | jstests/core/list_collections1.js | 36 | ||||
-rw-r--r-- | jstests/core/list_namespaces_invalidation.js | 4 | ||||
-rw-r--r-- | src/mongo/db/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/commands/list_collections.cpp | 69 | ||||
-rw-r--r-- | src/mongo/db/list_collections.idl | 113 | ||||
-rw-r--r-- | src/mongo/db/query/cursor_request.cpp | 30 | ||||
-rw-r--r-- | src/mongo/idl/basic_types.idl | 9 | ||||
-rw-r--r-- | src/mongo/util/uuid.h | 1 |
16 files changed, 265 insertions, 112 deletions
diff --git a/buildscripts/idl/idl/ast.py b/buildscripts/idl/idl/ast.py index e5d43e77067..1d0b97a1f82 100644 --- a/buildscripts/idl/idl/ast.py +++ b/buildscripts/idl/idl/ast.py @@ -169,6 +169,7 @@ class Field(common.SourceLocation): self.chained = False # type: bool self.comparison_order = -1 # type: int self.non_const_getter = False # type: bool + self.unstable = False # type: bool # Properties specific to fields which are types. self.cpp_type = None # type: str @@ -218,7 +219,6 @@ class Command(Struct): self.reply_type = None # type: Field self.api_version = "" # type: str self.is_deprecated = False # type: bool - self.unstable = False # type: bool super(Command, self).__init__(file_name, line, column) diff --git a/buildscripts/idl/idl/binder.py b/buildscripts/idl/idl/binder.py index e65b7f64dfb..2b62f6ece67 100644 --- a/buildscripts/idl/idl/binder.py +++ b/buildscripts/idl/idl/binder.py @@ -492,6 +492,7 @@ def _bind_command(ctxt, parsed_spec, command): """ ast_command = ast.Command(command.file_name, command.line, command.column) + ast_command.api_version = command.api_version # Inject special fields used for command parsing _inject_hidden_command_fields(command) @@ -693,6 +694,7 @@ def _bind_field(ctxt, parsed_spec, field): ast_field.constructed = field.constructed ast_field.comparison_order = field.comparison_order ast_field.non_const_getter = field.non_const_getter + ast_field.unstable = field.unstable ast_field.cpp_name = field.name if field.cpp_name: diff --git a/buildscripts/idl/idl/errors.py b/buildscripts/idl/idl/errors.py index 72172aacbca..e2c1fd264a9 100644 --- a/buildscripts/idl/idl/errors.py +++ b/buildscripts/idl/idl/errors.py @@ -831,7 +831,7 @@ class ParserContext(object): # pylint: disable=invalid-name self._add_error( location, ERROR_ID_UNSTABLE_NO_API_VERSION, - ("Command '%s' specifies 'unstable' but has no 'api_version" % (command_name, ))) + ("Command '%s' specifies 'unstable' but has no 'api_version'" % (command_name, ))) def add_missing_reply_type(self, location, command_name): # type: (common.SourceLocation, str) -> None diff --git a/buildscripts/idl/idl/parser.py b/buildscripts/idl/idl/parser.py index 0ad4b7766df..d319ab11427 100644 --- a/buildscripts/idl/idl/parser.py +++ b/buildscripts/idl/idl/parser.py @@ -315,6 +315,7 @@ def _parse_field(ctxt, name, node): "comparison_order": _RuleDesc("int_scalar"), "validator": _RuleDesc('mapping', mapping_parser_func=_parse_validator), "non_const_getter": _RuleDesc("bool_scalar"), + "unstable": _RuleDesc("bool_scalar"), }) return field @@ -631,7 +632,6 @@ def _parse_command(ctxt, spec, name, node): "reply_type": _RuleDesc('scalar'), "api_version": _RuleDesc('scalar'), "is_deprecated": _RuleDesc('bool_scalar'), - "unstable": _RuleDesc("bool_scalar"), "strict": _RuleDesc("bool_scalar"), "inline_chained_structs": _RuleDesc("bool_scalar"), "immutable": _RuleDesc('bool_scalar'), @@ -658,9 +658,6 @@ def _parse_command(ctxt, spec, name, node): if command.namespace != common.COMMAND_NAMESPACE_TYPE and command.type: ctxt.add_extranous_command_type(command, command.name) - if _has_field(node, "unstable") and not command.api_version: - ctxt.add_unstable_no_api_version(command, command.name) - if command.api_version and command.reply_type is None: ctxt.add_missing_reply_type(command, command.name) @@ -671,6 +668,11 @@ def _parse_command(ctxt, spec, name, node): if not command.fields: command.fields = [] + if not command.api_version: + for field in command.fields: + if field.unstable: + ctxt.add_unstable_no_api_version(field, command.name) + spec.symbols.add_command(ctxt, command) diff --git a/buildscripts/idl/idl/syntax.py b/buildscripts/idl/idl/syntax.py index 159e0b31800..a249bf235b7 100644 --- a/buildscripts/idl/idl/syntax.py +++ b/buildscripts/idl/idl/syntax.py @@ -348,6 +348,7 @@ class Field(common.SourceLocation): self.comparison_order = -1 # type: int self.validator = None # type: Validator self.non_const_getter = False # type: bool + self.unstable = False # type: bool # Internal fields - not generated by parser self.serialize_op_msg_request_only = False # type: bool @@ -441,7 +442,6 @@ class Command(Struct): self.reply_type = None # type: str self.api_version = "" # type: str self.is_deprecated = False # type: bool - self.unstable = False # type: bool super(Command, self).__init__(file_name, line, column) diff --git a/buildscripts/idl/tests/test_parser.py b/buildscripts/idl/tests/test_parser.py index b1d9f0f1826..1596cb6fd34 100644 --- a/buildscripts/idl/tests/test_parser.py +++ b/buildscripts/idl/tests/test_parser.py @@ -418,6 +418,7 @@ class TestParser(testcase.IDLTestcase): ignore: true cpp_name: bar comparison_order: 3 + unstable: true """)) # Test false bools @@ -432,6 +433,7 @@ class TestParser(testcase.IDLTestcase): type: string optional: false ignore: false + unstable: false """)) def test_field_negative(self): @@ -841,7 +843,6 @@ class TestParser(testcase.IDLTestcase): namespace: ignored api_version: 1 is_deprecated: true - unstable: true immutable: true inline_chained_structs: true generate_comparison_operators: true @@ -862,7 +863,6 @@ class TestParser(testcase.IDLTestcase): namespace: ignored api_version: 1 is_deprecated: false - unstable: false immutable: false inline_chained_structs: false generate_comparison_operators: false @@ -1075,60 +1075,6 @@ class TestParser(testcase.IDLTestcase): reply_type: foo_reply_struct """), idl.errors.ERROR_ID_API_VERSION_NO_STRICT) - # Cannot specify unstable with empty api_version - self.assert_parse_fail( - textwrap.dedent(""" - commands: - foo: - description: foo - command_name: foo - namespace: ignored - api_version: "" - unstable: true - fields: - foo: bar - reply_type: foo_reply_struct - """), idl.errors.ERROR_ID_UNSTABLE_NO_API_VERSION) - - self.assert_parse_fail( - textwrap.dedent(""" - commands: - foo: - description: foo - command_name: foo - namespace: ignored - api_version: "" - unstable: false - fields: - foo: bar - reply_type: foo_reply_struct - """), idl.errors.ERROR_ID_UNSTABLE_NO_API_VERSION) - - # Cannot specify unstable without an api_version - self.assert_parse_fail( - textwrap.dedent(""" - commands: - foo: - description: foo - command_name: foo - namespace: ignored - unstable: true - fields: - foo: bar - """), idl.errors.ERROR_ID_UNSTABLE_NO_API_VERSION) - - self.assert_parse_fail( - textwrap.dedent(""" - commands: - foo: - description: foo - command_name: foo - namespace: ignored - unstable: false - fields: - foo: bar - """), idl.errors.ERROR_ID_UNSTABLE_NO_API_VERSION) - # Must specify reply_type if api_version is non-empty self.assert_parse_fail( textwrap.dedent(""" @@ -1356,6 +1302,42 @@ class TestParser(testcase.IDLTestcase): foo: bar """), idl.errors.ERROR_ID_MISSING_REQUIRED_FIELD) + def test_unstable_positive(self): + # type: () -> None + """Positive unstable-field test cases.""" + for unstable in ("true", "false"): + self.assert_parse( + textwrap.dedent(f""" + commands: + foo: + description: foo + command_name: foo + namespace: ignored + api_version: "1" + fields: + foo: + type: bar + unstable: {unstable} + reply_type: foo_reply_struct + """)) + + def test_unstable_negative(self): + # type: () -> None + """Negative unstable-field test cases.""" + self.assert_parse_fail( + textwrap.dedent(f""" + commands: + foo: + description: foo + command_name: foo + namespace: ignored + fields: + foo: + type: bar + unstable: true + reply_type: foo_reply_struct + """), idl.errors.ERROR_ID_UNSTABLE_NO_API_VERSION) + def test_scalar_or_mapping_negative(self): # type: () -> None """Negative test for scalar_or_mapping type.""" diff --git a/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_jscore_passthrough.yml index 92535b6a866..f0563fb5aba 100644 --- a/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_jscore_passthrough.yml @@ -203,6 +203,7 @@ selector: - jstests/core/kill_cursors.js - jstests/core/list_collections1.js - jstests/core/list_indexes.js + - jstests/core/list_namespaces_invalidation.js - jstests/core/oro.js # Parallel Shell - we do not signal the override to end a txn when a parallel shell closes. diff --git a/buildscripts/resmokeconfig/suites/sharded_multi_stmt_txn_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/sharded_multi_stmt_txn_jscore_passthrough.yml index a460c9c269f..66ad99b80f5 100644 --- a/buildscripts/resmokeconfig/suites/sharded_multi_stmt_txn_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/sharded_multi_stmt_txn_jscore_passthrough.yml @@ -225,6 +225,7 @@ selector: - jstests/core/kill_cursors.js - jstests/core/list_collections1.js - jstests/core/list_indexes.js + - jstests/core/list_namespaces_invalidation.js - jstests/core/oro.js # Parallel Shell - we do not signal the override to end a txn when a parallel shell closes. diff --git a/jstests/core/list_collections1.js b/jstests/core/list_collections1.js index 39445a3ff91..700407c0843 100644 --- a/jstests/core/list_collections1.js +++ b/jstests/core/list_collections1.js @@ -243,6 +243,42 @@ assert(cursor.hasNext()); assert.eq(1, cursor.objsLeftInBatch()); // +// Test that batches are limited to ~16 MB +// + +assert.commandWorked(mydb.dropDatabase()); +const validator = { + $jsonSchema: { + bsonType: "object", + properties: { + stringWith4mbDescription: + {bsonType: "string", description: "x".repeat(3 * 1024 * 1024)}, + + } + } +}; + +// Each collection's info is about 3 MB; 4 collections fit in the first batch and 2 in the second. +const nCollections = 6; +jsTestLog(`Creating ${nCollections} collections with huge validator objects....`); +for (let i = 0; i < nCollections; i++) { + assert.commandWorked(mydb.createCollection("collection_" + i, {validator: validator})); +} +jsTestLog(`Done creating ${nCollections} collections`); +cursor = getListCollectionsCursor(); +assert(cursor.hasNext()); +const firstBatchSize = cursor.objsLeftInBatch(); +assert.gt(firstBatchSize, 0); +assert.lt(firstBatchSize, nCollections); +// Exhaust the first batch.. +while (cursor.objsLeftInBatch()) { + cursor.next(); +} +assert(cursor.hasNext()); +cursor.next(); +assert.eq(firstBatchSize + cursor.objsLeftInBatch() + 1, nCollections); + +// // Test on non-existent database. // diff --git a/jstests/core/list_namespaces_invalidation.js b/jstests/core/list_namespaces_invalidation.js index 4bfbdffd4e6..8e94c1e6ded 100644 --- a/jstests/core/list_namespaces_invalidation.js +++ b/jstests/core/list_namespaces_invalidation.js @@ -1,4 +1,4 @@ -// @tags: [requires_non_retryable_commands, requires_fastcount] +// @tags: [requires_non_retryable_commands, requires_fastcount, requires_getmore] (function() { 'use strict'; let dbInvalidName = 'system_namespaces_invalidations'; @@ -17,7 +17,7 @@ function testNamespaceInvalidation(namespaceAction, batchSize) { // Get the first two namespaces using listCollections. let cmd = {listCollections: dbInvalidName}; - Object.extend(cmd, {batchSize: batchSize}); + Object.extend(cmd, {cursor: {batchSize: batchSize}}); let res = dbInvalid.runCommand(cmd); assert.commandWorked(res, 'could not run ' + tojson(cmd)); printjson(res); diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript index a12cb0f9b76..97bc4e7bf1f 100644 --- a/src/mongo/db/SConscript +++ b/src/mongo/db/SConscript @@ -570,6 +570,7 @@ env.Library( source=[ 'commands.cpp', 'drop_database.idl', + 'list_collections.idl', ], LIBDEPS=[ '$BUILD_DIR/mongo/base', diff --git a/src/mongo/db/commands/list_collections.cpp b/src/mongo/db/commands/list_collections.cpp index 92e42f5f689..3310166501c 100644 --- a/src/mongo/db/commands/list_collections.cpp +++ b/src/mongo/db/commands/list_collections.cpp @@ -27,6 +27,8 @@ * it in the license file. */ +#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kCommand + #include "mongo/platform/basic.h" #include <memory> @@ -52,6 +54,7 @@ #include "mongo/db/exec/queued_data_stage.h" #include "mongo/db/exec/working_set.h" #include "mongo/db/index/index_descriptor.h" +#include "mongo/db/list_collections_gen.h" #include "mongo/db/query/cursor_request.h" #include "mongo/db/query/cursor_response.h" #include "mongo/db/query/find_common.h" @@ -61,6 +64,7 @@ #include "mongo/db/storage/storage_engine.h" #include "mongo/db/storage/storage_options.h" #include "mongo/db/views/view_catalog.h" +#include "mongo/logv2/log.h" namespace mongo { @@ -210,6 +214,15 @@ BSONObj buildCollectionBson(OperationContext* opCtx, return b.obj(); } +void appendListCollectionsCursorReply(CursorId cursorId, + const NamespaceString& cursorNss, + std::vector<mongo::ListCollectionsReplyItem>&& firstBatch, + BSONObjBuilder& result) { + auto reply = ListCollectionsReply( + ListCollectionsReplyCursor(cursorId, cursorNss, std::move(firstBatch))); + reply.serialize(&result); +} + class CmdListCollections : public BasicCommand { public: const std::set<std::string>& apiVersions() const { @@ -260,29 +273,18 @@ public: unique_ptr<MatchExpression> matcher; const auto as = AuthorizationSession::get(opCtx->getClient()); - const bool nameOnly = jsobj["nameOnly"].trueValue(); - const bool authorizedCollections = jsobj["authorizedCollections"].trueValue(); + auto parsed = ListCollections::parse(IDLParserErrorContext("listCollections"), jsobj); + const bool nameOnly = parsed.getNameOnly(); + const bool authorizedCollections = parsed.getAuthorizedCollections(); // The collator is null because collection objects are compared using binary comparison. auto expCtx = make_intrusive<ExpressionContext>( opCtx, std::unique_ptr<CollatorInterface>(nullptr), NamespaceString(dbname)); - // Check for 'filter' argument. - BSONElement filterElt = jsobj["filter"]; - if (!filterElt.eoo()) { - if (filterElt.type() != mongo::Object) { - uasserted(ErrorCodes::BadValue, "\"filter\" must be an object"); - } - - matcher = uassertStatusOK(MatchExpressionParser::parse(filterElt.Obj(), expCtx)); + if (parsed.getFilter()) { + matcher = uassertStatusOK(MatchExpressionParser::parse(*parsed.getFilter(), expCtx)); } - const long long defaultBatchSize = std::numeric_limits<long long>::max(); - long long batchSize; - Status parseCursorStatus = - CursorRequest::parseCommandCursorOptions(jsobj, defaultBatchSize, &batchSize); - uassertStatusOK(parseCursorStatus); - // Check for 'includePendingDrops' flag. The default is to not include drop-pending // collections. bool includePendingDrops; @@ -292,7 +294,7 @@ public: const NamespaceString cursorNss = NamespaceString::makeListCollectionsNSS(dbname); std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> exec; - BSONArrayBuilder firstBatch; + std::vector<mongo::ListCollectionsReplyItem> firstBatch; { AutoGetDb autoDb(opCtx, dbname, MODE_IS); Database* db = autoDb.getDb(); @@ -349,9 +351,10 @@ public: } // Skipping views is only necessary for internal cloning operations. - bool skipViews = filterElt.type() == mongo::Object && + bool skipViews = parsed.getFilter() && SimpleBSONObjComparator::kInstance.evaluate( - filterElt.Obj() == ListCollectionsFilter::makeTypeCollectionFilter()); + *parsed.getFilter() == ListCollectionsFilter::makeTypeCollectionFilter()); + if (!skipViews) { ViewCatalog::get(db)->iterate(opCtx, [&](const ViewDefinition& view) { if (authorizedCollections && @@ -377,6 +380,12 @@ public: PlanYieldPolicy::YieldPolicy::NO_YIELD, cursorNss)); + long long batchSize = std::numeric_limits<long long>::max(); + if (parsed.getCursor() && parsed.getCursor()->getBatchSize()) { + batchSize = *parsed.getCursor()->getBatchSize(); + } + + int bytesBuffered = 0; for (long long objCount = 0; objCount < batchSize; objCount++) { BSONObj nextDoc; PlanExecutor::ExecState state = exec->getNext(&nextDoc, nullptr); @@ -386,15 +395,26 @@ public: invariant(state == PlanExecutor::ADVANCED); // If we can't fit this result inside the current batch, then we stash it for later. - if (!FindCommon::haveSpaceForNext(nextDoc, objCount, firstBatch.len())) { + if (!FindCommon::haveSpaceForNext(nextDoc, objCount, bytesBuffered)) { exec->enqueue(nextDoc); break; } - firstBatch.append(nextDoc); + try { + firstBatch.push_back(ListCollectionsReplyItem::parse( + IDLParserErrorContext("ListCollectionsReplyItem"), nextDoc)); + } catch (const DBException& exc) { + LOGV2_ERROR(5254300, + "Could not parse catalog entry while replying to listCollections", + "entry"_attr = nextDoc, + "error"_attr = exc); + fassertFailed(5254301); + } + bytesBuffered += nextDoc.objsize(); } if (exec->isEOF()) { - appendCursorResponseObject(0LL, cursorNss.ns(), firstBatch.arr(), &result); + appendListCollectionsCursorReply( + 0 /* cursorId */, cursorNss, std::move(firstBatch), result); return true; } exec->saveState(); @@ -413,9 +433,8 @@ public: uassertStatusOK(AuthorizationSession::get(opCtx->getClient()) ->checkAuthorizedToListCollections(dbname, jsobj))}); - appendCursorResponseObject( - pinnedCursor.getCursor()->cursorid(), cursorNss.ns(), firstBatch.arr(), &result); - + appendListCollectionsCursorReply( + pinnedCursor.getCursor()->cursorid(), cursorNss, std::move(firstBatch), result); return true; } } cmdListCollections; diff --git a/src/mongo/db/list_collections.idl b/src/mongo/db/list_collections.idl new file mode 100644 index 00000000000..d7e88709296 --- /dev/null +++ b/src/mongo/db/list_collections.idl @@ -0,0 +1,113 @@ +# Copyright (C) 2020-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. +# + +global: + cpp_namespace: "mongo" + cpp_includes: + - "mongo/util/uuid.h" + +imports: + - "mongo/idl/basic_types.idl" + +structs: + ListCollectionsReplyInfo: + description: "Information about the collection." + fields: + readOnly: + type: bool + description: "If true, the data store is read only." + optional: true + uuid: + type: uuid + description: "Unique, immutable collection ID." + optional: true + + ListCollectionsReplyItem: + description: "Individual result" + fields: + name: + type: string + description: "Name of the collection." + type: + type: string + description: "Type of data store." + options: + type: object_owned + description: "Collection options." + optional: true + info: + type: ListCollectionsReplyInfo + description: "Information about the collection." + optional: true + idIndex: + type: object_owned + description: "Provides information on the _id index for the collection." + optional: true + + ListCollectionsReplyCursor: + description: "Cursor object" + fields: + id: long + ns: namespacestring + firstBatch: array<ListCollectionsReplyItem> + + ListCollectionsReply: + description: "The listCollection command's reply." + fields: + cursor: ListCollectionsReplyCursor + +commands: + listCollections: + description: "Parser for the listCollections command" + command_name: listCollections + namespace: ignored + cpp_name: listCollections + strict: true + api_version: "1" + fields: + cursor: + type: SimpleCursorOptions + optional: true + filter: + type: object + optional: true + nameOnly: + type: bool + default: false + authorizedCollections: + type: bool + default: false + includePendingDrops: + type: safeBool + unstable: true + default: false + comment: + type: IDLAnyType + description: "A user-provided comment to attach to this command." + optional: true + reply_type: ListCollectionsReply diff --git a/src/mongo/db/query/cursor_request.cpp b/src/mongo/db/query/cursor_request.cpp index ada35f8dc7c..700168f1367 100644 --- a/src/mongo/db/query/cursor_request.cpp +++ b/src/mongo/db/query/cursor_request.cpp @@ -33,12 +33,13 @@ #include "mongo/bson/bsonelement.h" #include "mongo/bson/bsonobj.h" +#include "mongo/idl/basic_types_gen.h" namespace mongo { Status CursorRequest::parseCommandCursorOptions(const BSONObj& cmdObj, long long defaultBatchSize, - long long* batchSize) { + long long* batchSize) try { invariant(batchSize); *batchSize = defaultBatchSize; @@ -52,30 +53,15 @@ Status CursorRequest::parseCommandCursorOptions(const BSONObj& cmdObj, } BSONObj cursor = cursorElem.embeddedObject(); - BSONElement batchSizeElem = cursor["batchSize"]; - - const int expectedNumberOfCursorFields = batchSizeElem.eoo() ? 0 : 1; - if (cursor.nFields() != expectedNumberOfCursorFields) { - return Status(ErrorCodes::BadValue, - "cursor object can't contain fields other than batchSize"); + auto options = + SimpleCursorOptions::parse(IDLParserErrorContext("parseCommandCursorOptions"), cursor); + if (options.getBatchSize()) { + *batchSize = *options.getBatchSize(); } - if (batchSizeElem.eoo()) { - return Status::OK(); - } - - if (!batchSizeElem.isNumber()) { - return Status(ErrorCodes::TypeMismatch, "cursor.batchSize must be a number"); - } - - // This can change in the future, but for now all negatives are reserved. - if (batchSizeElem.numberLong() < 0) { - return Status(ErrorCodes::BadValue, "cursor.batchSize must not be negative"); - } - - *batchSize = batchSizeElem.numberLong(); - return Status::OK(); +} catch (const DBException& exc) { + return exc.toStatus(); } } // namespace mongo diff --git a/src/mongo/idl/basic_types.idl b/src/mongo/idl/basic_types.idl index 27f7a366b51..2b5a4719fec 100644 --- a/src/mongo/idl/basic_types.idl +++ b/src/mongo/idl/basic_types.idl @@ -221,3 +221,12 @@ structs: errorLabels: type: array<string> optional: true + + SimpleCursorOptions: + description: "Parser for cursor options, for commands with minimal cursor support" + strict: true + fields: + batchSize: + type: safeInt64 + optional: true + validator: { gte: 0 } diff --git a/src/mongo/util/uuid.h b/src/mongo/util/uuid.h index 59c43ec5fb2..a095dde9428 100644 --- a/src/mongo/util/uuid.h +++ b/src/mongo/util/uuid.h @@ -76,6 +76,7 @@ class UUID { friend class idl::import::One_UUID; friend class IndexBuildEntry; friend class KeyStoreRecord; + friend class ListCollectionsReplyInfo; friend class LogicalSessionId; friend class LogicalSessionToClient; friend class LogicalSessionIdToClient; |