summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorA. Jesse Jiryu Davis <jesse@mongodb.com>2020-11-13 22:19:37 -0500
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-11-19 01:26:57 +0000
commit73d1a6f368b04161dce7c0afbcea23efb52e2070 (patch)
tree122e4b1f12cf13cd48e7b1e5bc0da29a06030c96
parentdff0aa11f53e6fdf104167f36cc0ab89cdd8f6d2 (diff)
downloadmongo-73d1a6f368b04161dce7c0afbcea23efb52e2070.tar.gz
SERVER-52543 Define listCollections in IDL
-rw-r--r--buildscripts/idl/idl/ast.py2
-rw-r--r--buildscripts/idl/idl/binder.py2
-rw-r--r--buildscripts/idl/idl/errors.py2
-rw-r--r--buildscripts/idl/idl/parser.py10
-rw-r--r--buildscripts/idl/idl/syntax.py2
-rw-r--r--buildscripts/idl/tests/test_parser.py94
-rw-r--r--buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_jscore_passthrough.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/sharded_multi_stmt_txn_jscore_passthrough.yml1
-rw-r--r--jstests/core/list_collections1.js36
-rw-r--r--jstests/core/list_namespaces_invalidation.js4
-rw-r--r--src/mongo/db/SConscript1
-rw-r--r--src/mongo/db/commands/list_collections.cpp69
-rw-r--r--src/mongo/db/list_collections.idl113
-rw-r--r--src/mongo/db/query/cursor_request.cpp30
-rw-r--r--src/mongo/idl/basic_types.idl9
-rw-r--r--src/mongo/util/uuid.h1
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;