Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DRIVERS-2533: add RunCursorCommand API #1412

Merged
merged 82 commits into from May 23, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
2ba230e
DRIVERS-2533: add RunCursorCommand API
nbbeeken Apr 24, 2023
8d46217
feat: add cleanup req
nbbeeken Apr 25, 2023
b688572
feat: add runCursorCommand UTF operation
nbbeeken Apr 25, 2023
eff1d85
chore: changelog
nbbeeken Apr 25, 2023
be59aa8
feat: add UTF operations and first test
nbbeeken Apr 25, 2023
155677b
feat: lb mode, and more csot
nbbeeken Apr 26, 2023
c437e53
docs: add tailable section
nbbeeken Apr 26, 2023
03779a1
feat: add clusterTime gossip
nbbeeken Apr 27, 2023
85e032d
apply suggestions
nbbeeken Apr 28, 2023
508990d
Add links to specs, monospacing, high level steps cleanup
nbbeeken Apr 28, 2023
3420d3e
Add improvements to Find, getMore and killCursors commands
nbbeeken Apr 28, 2023
3dfcd77
comments
nbbeeken May 1, 2023
679e14a
add invalid tests
nbbeeken May 1, 2023
0aab062
Merge branch 'master' into DRIVERS-2533-runCursorCommand-api
nbbeeken May 1, 2023
6745d71
rename invalid test file
nbbeeken May 1, 2023
1db918b
add clusterTime assertion
nbbeeken May 1, 2023
4890269
add valid pass
nbbeeken May 1, 2023
b39df09
add more test cases
nbbeeken May 1, 2023
31eebfc
fix server version
nbbeeken May 2, 2023
eb7e34a
add tailable cursor test
nbbeeken May 3, 2023
90cb9f0
fix schema issues
nbbeeken May 3, 2023
f159ebd
gen json
nbbeeken May 3, 2023
dc73ff8
fix: title
nbbeeken May 4, 2023
f623935
fix: title
nbbeeken May 4, 2023
576d914
fix: title
nbbeeken May 4, 2023
fa95fc2
gen: json
nbbeeken May 4, 2023
6f0ca16
test for errors from non-cursor responses
nbbeeken May 4, 2023
b7962b7
test: breakout clusterTime to its own test
nbbeeken May 4, 2023
b9e6fdb
update optionality language
nbbeeken May 4, 2023
cab4837
update unified test format test titles
nbbeeken May 4, 2023
a724120
Merge branch 'master' into DRIVERS-2533-runCursorCommand-api
nbbeeken May 9, 2023
052f399
test: add misconfiguration tests
nbbeeken May 9, 2023
eff3eef
Merge branch 'master' into DRIVERS-2533-runCursorCommand-api
nbbeeken May 10, 2023
386ef6b
find_getmore_killcursors comments
nbbeeken May 10, 2023
3393849
run-command comments
nbbeeken May 10, 2023
aadfd9d
remove extra assertions for runCommand.yml
nbbeeken May 10, 2023
661060a
reduce lsid strictness
nbbeeken May 10, 2023
2d206a5
rm empty filter
nbbeeken May 10, 2023
34de634
fix getMore value bitwidth
nbbeeken May 10, 2023
0994612
add interface for getMore controls
nbbeeken May 10, 2023
a0ca7bd
ignore result
nbbeeken May 10, 2023
d522f27
ignore result - correctly
nbbeeken May 10, 2023
850f26d
add comment to find
nbbeeken May 10, 2023
8f13d54
move CSOT, remove invalid test
nbbeeken May 10, 2023
66c7199
getmore and lsid, name fix
nbbeeken May 10, 2023
3117133
killCursors array type
nbbeeken May 10, 2023
4a7958e
fix getMore settings, pos int mention
nbbeeken May 10, 2023
330aead
entity and cursor operations
nbbeeken May 10, 2023
f30d110
must->should
nbbeeken May 10, 2023
7d172b1
changelogs
nbbeeken May 10, 2023
80fd8f7
fix rst
nbbeeken May 10, 2023
55e5cd8
Merge branch 'master' into DRIVERS-2533-runCursorCommand-api
nbbeeken May 11, 2023
2fe6fa8
add option to configure getMore fields and mention of testing
nbbeeken May 11, 2023
86a5ee8
remove check for result
nbbeeken May 11, 2023
699328f
Add a comment about ignoring extra events
nbbeeken May 11, 2023
7391ea2
non-negative
nbbeeken May 11, 2023
933c237
update numeric types
nbbeeken May 11, 2023
ce7631d
suggestions
nbbeeken May 11, 2023
259583c
Merge branch 'DRIVERS-2533-runCursorCommand-api' of github.com:mongod…
nbbeeken May 11, 2023
f0497fe
rm R/W concern sentence, update RP
nbbeeken May 11, 2023
0876609
Merge branch 'master' into DRIVERS-2533-runCursorCommand-api
jmikola May 12, 2023
09195d5
test: add more CSOT tests
nbbeeken May 16, 2023
b113dde
Merge branch 'DRIVERS-2533-runCursorCommand-api' of github.com:mongod…
nbbeeken May 16, 2023
e055387
test: add tailable awaitdata case
nbbeeken May 16, 2023
e612efe
comments
nbbeeken May 17, 2023
dc500f5
fix operation name
nbbeeken May 17, 2023
7dd20be
fix format
nbbeeken May 17, 2023
73703b3
fix: db
nbbeeken May 18, 2023
4e50497
docs: cursorType
nbbeeken May 18, 2023
5b4651d
fix: clusterTime test
nbbeeken May 18, 2023
8a87576
comments
nbbeeken May 18, 2023
90f12a6
fix: add documents to insert
nbbeeken May 19, 2023
0adbd22
fmt
nbbeeken May 19, 2023
c50a20d
fix LB test
nbbeeken May 19, 2023
d4e60c0
descriptive comments
nbbeeken May 19, 2023
299705d
LB fix session
nbbeeken May 19, 2023
2ea4e3d
remove inequality tests
nbbeeken May 22, 2023
d3f73c7
add serverless forbid to capped collection test
nbbeeken May 22, 2023
f498f9a
forbid serverless for csot capped collection tests
nbbeeken May 22, 2023
5c8d733
flaky timeouts on serverless
nbbeeken May 22, 2023
942549c
update timeouts
nbbeeken May 23, 2023
0a42153
fix: comments and timeout value
nbbeeken May 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
159 changes: 159 additions & 0 deletions source/run-command/run-command.rst
Expand Up @@ -44,6 +44,8 @@ Deviations

