diff --git a/source/sessions/snapshot-sessions.md b/source/sessions/snapshot-sessions.md index a20281f2bf..76b984db72 100644 --- a/source/sessions/snapshot-sessions.md +++ b/source/sessions/snapshot-sessions.md @@ -63,24 +63,28 @@ response. The `atClusterTime` field represents the timestamp of the read and is ## Specification An application requests snapshot reads by creating a `ClientSession` with options that specify that snapshot reads are -desired. An application then passes the session as an argument to methods in the `MongoDatabase` and `MongoCollection` -classes. Read operations (find/aggregate/distinct) performed against that session will be read from the same snapshot. +desired and optionally specifying a `snapshotTime`. An application then passes the session as an argument to methods in +the `MongoDatabase` and `MongoCollection` classes. Read operations (find/aggregate/distinct) performed against that +session will be read from the same snapshot. ## High level summary of the API changes for snapshot reads Snapshot reads are built on top of client sessions. -Applications will start a new client session for snapshot reads like this: +Applications will start a new client session for snapshot reads and possibly retrieve the snapshot time like this: ```typescript -options = new SessionOptions(snapshot = true); +options = new SessionOptions(snapshot = true, snapshotTime = timestampValue); session = client.startSession(options); +snapshotTime = session.snapshotTime; ``` All read operations performed using this session will be read from the same snapshot. -If no value is provided for `snapshot` a value of false is implied. There are no MongoDatabase, MongoClient, or -MongoCollection API changes. +If no value is provided for `snapshot` a value of false is implied. `snapshotTime` is an optional parameter and if not +passed the snapshot time will be set internally after the first find/aggregate/distinct operation inside the session. + +There are no MongoDatabase, MongoClient, or MongoCollection API changes. ## SessionOptions changes @@ -89,14 +93,16 @@ MongoCollection API changes. ```typescript class SessionOptions { Optional snapshot; + Optional snapshotTime; // other options defined by other specs } ``` -In order to support snapshot reads a new property named `snapshot` is added to `SessionOptions`. Applications set -`snapshot` when starting a client session to indicate whether they want snapshot reads. All read operations performed -using that client session will share the same snapshot. +In order to support snapshot reads two properties called `snapshot` and `snapshotTime` are added to `SessionOptions`. +Applications set `snapshot` when starting a client session to indicate whether they want snapshot reads and optionally +set `snapshotTime` to specify the desired snapshot time beforehand. All read operations performed using that client +session will share the same snapshot. Each new member is documented below. @@ -110,8 +116,29 @@ Snapshot reads and causal consistency are mutually exclusive. Therefore if `snap `causalConsistency` must be false. Client MUST throw an error if both `snapshot` and `causalConsistency` are set to true. Snapshot reads are supported on both primaries and secondaries. +### snapshotTime + +Applications set `snapshotTime` when starting a snapshot session to specify the desired snapshot time. + +Note that the `snapshotTime` property is optional. The default value of this property is null. + +Client MUST throw an error if `snapshotTime` is set and `snapshot` is not set to true. + ## ClientSession changes +A readonly property called `snapshotTime` will be added to `ClientSession` that allows applications to retrieve the +snapshot time of the session: + +```typescript +class ClientSession { + readonly Optional snapshotTime; + + // other options defined by other specs +} +``` + +Getting the value of `snapshotTime` on a non-snapshot session MUST raise an error. + Transactions are not allowed with snapshot sessions. Calling `session.startTransaction(options)` on a snapshot session MUST raise an error. @@ -123,10 +150,12 @@ MUST raise an error. There are no new server commands related to snapshot reads. Instead, snapshot reads are implemented by: -1. Saving the `atClusterTime` returned by 5.0+ servers for the first find/aggregate/distinct operation in a private - `snapshotTime` property of the `ClientSession` object. Drivers MUST save `atClusterTime` in the `ClientSession` - object. -2. Passing that `snapshotTime` in the `atClusterTime` field of the `readConcern` field for subsequent snapshot read +1. If `snapshotTime` is specified in `SessionOptions`, saving the value in a `snapshotTime` property of the + `ClientSession`. +2. If `snapshotTime` is not specified in `SessionOptions`, saving the `atClusterTime` returned by 5.0+ servers for the + first find/aggregate/distinct operation in a `snapshotTime` property of the `ClientSession` object. Drivers MUST + save `atClusterTime` in the `ClientSession` object. +3. Passing that `snapshotTime` in the `atClusterTime` field of the `readConcern` field for subsequent snapshot read operations (i.e. find/aggregate/distinct commands). ## Server Command Responses @@ -241,6 +270,7 @@ C# driver will provide the reference implementation. The corresponding ticket is ## Changelog +- 2025-09-23: Exposed snapshotTime to applications. - 2024-05-08: Migrated from reStructuredText to Markdown. - 2021-06-15: Initial version. - 2021-06-28: Raise client side error on < 5.0. diff --git a/source/sessions/tests/README.md b/source/sessions/tests/README.md index 8d817a59fd..0a94b9639e 100644 --- a/source/sessions/tests/README.md +++ b/source/sessions/tests/README.md @@ -29,6 +29,15 @@ As part of the test setup for these cases, create a `MongoClient` pointed at the in the test case and verify that the test server does NOT define a value for `logicalSessionTimeoutMinutes` by sending a hello command and checking the response. +## Specific operations and behaviour for unified tests + +An operation on sessions called `getSnapshotTime` must be supported for unified tests. This operation returns the value +of `snapshotTime` on the session, so that it can be used in subsequent operations. + +When parsing `snapshotTime` from `sessionOptions` for unified tests, the parsed string is the name of the key for the +actual value of `snapshotTime` to be found in the +[entity map](https://github.com/mongodb/specifications/blob/master/source/unified-test-format/unified-test-format.md#entity-map). + ## Prose tests ### 1. Setting both `snapshot` and `causalConsistency` to true is not allowed @@ -250,8 +259,24 @@ and configure a `MongoClient` with default options. - Run a ping command using C1 and assert that `$clusterTime` sent is the same as the `clusterTime` recorded earlier. This assertion proves that C1's `$clusterTime` was not advanced by gossiping through SDAM. +### 21. Having `snapshotTime` set and `snapshot` set to false is not allowed + +Snapshot sessions tests require server of version 5.0 or higher and replica set or a sharded cluster deployment. + +- Start a session by calling `startSession` with `snapshot = false` and `snapshotTime = new Timestamp(1)`. +- Assert that a client side error was raised. + +### 22. Retrieving `snapshotTime` on a non-snapshot session raises an error + +Snapshot sessions tests require server of version 5.0 or higher and replica set or a sharded cluster deployment. + +- Start a session by calling `startSession` on with `snapshot = false`. +- Try to access the session's snapshot time. +- Assert that a client side error was raised. + ## Changelog +- 2025-09-25: Added tests for snapshotTime. - 2025-02-24: Test drivers do not gossip $clusterTime on SDAM. - 2024-05-08: Migrated from reStructuredText to Markdown. - 2019-05-15: Initial version. diff --git a/source/sessions/tests/snapshot-sessions.json b/source/sessions/tests/snapshot-sessions.json index 260f8b6f48..8f806ea759 100644 --- a/source/sessions/tests/snapshot-sessions.json +++ b/source/sessions/tests/snapshot-sessions.json @@ -988,6 +988,810 @@ } } ] + }, + { + "description": "Find operation with snapshot and snapshot time", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": {} + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 11 + } + ] + }, + { + "name": "getSnapshotTime", + "object": "session0", + "saveResultAsEntity": "savedSnapshotTime" + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "_id": 3, + "x": 33 + } + } + }, + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "session": { + "id": "session2", + "client": "client0", + "sessionOptions": { + "snapshot": true, + "snapshotTime": "savedSnapshotTime" + } + } + } + ] + } + }, + { + "name": "find", + "object": "collection0", + "arguments": { + "session": "session2", + "filter": {} + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 11 + } + ] + }, + { + "name": "find", + "object": "collection0", + "arguments": { + "session": "session2", + "filter": {} + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 11 + } + ] + }, + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": {} + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 11 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "collection0", + "readConcern": { + "level": "snapshot", + "atClusterTime": { + "$$exists": false + } + } + }, + "databaseName": "database0" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "collection0", + "readConcern": { + "level": "snapshot", + "atClusterTime": { + "$$matchesEntity": "savedSnapshotTime" + } + } + }, + "databaseName": "database0" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "collection0", + "readConcern": { + "level": "snapshot", + "atClusterTime": { + "$$matchesEntity": "savedSnapshotTime" + } + } + }, + "databaseName": "database0" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "collection0", + "readConcern": { + "$$exists": false + } + }, + "databaseName": "database0" + } + } + ] + } + ] + }, + { + "description": "Distinct operation with snapshot and snapshot time", + "operations": [ + { + "name": "distinct", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": {}, + "fieldName": "x" + }, + "expectResult": [ + 11 + ] + }, + { + "name": "getSnapshotTime", + "object": "session0", + "saveResultAsEntity": "savedSnapshotTime" + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "_id": 3, + "x": 33 + } + } + }, + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "session": { + "id": "session2", + "client": "client0", + "sessionOptions": { + "snapshot": true, + "snapshotTime": "savedSnapshotTime" + } + } + } + ] + } + }, + { + "name": "distinct", + "object": "collection0", + "arguments": { + "session": "session2", + "filter": {}, + "fieldName": "x" + }, + "expectResult": [ + 11 + ] + }, + { + "name": "distinct", + "object": "collection0", + "arguments": { + "session": "session2", + "filter": {}, + "fieldName": "x" + }, + "expectResult": [ + 11 + ] + }, + { + "name": "distinct", + "object": "collection0", + "arguments": { + "filter": {}, + "fieldName": "x" + }, + "expectResult": [ + 11, + 33 + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "collection0", + "readConcern": { + "level": "snapshot", + "atClusterTime": { + "$$exists": false + } + } + }, + "databaseName": "database0" + } + }, + { + "commandStartedEvent": { + "command": { + "distinct": "collection0", + "readConcern": { + "level": "snapshot", + "atClusterTime": { + "$$matchesEntity": "savedSnapshotTime" + } + } + }, + "databaseName": "database0" + } + }, + { + "commandStartedEvent": { + "command": { + "distinct": "collection0", + "readConcern": { + "level": "snapshot", + "atClusterTime": { + "$$matchesEntity": "savedSnapshotTime" + } + } + }, + "databaseName": "database0" + } + }, + { + "commandStartedEvent": { + "command": { + "distinct": "collection0", + "readConcern": { + "$$exists": false + } + }, + "databaseName": "database0" + } + } + ] + } + ] + }, + { + "description": "Aggregate operation with snapshot and snapshot time", + "operations": [ + { + "name": "aggregate", + "object": "collection0", + "arguments": { + "session": "session0", + "pipeline": [ + { + "$match": { + "_id": 1 + } + } + ] + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + } + ] + }, + { + "name": "getSnapshotTime", + "object": "session0", + "saveResultAsEntity": "savedSnapshotTime" + }, + { + "name": "findOneAndUpdate", + "object": "collection0", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "returnDocument": "After" + }, + "expectResult": { + "_id": 1, + "x": 12 + } + }, + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "session": { + "id": "session2", + "client": "client0", + "sessionOptions": { + "snapshot": true, + "snapshotTime": "savedSnapshotTime" + } + } + } + ] + } + }, + { + "name": "aggregate", + "object": "collection0", + "arguments": { + "session": "session2", + "pipeline": [ + { + "$match": { + "_id": 1 + } + } + ] + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + } + ] + }, + { + "name": "aggregate", + "object": "collection0", + "arguments": { + "session": "session2", + "pipeline": [ + { + "$match": { + "_id": 1 + } + } + ] + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + } + ] + }, + { + "name": "aggregate", + "object": "collection0", + "arguments": { + "pipeline": [ + { + "$match": { + "_id": 1 + } + } + ] + }, + "expectResult": [ + { + "_id": 1, + "x": 12 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "collection0", + "readConcern": { + "level": "snapshot", + "atClusterTime": { + "$$exists": false + } + } + }, + "databaseName": "database0" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "collection0", + "readConcern": { + "level": "snapshot", + "atClusterTime": { + "$$matchesEntity": "savedSnapshotTime" + } + } + }, + "databaseName": "database0" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "collection0", + "readConcern": { + "level": "snapshot", + "atClusterTime": { + "$$matchesEntity": "savedSnapshotTime" + } + } + }, + "databaseName": "database0" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "collection0", + "readConcern": { + "$$exists": false + } + }, + "databaseName": "database0" + } + } + ] + } + ] + }, + { + "description": "countDocuments operation with snapshot and snapshot time", + "operations": [ + { + "name": "countDocuments", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": {} + }, + "expectResult": 2 + }, + { + "name": "getSnapshotTime", + "object": "session0", + "saveResultAsEntity": "savedSnapshotTime" + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "_id": 3, + "x": 33 + } + } + }, + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "session": { + "id": "session2", + "client": "client0", + "sessionOptions": { + "snapshot": true, + "snapshotTime": "savedSnapshotTime" + } + } + } + ] + } + }, + { + "name": "countDocuments", + "object": "collection0", + "arguments": { + "session": "session2", + "filter": {} + }, + "expectResult": 2 + }, + { + "name": "countDocuments", + "object": "collection0", + "arguments": { + "session": "session2", + "filter": {} + }, + "expectResult": 2 + }, + { + "name": "countDocuments", + "object": "collection0", + "arguments": { + "filter": {} + }, + "expectResult": 3 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "collection0", + "readConcern": { + "level": "snapshot", + "atClusterTime": { + "$$exists": false + } + } + }, + "databaseName": "database0" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "collection0", + "readConcern": { + "level": "snapshot", + "atClusterTime": { + "$$matchesEntity": "savedSnapshotTime" + } + } + }, + "databaseName": "database0" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "collection0", + "readConcern": { + "level": "snapshot", + "atClusterTime": { + "$$matchesEntity": "savedSnapshotTime" + } + } + }, + "databaseName": "database0" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "collection0", + "readConcern": { + "$$exists": false + } + }, + "databaseName": "database0" + } + } + ] + } + ] + }, + { + "description": "Mixed operation with snapshot and snapshotTime", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + } + ] + }, + { + "name": "getSnapshotTime", + "object": "session0", + "saveResultAsEntity": "savedSnapshotTime" + }, + { + "name": "findOneAndUpdate", + "object": "collection0", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "returnDocument": "After" + }, + "expectResult": { + "_id": 1, + "x": 12 + } + }, + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "session": { + "id": "session2", + "client": "client0", + "sessionOptions": { + "snapshot": true, + "snapshotTime": "savedSnapshotTime" + } + } + } + ] + } + }, + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectResult": [ + { + "_id": 1, + "x": 12 + } + ] + }, + { + "name": "aggregate", + "object": "collection0", + "arguments": { + "pipeline": [ + { + "$match": { + "_id": 1 + } + } + ], + "session": "session2" + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + } + ] + }, + { + "name": "distinct", + "object": "collection0", + "arguments": { + "fieldName": "x", + "filter": {}, + "session": "session2" + }, + "expectResult": [ + 11 + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "collection0", + "readConcern": { + "level": "snapshot", + "atClusterTime": { + "$$exists": false + } + } + } + } + }, + { + "commandStartedEvent": { + "command": { + "find": "collection0", + "readConcern": { + "$$exists": false + } + } + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "collection0", + "readConcern": { + "level": "snapshot", + "atClusterTime": { + "$$matchesEntity": "savedSnapshotTime" + } + } + } + } + }, + { + "commandStartedEvent": { + "command": { + "distinct": "collection0", + "readConcern": { + "level": "snapshot", + "atClusterTime": { + "$$matchesEntity": "savedSnapshotTime" + } + } + } + } + } + ] + } + ] } ] } diff --git a/source/sessions/tests/snapshot-sessions.yml b/source/sessions/tests/snapshot-sessions.yml index bcf0f7eec6..03a55d31eb 100644 --- a/source/sessions/tests/snapshot-sessions.yml +++ b/source/sessions/tests/snapshot-sessions.yml @@ -309,6 +309,7 @@ tests: atClusterTime: "$$exists": true +## This test seems to be wrong - description: countDocuments operation with snapshot operations: - name: countDocuments @@ -378,7 +379,7 @@ tests: fieldName: x filter: {} session: session0 - expectResult: [ 11 ] + expectResult: [ 11 ] expectEvents: - client: client0 events: @@ -480,3 +481,406 @@ tests: isError: true isClientError: true errorContains: Transactions are not supported in snapshot sessions + +- description: Find operation with snapshot and snapshot time + operations: + - name: find + object: collection0 + arguments: + session: session0 + filter: {} + expectResult: + - { _id: 1, x: 11 } + - { _id: 2, x: 11 } + - name: getSnapshotTime + object: session0 + saveResultAsEntity: &savedSnapshotTime savedSnapshotTime + - name: insertOne + object: collection0 + arguments: + document: { _id: 3, x: 33 } + - name: createEntities + object: testRunner + arguments: + entities: + - session: + id: session2 + client: client0 + sessionOptions: + snapshot: true + snapshotTime: *savedSnapshotTime + - name: find + object: collection0 + arguments: + session: session2 + filter: {} + expectResult: + - { _id: 1, x: 11 } + - { _id: 2, x: 11 } + ## Calling the operation again to verify that atClusterTime/snapshotTime has not been modified after the first query + - name: find + object: collection0 + arguments: + session: session2 + filter: {} + expectResult: + - { _id: 1, x: 11 } + - { _id: 2, x: 11 } + - name: find + object: collection0 + arguments: + filter: {} + expectResult: + - { _id: 1, x: 11 } + - { _id: 2, x: 11 } + - { _id: 3, x: 33 } + expectEvents: + - client: client0 + events: + - commandStartedEvent: + command: + find: collection0 + readConcern: + level: snapshot + atClusterTime: + "$$exists": false + databaseName: database0 + - commandStartedEvent: + command: + find: collection0 + readConcern: + level: snapshot + atClusterTime: { $$matchesEntity: *savedSnapshotTime } + databaseName: database0 + - commandStartedEvent: + command: + find: collection0 + readConcern: + level: snapshot + atClusterTime: { $$matchesEntity: *savedSnapshotTime } + databaseName: database0 + - commandStartedEvent: + command: + find: collection0 + readConcern: + "$$exists": false + databaseName: database0 + +- description: Distinct operation with snapshot and snapshot time + operations: + - name: distinct + object: collection0 + arguments: + session: session0 + filter: {} + fieldName: x + expectResult: [11] + - name: getSnapshotTime + object: session0 + saveResultAsEntity: &savedSnapshotTime savedSnapshotTime + - name: insertOne + object: collection0 + arguments: + document: { _id: 3, x: 33 } + - name: createEntities + object: testRunner + arguments: + entities: + - session: + id: session2 + client: client0 + sessionOptions: + snapshot: true + snapshotTime: *savedSnapshotTime + - name: distinct + object: collection0 + arguments: + session: session2 + filter: {} + fieldName: x + expectResult: [11] + ## Calling the operation again to verify that atClusterTime/snapshotTime has not been modified after the first query + - name: distinct + object: collection0 + arguments: + session: session2 + filter: {} + fieldName: x + expectResult: [11] + - name: distinct + object: collection0 + arguments: + filter: {} + fieldName: x + expectResult: [11, 33] + expectEvents: + - client: client0 + events: + - commandStartedEvent: + command: + distinct: collection0 + readConcern: + level: snapshot + atClusterTime: + "$$exists": false + databaseName: database0 + - commandStartedEvent: + command: + distinct: collection0 + readConcern: + level: snapshot + atClusterTime: { $$matchesEntity: *savedSnapshotTime } + databaseName: database0 + - commandStartedEvent: + command: + distinct: collection0 + readConcern: + level: snapshot + atClusterTime: { $$matchesEntity: *savedSnapshotTime } + databaseName: database0 + - commandStartedEvent: + command: + distinct: collection0 + readConcern: + "$$exists": false + databaseName: database0 + +- description: Aggregate operation with snapshot and snapshot time + operations: + - name: aggregate + object: collection0 + arguments: + session: session0 + pipeline: + - "$match": { _id: 1 } + expectResult: + - { _id: 1, x: 11 } + - name: getSnapshotTime + object: session0 + saveResultAsEntity: &savedSnapshotTime savedSnapshotTime + - name: findOneAndUpdate + object: collection0 + arguments: + filter: { _id: 1 } + update: { $inc: { x: 1 } } + returnDocument: After + expectResult: { _id: 1, x: 12 } + - name: createEntities + object: testRunner + arguments: + entities: + - session: + id: session2 + client: client0 + sessionOptions: + snapshot: true + snapshotTime: *savedSnapshotTime + - name: aggregate + object: collection0 + arguments: + session: session2 + pipeline: + - "$match": { _id: 1 } + expectResult: + - { _id: 1, x: 11 } + ## Calling the operation again to verify that atClusterTime/snapshotTime has not been modified after the first query + - name: aggregate + object: collection0 + arguments: + session: session2 + pipeline: + - "$match": { _id: 1 } + expectResult: + - { _id: 1, x: 11 } + - name: aggregate + object: collection0 + arguments: + pipeline: + - "$match": { _id: 1 } + expectResult: + - { _id: 1, x: 12 } + expectEvents: + - client: client0 + events: + - commandStartedEvent: + command: + aggregate: collection0 + readConcern: + level: snapshot + atClusterTime: + "$$exists": false + databaseName: database0 + - commandStartedEvent: + command: + aggregate: collection0 + readConcern: + level: snapshot + atClusterTime: { $$matchesEntity: *savedSnapshotTime } + databaseName: database0 + - commandStartedEvent: + command: + aggregate: collection0 + readConcern: + level: snapshot + atClusterTime: { $$matchesEntity: *savedSnapshotTime } + databaseName: database0 + - commandStartedEvent: + command: + aggregate: collection0 + readConcern: + "$$exists": false + databaseName: database0 + +- description: countDocuments operation with snapshot and snapshot time + operations: + - name: countDocuments + object: collection0 + arguments: + session: session0 + filter: {} + expectResult: 2 + - name: getSnapshotTime + object: session0 + saveResultAsEntity: &savedSnapshotTime savedSnapshotTime + - name: insertOne + object: collection0 + arguments: + document: { _id: 3, x: 33 } + - name: createEntities + object: testRunner + arguments: + entities: + - session: + id: session2 + client: client0 + sessionOptions: + snapshot: true + snapshotTime: *savedSnapshotTime + - name: countDocuments + object: collection0 + arguments: + session: session2 + filter: {} + expectResult: 2 + ## Calling the operation again to verify that atClusterTime/snapshotTime has not been modified after the first query + - name: countDocuments + object: collection0 + arguments: + session: session2 + filter: {} + expectResult: 2 + - name: countDocuments + object: collection0 + arguments: + filter: {} + expectResult: 3 + expectEvents: + - client: client0 + events: + - commandStartedEvent: + command: + aggregate: collection0 + readConcern: + level: snapshot + atClusterTime: + "$$exists": false + databaseName: database0 + - commandStartedEvent: + command: + aggregate: collection0 + readConcern: + level: snapshot + atClusterTime: { $$matchesEntity: *savedSnapshotTime } + databaseName: database0 + - commandStartedEvent: + command: + aggregate: collection0 + readConcern: + level: snapshot + atClusterTime: { $$matchesEntity: *savedSnapshotTime } + databaseName: database0 + - commandStartedEvent: + command: + aggregate: collection0 + readConcern: + "$$exists": false + databaseName: database0 + +- description: Mixed operation with snapshot and snapshotTime + operations: + - name: find + object: collection0 + arguments: + session: session0 + filter: { _id: 1 } + expectResult: + - { _id: 1, x: 11 } + - name: getSnapshotTime + object: session0 + saveResultAsEntity: &savedSnapshotTime savedSnapshotTime + - name: findOneAndUpdate + object: collection0 + arguments: + filter: { _id: 1 } + update: { $inc: { x: 1 } } + returnDocument: After + expectResult: { _id: 1, x: 12 } + - name: createEntities + object: testRunner + arguments: + entities: + - session: + id: session2 + client: client0 + sessionOptions: + snapshot: true + snapshotTime: *savedSnapshotTime + - name: find + object: collection0 + arguments: + filter: { _id: 1 } + expectResult: + - { _id: 1, x: 12 } + - name: aggregate + object: collection0 + arguments: + pipeline: + - "$match": + _id: 1 + session: session2 + expectResult: + - { _id: 1, x: 11 } + - name: distinct + object: collection0 + arguments: + fieldName: x + filter: {} + session: session2 + expectResult: [ 11 ] + expectEvents: + - client: client0 + events: + - commandStartedEvent: + command: + find: collection0 + readConcern: + level: snapshot + atClusterTime: + "$$exists": false + - commandStartedEvent: + command: + find: collection0 + readConcern: + "$$exists": false + - commandStartedEvent: + command: + aggregate: collection0 + readConcern: + level: snapshot + atClusterTime: { $$matchesEntity: *savedSnapshotTime } + - commandStartedEvent: + command: + distinct: collection0 + readConcern: + level: snapshot + atClusterTime: { $$matchesEntity: *savedSnapshotTime } \ No newline at end of file