From c9b29fe79c83f1ed94036c4f3b1dd4d30a62c99c Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Tue, 23 Sep 2025 11:08:22 +0200 Subject: [PATCH 01/16] =?UTF-8?q?[WIP]=C2=A0DRIVERS-2782=20Expose=20snapsh?= =?UTF-8?q?otTime?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/sessions/snapshot-sessions.md | 42 +++++++++++++++++++++------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/source/sessions/snapshot-sessions.md b/source/sessions/snapshot-sessions.md index a20281f2bf..28033b209d 100644 --- a/source/sessions/snapshot-sessions.md +++ b/source/sessions/snapshot-sessions.md @@ -63,24 +63,26 @@ 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` +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, orMongoCollection API changes. ## SessionOptions changes @@ -89,14 +91,13 @@ 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 new 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 +111,28 @@ 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 session to indicate whether they want snapshot reads. + +Note that the `snapshotTime` property is optional. The default value of this property is null. + +Client MUST throw an error if `snapshotTime` and `snapshot` is not set to true. + ## ClientSession changes +A new readonly property (or method?) called `snapshotTime` will be added to `ClientSession` that allows developer 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,8 +144,8 @@ 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` +1. If `snapshotTime` is specified in `SessionOptions`, saving the value in a `snapshotTime` property of the `ClientSession`. +1. If `snapshotTime` is not specified in `SessionOptions`, 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 operations (i.e. find/aggregate/distinct commands). @@ -241,6 +262,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. From 9765387a4f3694dd023faa247032e98512ec7468 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Tue, 23 Sep 2025 11:20:41 +0200 Subject: [PATCH 02/16] Fixed linting --- source/sessions/snapshot-sessions.md | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/source/sessions/snapshot-sessions.md b/source/sessions/snapshot-sessions.md index 28033b209d..6067876b88 100644 --- a/source/sessions/snapshot-sessions.md +++ b/source/sessions/snapshot-sessions.md @@ -63,8 +63,9 @@ 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 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. +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 @@ -80,7 +81,8 @@ 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. `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. +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, orMongoCollection API changes. @@ -97,7 +99,10 @@ class SessionOptions { } ``` -In order to support snapshot reads two new 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. +In order to support snapshot reads two new 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. @@ -121,7 +126,8 @@ Client MUST throw an error if `snapshotTime` and `snapshot` is not set to true. ## ClientSession changes -A new readonly property (or method?) called `snapshotTime` will be added to `ClientSession` that allows developer to retrieve the snapshot time of the session: +A new readonly property (or method?) called `snapshotTime` will be added to `ClientSession` that allows developer to +retrieve the snapshot time of the session: ```typescript class ClientSession { @@ -144,10 +150,12 @@ MUST raise an error. There are no new server commands related to snapshot reads. Instead, snapshot reads are implemented by: -1. If `snapshotTime` is specified in `SessionOptions`, saving the value in a `snapshotTime` property of the `ClientSession`. -1. If `snapshotTime` is not specified in `SessionOptions`, 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 private `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 From 364771e3c7d44668bfbb450c1e0fbe47d0e81d85 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Tue, 23 Sep 2025 11:27:21 +0200 Subject: [PATCH 03/16] Small fixes --- source/sessions/snapshot-sessions.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/sessions/snapshot-sessions.md b/source/sessions/snapshot-sessions.md index 6067876b88..5c5a6cbc22 100644 --- a/source/sessions/snapshot-sessions.md +++ b/source/sessions/snapshot-sessions.md @@ -84,7 +84,7 @@ All read operations performed using this session will be read from the same snap 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, orMongoCollection API changes. +There are no MongoDatabase, MongoClient, or MongoCollection API changes. ## SessionOptions changes @@ -118,16 +118,16 @@ true. Snapshot reads are supported on both primaries and secondaries. ### snapshotTime -Applications set `snapshotTime` when starting a session to indicate whether they want snapshot reads. +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` and `snapshot` is not set to true. +Client MUST throw an error if `snapshotTime` is set and `snapshot` is not set to true. ## ClientSession changes -A new readonly property (or method?) called `snapshotTime` will be added to `ClientSession` that allows developer to -retrieve the snapshot time of the session: +A new readonly property called `snapshotTime` will be added to `ClientSession` that allows applications to retrieve the +snapshot time of the session: ```typescript class ClientSession { From 1d581c64c8d4bf14b2c7ac89b618c9ee16ded304 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Tue, 23 Sep 2025 22:03:23 +0200 Subject: [PATCH 04/16] Small fixes following review. --- source/sessions/snapshot-sessions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/sessions/snapshot-sessions.md b/source/sessions/snapshot-sessions.md index 5c5a6cbc22..bbaa4da684 100644 --- a/source/sessions/snapshot-sessions.md +++ b/source/sessions/snapshot-sessions.md @@ -99,7 +99,7 @@ class SessionOptions { } ``` -In order to support snapshot reads two new properties called `snapshot` and `snapshotTime` are added to +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. @@ -153,7 +153,7 @@ There are no new server commands related to snapshot reads. Instead, snapshot re 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 private `snapshotTime` property of the `ClientSession` object. Drivers + 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). From 9d1da1ba341984da8a118fd0d08002faa08c2d71 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Thu, 25 Sep 2025 11:16:05 +0200 Subject: [PATCH 05/16] Added prose test --- source/sessions/tests/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/source/sessions/tests/README.md b/source/sessions/tests/README.md index 8d817a59fd..0d5685dfc6 100644 --- a/source/sessions/tests/README.md +++ b/source/sessions/tests/README.md @@ -250,8 +250,16 @@ 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. + +- `client.startSession(snapshot = false, snapshotTime = new Timestamp(1))` +- Assert that an error was raised by driver + ## Changelog +- 2025-09-25: Added test 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. From 610dc371512d9238fac3bfb80f09e45e394027a5 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Thu, 25 Sep 2025 11:17:07 +0200 Subject: [PATCH 06/16] Added unified tests --- source/sessions/tests/snapshot-sessions.json | 804 +++++++++++++++++++ source/sessions/tests/snapshot-sessions.yml | 410 +++++++++- 2 files changed, 1213 insertions(+), 1 deletion(-) 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..48cf415b4a 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,410 @@ 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 find again to verify that atClusterTime/snapshotTime has not been modified after the first query + ## as it would happen if snapshotTime had not been specified + - 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 find again to verify that atClusterTime/snapshotTime has not been modified after the first query + ## as it would happen if snapshotTime had not been specified + - 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 find again to verify that atClusterTime/snapshotTime has not been modified after the first query + ## as it would happen if snapshotTime had not been specified + - 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 find again to verify that atClusterTime/snapshotTime has not been modified after the first query + ## as it would happen if snapshotTime had not been specified + - 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 From 63a6e0d633b90f534669e04d925f92c5e5ef83cc Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Fri, 26 Sep 2025 16:03:44 +0200 Subject: [PATCH 07/16] Added improvement to tests --- source/sessions/snapshot-sessions.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/sessions/snapshot-sessions.md b/source/sessions/snapshot-sessions.md index bbaa4da684..755431dae4 100644 --- a/source/sessions/snapshot-sessions.md +++ b/source/sessions/snapshot-sessions.md @@ -124,6 +124,8 @@ Note that the `snapshotTime` property is optional. The default value of this pro Client MUST throw an error if `snapshotTime` is set and `snapshot` is not set to true. +Note that when parsing `snapshotTime` for [unified tests](https://github.com/mongodb/specifications/blob/master/source/unified-test-format/unified-test-format.md), 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). + ## ClientSession changes A new readonly property called `snapshotTime` will be added to `ClientSession` that allows applications to retrieve the @@ -142,6 +144,8 @@ Getting the value of `snapshotTime` on a non-snapshot session MUST raise an erro Transactions are not allowed with snapshot sessions. Calling `session.startTransaction(options)` on a snapshot session MUST raise an error. +Note that a new operation on session called `getSnapshotTime` must be supported for [unified tests](https://github.com/mongodb/specifications/blob/master/source/unified-test-format/unified-test-format.md). This operation returns the value of `snapshotTime` on the session, so that it can be used in following operations. + ## ReadConcern changes `snapshot` added to [ReadConcernLevel enumeration](../read-write-concern/read-write-concern.md#read-concern). From 4c4691cfa81b26ca512639b57c2fbc9ec16c663c Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Fri, 26 Sep 2025 16:09:13 +0200 Subject: [PATCH 08/16] Improved text. --- source/sessions/snapshot-sessions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/sessions/snapshot-sessions.md b/source/sessions/snapshot-sessions.md index 755431dae4..3254e51a71 100644 --- a/source/sessions/snapshot-sessions.md +++ b/source/sessions/snapshot-sessions.md @@ -124,7 +124,7 @@ Note that the `snapshotTime` property is optional. The default value of this pro Client MUST throw an error if `snapshotTime` is set and `snapshot` is not set to true. -Note that when parsing `snapshotTime` for [unified tests](https://github.com/mongodb/specifications/blob/master/source/unified-test-format/unified-test-format.md), 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). +Note that when parsing `snapshotTime` from `sessionOptions` for [unified tests](https://github.com/mongodb/specifications/blob/master/source/unified-test-format/unified-test-format.md), 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). ## ClientSession changes From ff0582f402ac3d0d897a18e164f0634c627e9ea2 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Mon, 29 Sep 2025 16:45:14 +0200 Subject: [PATCH 09/16] Corrected linting --- source/sessions/snapshot-sessions.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/source/sessions/snapshot-sessions.md b/source/sessions/snapshot-sessions.md index 3254e51a71..df4cde247f 100644 --- a/source/sessions/snapshot-sessions.md +++ b/source/sessions/snapshot-sessions.md @@ -99,10 +99,10 @@ class SessionOptions { } ``` -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. +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. @@ -124,7 +124,10 @@ Note that the `snapshotTime` property is optional. The default value of this pro Client MUST throw an error if `snapshotTime` is set and `snapshot` is not set to true. -Note that when parsing `snapshotTime` from `sessionOptions` for [unified tests](https://github.com/mongodb/specifications/blob/master/source/unified-test-format/unified-test-format.md), 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). +Note that when parsing `snapshotTime` from `sessionOptions` for +[unified tests](https://github.com/mongodb/specifications/blob/master/source/unified-test-format/unified-test-format.md), +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). ## ClientSession changes @@ -144,7 +147,9 @@ Getting the value of `snapshotTime` on a non-snapshot session MUST raise an erro Transactions are not allowed with snapshot sessions. Calling `session.startTransaction(options)` on a snapshot session MUST raise an error. -Note that a new operation on session called `getSnapshotTime` must be supported for [unified tests](https://github.com/mongodb/specifications/blob/master/source/unified-test-format/unified-test-format.md). This operation returns the value of `snapshotTime` on the session, so that it can be used in following operations. +Note that a new operation on session called `getSnapshotTime` must be supported for +[unified tests](https://github.com/mongodb/specifications/blob/master/source/unified-test-format/unified-test-format.md). +This operation returns the value of `snapshotTime` on the session, so that it can be used in following operations. ## ReadConcern changes @@ -157,8 +162,8 @@ There are no new server commands related to snapshot reads. Instead, snapshot re 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. + 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). From 4fdf70100b0ed180d0d5b750864f41239d18e4b9 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Mon, 29 Sep 2025 16:51:46 +0200 Subject: [PATCH 10/16] Removed comments --- source/sessions/tests/snapshot-sessions.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/source/sessions/tests/snapshot-sessions.yml b/source/sessions/tests/snapshot-sessions.yml index 48cf415b4a..03336a26c6 100644 --- a/source/sessions/tests/snapshot-sessions.yml +++ b/source/sessions/tests/snapshot-sessions.yml @@ -517,8 +517,6 @@ tests: expectResult: - { _id: 1, x: 11 } - { _id: 2, x: 11 } - ## Calling find again to verify that atClusterTime/snapshotTime has not been modified after the first query - ## as it would happen if snapshotTime had not been specified - name: find object: collection0 arguments: @@ -600,8 +598,6 @@ tests: filter: {} fieldName: x expectResult: [11] - ## Calling find again to verify that atClusterTime/snapshotTime has not been modified after the first query - ## as it would happen if snapshotTime had not been specified - name: distinct object: collection0 arguments: @@ -685,8 +681,6 @@ tests: - "$match": { _id: 1 } expectResult: - { _id: 1, x: 11 } - ## Calling find again to verify that atClusterTime/snapshotTime has not been modified after the first query - ## as it would happen if snapshotTime had not been specified - name: aggregate object: collection0 arguments: From 48b70fcfd5ac8f7d30222d37fb5b8bcfac84be88 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Mon, 29 Sep 2025 16:57:37 +0200 Subject: [PATCH 11/16] Apply suggestion from @papafe --- source/sessions/snapshot-sessions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/sessions/snapshot-sessions.md b/source/sessions/snapshot-sessions.md index df4cde247f..bfdbfc78ee 100644 --- a/source/sessions/snapshot-sessions.md +++ b/source/sessions/snapshot-sessions.md @@ -131,7 +131,7 @@ the parsed string is the name of the key for the actual value of `snapshotTime` ## ClientSession changes -A new readonly property called `snapshotTime` will be added to `ClientSession` that allows applications to retrieve the +A readonly property called `snapshotTime` will be added to `ClientSession` that allows applications to retrieve the snapshot time of the session: ```typescript From de292a63fbf7eef65b275f87ee0673ae92cfbd49 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Mon, 29 Sep 2025 17:16:34 +0200 Subject: [PATCH 12/16] Added test --- source/sessions/tests/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/source/sessions/tests/README.md b/source/sessions/tests/README.md index 0d5685dfc6..5afdf3f407 100644 --- a/source/sessions/tests/README.md +++ b/source/sessions/tests/README.md @@ -257,6 +257,21 @@ Snapshot sessions tests require server of version 5.0 or higher and replica set - `client.startSession(snapshot = false, snapshotTime = new Timestamp(1))` - Assert that an error was raised by driver +### 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. + +- `client.startSession(snapshot = false, snapshotTime = new Timestamp(1))` +- Assert that an error was raised by driver + +### 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. + +- `client.startSession(snapshot = false)` +- `client.snapshotTime` +- Assert that an error was raised by driver + ## Changelog - 2025-09-25: Added test for snapshotTime. From df9d40ddaab9e69727e18ac4673c3b530f5aef54 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Mon, 29 Sep 2025 17:17:13 +0200 Subject: [PATCH 13/16] Corrected test. --- source/sessions/tests/README.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/source/sessions/tests/README.md b/source/sessions/tests/README.md index 5afdf3f407..d7cd76d0b2 100644 --- a/source/sessions/tests/README.md +++ b/source/sessions/tests/README.md @@ -257,13 +257,6 @@ Snapshot sessions tests require server of version 5.0 or higher and replica set - `client.startSession(snapshot = false, snapshotTime = new Timestamp(1))` - Assert that an error was raised by driver -### 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. - -- `client.startSession(snapshot = false, snapshotTime = new Timestamp(1))` -- Assert that an error was raised by driver - ### 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. @@ -274,7 +267,7 @@ Snapshot sessions tests require server of version 5.0 or higher and replica set ## Changelog -- 2025-09-25: Added test for snapshotTime. +- 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. From d425f2b394ac50bba9962ff16331f285bbc821c0 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Fri, 3 Oct 2025 09:50:37 +0200 Subject: [PATCH 14/16] Corrected test --- source/sessions/tests/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/sessions/tests/README.md b/source/sessions/tests/README.md index d7cd76d0b2..76f4f1b44d 100644 --- a/source/sessions/tests/README.md +++ b/source/sessions/tests/README.md @@ -254,16 +254,16 @@ and configure a `MongoClient` with default options. Snapshot sessions tests require server of version 5.0 or higher and replica set or a sharded cluster deployment. -- `client.startSession(snapshot = false, snapshotTime = new Timestamp(1))` -- Assert that an error was raised by driver +- 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. -- `client.startSession(snapshot = false)` -- `client.snapshotTime` -- Assert that an error was raised by driver +- 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 From e640b2e416bd004a943b7b181facfa7cafe67348 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Fri, 3 Oct 2025 09:57:20 +0200 Subject: [PATCH 15/16] Added comment. --- source/sessions/tests/snapshot-sessions.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/sessions/tests/snapshot-sessions.yml b/source/sessions/tests/snapshot-sessions.yml index 03336a26c6..03a55d31eb 100644 --- a/source/sessions/tests/snapshot-sessions.yml +++ b/source/sessions/tests/snapshot-sessions.yml @@ -517,6 +517,7 @@ tests: 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: @@ -598,6 +599,7 @@ tests: 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: @@ -681,6 +683,7 @@ tests: - "$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: @@ -759,8 +762,7 @@ tests: session: session2 filter: {} expectResult: 2 - ## Calling find again to verify that atClusterTime/snapshotTime has not been modified after the first query - ## as it would happen if snapshotTime had not been specified + ## Calling the operation again to verify that atClusterTime/snapshotTime has not been modified after the first query - name: countDocuments object: collection0 arguments: From 2f502f0dbed70ca553f900a07575ea7ae89d8fbe Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Fri, 3 Oct 2025 10:13:54 +0200 Subject: [PATCH 16/16] Moved comments --- source/sessions/snapshot-sessions.md | 9 --------- source/sessions/tests/README.md | 9 +++++++++ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/source/sessions/snapshot-sessions.md b/source/sessions/snapshot-sessions.md index bfdbfc78ee..76b984db72 100644 --- a/source/sessions/snapshot-sessions.md +++ b/source/sessions/snapshot-sessions.md @@ -124,11 +124,6 @@ Note that the `snapshotTime` property is optional. The default value of this pro Client MUST throw an error if `snapshotTime` is set and `snapshot` is not set to true. -Note that when parsing `snapshotTime` from `sessionOptions` for -[unified tests](https://github.com/mongodb/specifications/blob/master/source/unified-test-format/unified-test-format.md), -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). - ## ClientSession changes A readonly property called `snapshotTime` will be added to `ClientSession` that allows applications to retrieve the @@ -147,10 +142,6 @@ Getting the value of `snapshotTime` on a non-snapshot session MUST raise an erro Transactions are not allowed with snapshot sessions. Calling `session.startTransaction(options)` on a snapshot session MUST raise an error. -Note that a new operation on session called `getSnapshotTime` must be supported for -[unified tests](https://github.com/mongodb/specifications/blob/master/source/unified-test-format/unified-test-format.md). -This operation returns the value of `snapshotTime` on the session, so that it can be used in following operations. - ## ReadConcern changes `snapshot` added to [ReadConcernLevel enumeration](../read-write-concern/read-write-concern.md#read-concern). diff --git a/source/sessions/tests/README.md b/source/sessions/tests/README.md index 76f4f1b44d..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