Please refer to `The CRUD specification's Guidance <https://github.com/mongodb/specifications/blob/master/source/crud/crud.rst#guidance>`_ on how APIs may deviate between languages.

Cursor iterating APIs MAY be offered via language syntax or predefined iterable methods.

--------------
``runCommand``
--------------
Expand Down Expand Up @@ -129,6 +131,10 @@ The logical session ID MUST be included under ``lsid`` in the command sent to th

* See Driver Sessions' section on `Sending the session ID to the server on all commands <https://github.com/mongodb/specifications/blob/master/source/sessions/driver-sessions.rst#sending-the-session-id-to-the-server-on-all-commands>`_

The command sent to the server MUST gossip the ``$clusterTime`` if cluster time support is detected.

* See Driver Sessions' section on `Gossipping the cluster time <https://github.com/mongodb/specifications/blob/master/source/sessions/driver-sessions.rst#gossipping-the-cluster-time>`_
nbbeeken marked this conversation as resolved.
Show resolved Hide resolved

Transactions
""""""""""""

Expand Down Expand Up @@ -187,7 +193,160 @@ Drivers MUST document the behavior of RunCommand if a ``maxTimeMS`` field is al
* See Client Side Operations Timeout's section on `runCommand <https://github.com/mongodb/specifications/blob/master/source/client-side-operations-timeout/client-side-operations-timeout.rst#runcommand>`_
* See Client Side Operations Timeout's section on `runCommand behavior <https://github.com/mongodb/specifications/blob/master/source/client-side-operations-timeout/client-side-operations-timeout.rst#runcommand-behavior>`_


--------------------
``runCursorCommand``
--------------------

The following represents how a runCursorCommand API may be exposed.
alcaeus marked this conversation as resolved.
Show resolved Hide resolved

.. code:: typescript

interface Database {
/**
* Takes an argument representing an arbitrary BSON document and executes it against the server.
*/
runCursorCommand(command: BSONDocument, options: RunCursorCommandOptions): RunCommandCursor;
}

interface RunCursorCommandOptions extends RunCommandOptions {
/**
* For operations that create cursors, timeoutMS can either cap the lifetime of the cursor or be applied separately to the original operation and all subsequent calls.
* To support both of these use cases, these operations MUST support a timeoutMode option.
*
* @defaultValue 'CURSOR_LIFETIME'
*
* @see https://github.com/mongodb/specifications/blob/master/source/client-side-operations-timeout/client-side-operations-timeout.rst
*/
timeoutMode?: 'ITERATION' | 'CURSOR_LIFETIME';
dariakp marked this conversation as resolved.
Show resolved Hide resolved

/**
* Identifies the type of cursor this is.
*
* A tailable cursor can receive empty `nextBatch` arrays in `getMore` responses.
* However, subsequent `getMore` operations may return documents if new data has become available.
*
* A tailableAwait cursor is an enhancement where instead of dealing with empty responses the server will block until data becomes available.
*
* @defaultValue NON_TAILABLE
*/
cursorType: NON_TAILABLE | TAILABLE | TAILABLE_AWAIT;
dariakp marked this conversation as resolved.
Show resolved Hide resolved
}

RunCursorCommand implementation details
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

RunCursorCommand provides a way to access MongoDB server commands that return a cursor directly without requiring a driver to implement a bespoke cursor implementation.
The API is intended to be built upon RunCommand and take a document from a user and apply a number of common driver internal concerns before forwarding the command to a server.
A driver can expect that the result from running this command will return a document with a ``cursor`` field and MUST provide the caller with a language native abstraction to continue iterating the results from the server.
dariakp marked this conversation as resolved.
Show resolved Hide resolved

High level RunCursorCommand steps:

* Run the cursor creating command provided by the caller and retain the ClientSession used as well as the server the command was executed on.
* Create a local Cursor instance and store the ``firstBatch``, ``ns``, and ``id`` from the response.
* When the current batch has been fully iterated, using the same server the initial command was executed on execute a ``getMore``
dariakp marked this conversation as resolved.
Show resolved Hide resolved
* Store the ``nextBatch`` from the ``getMore`` response and update the cursor's ``id``
* Continue executing ``getMore`` commands until the cursor is exhausted
dariakp marked this conversation as resolved.
Show resolved Hide resolved

Driver Sessions
jmikola marked this conversation as resolved.
Show resolved Hide resolved
"""""""""""""""

A driver MUST create an implicit ClientSession if none is provided and it MUST be attached for the duration of the cursor's lifetime.
All ``getMore`` commands constructed for this cursor MUST send the same ``lsid`` used on the initial command.
A cursor is exhausted when the server reports a cursor is equal to zero.
jmikola marked this conversation as resolved.
Show resolved Hide resolved
When the cursor is exhausted the client session MUST be ended and the server session returned to the pool as early as possible rather than waiting for a caller to completely iterate the final batch.

* See Drivers Sessions' section on `Sessions and Cursors <https://github.com/mongodb/specifications/blob/master/source/sessions/driver-sessions.rst#sessions-and-cursors>`_

Server Selection
""""""""""""""""

RunCursorCommand MUST support a ``readPreference`` option that MUST be used to determine server selection.
The selected server MUST be used for subsequent ``getMore`` commands.

Load Balancers
""""""""""""""

When in ``loadBalanced`` mode, a driver MUST pin the connection used to execute the initial operation, and reuse it for subsequent ``getMore`` operations.

* See Load Balancer's section on `Behaviour With Cursors <https://github.com/mongodb/specifications/blob/master/source/load-balancers/load-balancers.rst#behaviour-with-cursors>`_

Iterating the Cursor
""""""""""""""""""""

Drivers MUST provide an API, typically, a method named ``next()``, that returns one document per invocation.
If the cursor's batch is empty and the cursor id is nonzero, the driver MUST perform a ``getMore`` operation.

Executing GetMores
jmikola marked this conversation as resolved.
Show resolved Hide resolved
""""""""""""""""""
jmikola marked this conversation as resolved.
Show resolved Hide resolved

The cursor API returned to the caller MUST offer a way to configure ``batchSize``, ``maxTimeMS``, and ``comment`` that are sent on the ``getMore`` command.

.. code:: typescript

interface GetMore {
/** Set to the nonzero cursor id */
getMore: int64;
/** Set to the namespace returned by the initial command response */
collection: string;
/**
* User configurable document count for the batch returned for this getMore.
* Only attached to command document if nonzero.
*/
batchSize?: int32;
/**
* User configurable time limit enforced by the server.
*/
maxTimeMS?: int32;
/**
* User configurable comment that can be used to identify the operation in logs.
* This can be any BSON value.
*/
comment?: BSONValue;
}

The driver's cursor MUST update its ``id`` and ``ns``, as well as store the ``nextBatch`` from every ``getMore`` response.

* See Find, getMore and killCursors commands' section on `GetMore <https://github.com/mongodb/specifications/blob/master/source/find_getmore_killcursors_commands.rst#getmore>`_

Tailable and TailableAwait
jmikola marked this conversation as resolved.
Show resolved Hide resolved
""""""""""""""""""""""""""

By default most cursors are non-tailable, for example, a ``find`` that exhausts when all results for the filter have been satisfied.
MongoDB also supports creating cursors that "tail" or follow the target namespace for new data.
Querying capped collections and change streams are some examples of tailable cursor use cases.
A tailable cursor can receive ``getMore`` responses with an empty ``nextBatch`` array, this does not indicate that the cursor has been exhausted.

In addition to considering a cursor tailable, an ``awaitData`` flag may be sent on the initial command.
This will request that the server block responding to the ``getMore`` immediately and instead rely on the ``maxTimeMS`` field of the ``getMore`` (or server default).
If the time does expire an empty batch will be returned and the driver MUST issue another ``getMore``.
jmikola marked this conversation as resolved.
Show resolved Hide resolved

It is up to the user to construct their initial command with ``awaitData`` and ``tailable`` flags as well as inform RunCursorCommand of the ``cursorType`` that should be constructed.
Requesting a ``cursorType`` that does not align with the fields sent to the server on the initial command SHOULD be documented as undefined behavior.

Resource Cleanup
""""""""""""""""

Drivers MUST provide an explicit mechanism for releasing the cursor resources, typically a ``.close()`` method.
If the cursor id is nonzero a KillCursors operation MUST be attempted, the result of the operation SHOULD be ignored.
The ClientSession associated with the cursor MUST be ended and the ServerSession returned to the pool.

* See Driver Sessions' section on `When sending a killCursors command <https://github.com/mongodb/specifications/blob/master/source/sessions/driver-sessions.rst#when-sending-a-killcursors-command>`_
jmikola marked this conversation as resolved.
Show resolved Hide resolved
* See Find, getMore and killCursors commands' section on `killCursors <https://github.com/mongodb/specifications/blob/master/source/find_getmore_killcursors_commands.rst#killcursors>`_

Client Side Operations Timeout
""""""""""""""""""""""""""""""

As with RunCommand if the initial command has a ``maxTimeMS`` and CSOT settings are provided the behavior is undefined and drivers MUST NOT inspect the document to detect this.
dariakp marked this conversation as resolved.
Show resolved Hide resolved
Drivers SHOULD not allow both a ``getMore`` ``maxTimeMS`` setting to be provided and CSOT settings.
dariakp marked this conversation as resolved.
Show resolved Hide resolved
Drivers MUST document that attempting to set both can have undefined behavior and is not supported.

When ``timeoutMS`` and ``timeoutMode`` are provided the driver MUST support timeout functionality as described in the CSOT specification.

* See Client Side Operations Timeout's section on `Cursors <https://github.com/mongodb/specifications/blob/master/source/client-side-operations-timeout/client-side-operations-timeout.rst#cursors>`_

Changelog
=========

:2023-04-24: Add runCursorCommand API specification.
:2023-04-20: Add run command specification.
215 changes: 215 additions & 0 deletions source/run-command/tests/unified/runCursorCommand.json
@@ -0,0 +1,215 @@
{
"description": "runCursorCommand",
"schemaVersion": "1.3",
"createEntities": [
{
"client": {
"id": "client",
"useMultipleMongoses": false,
"observeEvents": [
"commandStartedEvent"
]
}
},
{
"database": {
"id": "db",
"client": "client",
"databaseName": "db"
}
},
{
"collection": {
"id": "collection",
"database": "db",
"collectionName": "collection"
}
}
],
"initialData": [
{
"collectionName": "collection",
"databaseName": "db",
"documents": [
{
"_id": 1,
"x": 11
},
{
"_id": 2,
"x": 22
},
{
"_id": 3,
"x": 33
},
{
"_id": 4,
"x": 44
},
{
"_id": 5,
"x": 55
}
]
}
],
"tests": [
{
"description": "creates a cursor and iterates results from getMores",
dariakp marked this conversation as resolved.
Show resolved Hide resolved
"operations": [
{
"name": "runCursorCommand",
"object": "db",
"arguments": {
"commandName": "find",
"batchSize": 2,
"command": {
"find": "collection",
"filter": {},
"batchSize": 2
}
},
"expectResult": [
{
"_id": 1,
"x": 11
},
{
"_id": 2,
"x": 22
},
{
"_id": 3,
"x": 33
},
{
"_id": 4,
"x": 44
},
{
"_id": 5,
"x": 55
}
]
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"command": {
"find": "collection",
"filter": {},
"batchSize": 2,
"$db": "db",
"lsid": {
"id": {
"$$type": "binData"
}
}
},
"commandName": "find"
}
},
{
"commandStartedEvent": {
"command": {
"getMore": {
"$$type": "long"
},
"collection": "collection",
"$db": "db",
"lsid": {
"id": {
"$$type": "binData"
}
}
},
"commandName": "getMore"
}
},
{
"commandStartedEvent": {
"command": {
"getMore": {
"$$type": "long"
},
"collection": "collection",
"$db": "db",
"lsid": {
"id": {
"$$type": "binData"
}
}
},
"commandName": "getMore"
}
}
]
}
]
},
{
"description": "creates a cursor and iterates one result",
"operations": [
{
"name": "createRunCursorCommand",
"object": "db",
"arguments": {
"commandName": "find",
"batchSize": 2,
"command": {
"find": "collection",
"filter": {},
"batchSize": 2
}
},
"saveResultAsEntity": "myRunCommandCursor"
},
{
"name": "iterateUntilDocumentOrError",
"object": "myRunCommandCursor",
"expectResult": {
"_id": 1,
"x": 11
}
},
{
"name": "iterateUntilDocumentOrError",
"object": "myRunCommandCursor",
"expectResult": {
"_id": 2,
"x": 22
}
},
{
"name": "iterateUntilDocumentOrError",
"object": "myRunCommandCursor",
"expectResult": {
"_id": 3,
"x": 33
}
},
{
"name": "iterateUntilDocumentOrError",
"object": "myRunCommandCursor",
"expectResult": {
"_id": 4,
"x": 44
}
},
{
"name": "iterateUntilDocumentOrError",
"object": "myRunCommandCursor",
"expectResult": {
"_id": 5,
"x": 55
}
}
]
}
]
}