diff --git a/specifications/sessions/tests/snapshot-sessions.json b/specifications/sessions/tests/snapshot-sessions.json
index 260f8b6f489..8f806ea7595 100644
--- a/specifications/sessions/tests/snapshot-sessions.json
+++ b/specifications/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/specifications/sessions/tests/snapshot-sessions.yml b/specifications/sessions/tests/snapshot-sessions.yml
index bcf0f7eec6b..48cf415b4aa 100644
--- a/specifications/sessions/tests/snapshot-sessions.yml
+++ b/specifications/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
diff --git a/src/MongoDB.Driver/ClientSessionHandle.cs b/src/MongoDB.Driver/ClientSessionHandle.cs
index 144e6a94991..084c4db740b 100644
--- a/src/MongoDB.Driver/ClientSessionHandle.cs
+++ b/src/MongoDB.Driver/ClientSessionHandle.cs
@@ -89,6 +89,8 @@ public IServerSession ServerSession
}
}
+ public BsonTimestamp SnapshotTime => _coreSession.SnapshotTime;
+
///
public ICoreSessionHandle WrappedCoreSession => _coreSession;
diff --git a/src/MongoDB.Driver/ClientSessionOptions.cs b/src/MongoDB.Driver/ClientSessionOptions.cs
index f4b7f80ccac..3ff667ada60 100644
--- a/src/MongoDB.Driver/ClientSessionOptions.cs
+++ b/src/MongoDB.Driver/ClientSessionOptions.cs
@@ -14,6 +14,7 @@
*/
using System;
+using MongoDB.Bson;
using MongoDB.Driver.Core.Bindings;
namespace MongoDB.Driver
@@ -46,6 +47,12 @@ public class ClientSessionOptions
///
public bool Snapshot { get; set;}
+ ///
+ /// Gets or sets the snapshot time. If set, Snapshot must be true.
+ /// The snapshot time
+ ///
+ public BsonTimestamp SnapshotTime { get; set; }
+
// internal methods
internal CoreSessionOptions ToCore(bool isImplicit = false)
{
@@ -55,7 +62,8 @@ internal CoreSessionOptions ToCore(bool isImplicit = false)
isCausallyConsistent: isCausallyConsistent,
isImplicit: isImplicit,
isSnapshot: Snapshot,
- defaultTransactionOptions: DefaultTransactionOptions);
+ defaultTransactionOptions: DefaultTransactionOptions,
+ snapshotTime: SnapshotTime);
}
}
}
diff --git a/src/MongoDB.Driver/Core/Bindings/CoreSession.cs b/src/MongoDB.Driver/Core/Bindings/CoreSession.cs
index 954c639e244..d3366cdc188 100644
--- a/src/MongoDB.Driver/Core/Bindings/CoreSession.cs
+++ b/src/MongoDB.Driver/Core/Bindings/CoreSession.cs
@@ -71,6 +71,7 @@ private CoreSession(
{
_cluster = Ensure.IsNotNull(cluster, nameof(cluster));
_options = Ensure.IsNotNull(options, nameof(options));
+ _snapshotTime = options.SnapshotTime;
}
// public properties
diff --git a/src/MongoDB.Driver/Core/Bindings/CoreSessionOptions.cs b/src/MongoDB.Driver/Core/Bindings/CoreSessionOptions.cs
index d5697312fcf..d0bc03f2fe6 100644
--- a/src/MongoDB.Driver/Core/Bindings/CoreSessionOptions.cs
+++ b/src/MongoDB.Driver/Core/Bindings/CoreSessionOptions.cs
@@ -13,6 +13,8 @@
* limitations under the License.
*/
+using MongoDB.Bson;
+
namespace MongoDB.Driver.Core.Bindings
{
///
@@ -25,6 +27,7 @@ public class CoreSessionOptions
private readonly bool _isCausallyConsistent;
private readonly bool _isImplicit;
private readonly bool _isSnapshot;
+ private readonly BsonTimestamp _snapshotTime;
// constructors
///
@@ -34,16 +37,35 @@ public class CoreSessionOptions
/// if set to true this session is an implicit session.
/// if set to true this session is a snapshot session.
/// The default transaction options.
+ /// The snapshot time. If this is set, isSnapshot must be true.
public CoreSessionOptions(
bool isCausallyConsistent = false,
bool isImplicit = false,
TransactionOptions defaultTransactionOptions = null,
- bool isSnapshot = false)
+ bool isSnapshot = false,
+ BsonTimestamp snapshotTime = null)
{
_isCausallyConsistent = isCausallyConsistent;
_isImplicit = isImplicit;
_isSnapshot = isSnapshot;
_defaultTransactionOptions = defaultTransactionOptions;
+ _snapshotTime = snapshotTime;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// if set to true this session is causally consistent]
+ /// if set to true this session is an implicit session.
+ /// if set to true this session is a snapshot session.
+ /// The default transaction options.
+ public CoreSessionOptions(
+ bool isCausallyConsistent,
+ bool isImplicit,
+ TransactionOptions defaultTransactionOptions,
+ bool isSnapshot)
+ : this(isCausallyConsistent, isImplicit, defaultTransactionOptions, isSnapshot, null)
+ {
}
// public properties
@@ -78,5 +100,10 @@ public CoreSessionOptions(
/// true if this session is a snapshot session; otherwise, false.
///
public bool IsSnapshot => _isSnapshot;
+
+ ///
+ /// //TODO
+ ///
+ public BsonTimestamp SnapshotTime => _snapshotTime;
}
}
diff --git a/src/MongoDB.Driver/IClientSession.cs b/src/MongoDB.Driver/IClientSession.cs
index a0809fcae3c..7e17f1331ac 100644
--- a/src/MongoDB.Driver/IClientSession.cs
+++ b/src/MongoDB.Driver/IClientSession.cs
@@ -21,6 +21,23 @@
namespace MongoDB.Driver
{
+ ///
+ /// //TODO
+ ///
+ public static class ClientSessionExtensions
+ {
+ //TODO This will need to be moved somewhere else
+ ///
+ /// //TODO
+ ///
+ ///
+ ///
+ public static BsonTimestamp GetSnapshotTime(this IClientSessionHandle session)
+ {
+ return ((ClientSessionHandle)session).SnapshotTime;
+ }
+ }
+
///
/// The interface for a client session.
///
diff --git a/src/MongoDB.Driver/MongoClient.cs b/src/MongoDB.Driver/MongoClient.cs
index 4b70bfdfd16..833991d511c 100644
--- a/src/MongoDB.Driver/MongoClient.cs
+++ b/src/MongoDB.Driver/MongoClient.cs
@@ -622,9 +622,17 @@ private RenderArgs GetRenderArgs()
private IClientSessionHandle StartSession(ClientSessionOptions options)
{
- if (options != null && options.Snapshot && options.CausalConsistency == true)
+ if (options != null)
{
- throw new NotSupportedException("Combining both causal consistency and snapshot options is not supported.");
+ if (options.SnapshotTime != null && !options.Snapshot)
+ {
+ throw new NotSupportedException("Specifying a snapshot time requires snapshot to be true.");
+ }
+
+ if (options.Snapshot && options.CausalConsistency == true)
+ {
+ throw new NotSupportedException("Combining both causal consistency and snapshot options is not supported.");
+ }
}
options ??= new ClientSessionOptions();
diff --git a/tests/MongoDB.Driver.Tests/AtClusterTimeTests.cs b/tests/MongoDB.Driver.Tests/AtClusterTimeTests.cs
new file mode 100644
index 00000000000..a97e96343bf
--- /dev/null
+++ b/tests/MongoDB.Driver.Tests/AtClusterTimeTests.cs
@@ -0,0 +1,208 @@
+/* Copyright 2010-present MongoDB Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Collections.Generic;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+using MongoDB.Driver.Core.Clusters;
+using MongoDB.Driver.Core.Misc;
+using MongoDB.Driver.TestHelpers;
+using Xunit;
+
+namespace MongoDB.Driver.Tests;
+//TODO This file will need to be deleted, but it's useful for testing at the moment
+public class AtClusterTimeTests : IntegrationTest
+{
+ public AtClusterTimeTests(ClassFixture fixture)
+ : base(fixture, server => server.Supports(Feature.SnapshotReads).ClusterType(ClusterType.ReplicaSet))
+ {
+ }
+
+ [Fact]
+ public void MainTest()
+ {
+ var client = Fixture.Client;
+ var collection = Fixture.Collection;
+
+ BsonTimestamp clusterTime1;
+
+ var sessionOptions1 = new ClientSessionOptions
+ {
+ Snapshot = true
+ };
+
+ using (var session1 = client.StartSession(sessionOptions1))
+ {
+ var results = GetTestObjects(collection, session1);
+ AssertOneObj(results);
+
+ clusterTime1 = session1.GetSnapshotTime();
+ Assert.NotEqual(null, clusterTime1);
+ }
+
+ var obj2 = new TestObject { Name = "obj2" };
+ collection.InsertOne(obj2);
+
+ var sessionOptions2 = new ClientSessionOptions
+ {
+ Snapshot = true,
+ SnapshotTime = clusterTime1
+ };
+
+ //Snapshot read session at clusterTime1 should not see obj2
+ using (var session2 = client.StartSession(sessionOptions2))
+ {
+ var results = GetTestObjects(collection, session2);
+ AssertOneObj(results);
+
+ var clusterTime2 = session2.GetSnapshotTime();
+ Assert.Equal(clusterTime2, clusterTime1);
+ }
+
+ var sessionOptions3 = new ClientSessionOptions
+ {
+ Snapshot = true,
+ };
+
+ //Snapshot read session without cluster time should see obj2
+ using (var session3 = client.StartSession(sessionOptions3))
+ {
+ var results = GetTestObjects(collection, session3);
+ AssertTwoObjs(results);
+
+ var clusterTime3 = session3.GetSnapshotTime();
+ Assert.NotEqual(clusterTime3, clusterTime1);
+ }
+ }
+
+ [Fact]
+ public void IncreasedTimestamp()
+ {
+ var client = Fixture.Client;
+ var collection = Fixture.Collection;
+
+ BsonTimestamp clusterTime1;
+
+ var sessionOptions1 = new ClientSessionOptions
+ {
+ Snapshot = true
+ };
+
+ using (var session1 = client.StartSession(sessionOptions1))
+ {
+ var results = GetTestObjects(collection, session1);
+ AssertOneObj(results);
+
+ clusterTime1 = session1.GetSnapshotTime();
+ Assert.NotEqual(null, clusterTime1);
+ }
+
+ var obj2 = new TestObject { Name = "obj2" };
+ collection.InsertOne(obj2);
+
+ var modifiedClusterTime = new BsonTimestamp(clusterTime1.Value + 1);
+ var sessionOptions2 = new ClientSessionOptions
+ {
+ Snapshot = true,
+ SnapshotTime = modifiedClusterTime
+ };
+
+ //Snapshot read session at clusterTime1+1 should see obj2
+ using (var session2 = client.StartSession(sessionOptions2))
+ {
+ var results = GetTestObjects(collection, session2);
+ AssertTwoObjs(results);
+
+ var clusterTime2 = session2.GetSnapshotTime();
+ Assert.Equal(modifiedClusterTime, clusterTime2);
+ }
+ }
+
+ [Fact]
+ public void DecreasedTimestamp()
+ {
+ var client = Fixture.Client;
+ var collection = Fixture.Collection;
+
+ BsonTimestamp clusterTime1;
+
+ var sessionOptions1 = new ClientSessionOptions
+ {
+ Snapshot = true
+ };
+
+ using (var session1 = client.StartSession(sessionOptions1))
+ {
+ var results = GetTestObjects(collection, session1);
+ AssertOneObj(results);
+
+ clusterTime1 = session1.GetSnapshotTime();
+ Assert.NotEqual(null, clusterTime1);
+ }
+
+ var obj2 = new TestObject { Name = "obj2" };
+ collection.InsertOne(obj2);
+
+ var modifiedClusterTime = new BsonTimestamp(clusterTime1.Value - 1);
+ var sessionOptions2 = new ClientSessionOptions
+ {
+ Snapshot = true,
+ SnapshotTime = modifiedClusterTime
+ };
+
+ //Snapshot read session at clusterTime1-1 should not see obj2
+ using (var session2 = client.StartSession(sessionOptions2))
+ {
+ var results = GetTestObjects(collection, session2);
+ Assert.Equal(0, results.Count);
+
+ var clusterTime2 = session2.GetSnapshotTime();
+ Assert.Equal(modifiedClusterTime, clusterTime2);
+ }
+ }
+
+ List GetTestObjects(IMongoCollection collection, IClientSessionHandle session)
+ {
+ var filterDefinition = Builders.Filter.Empty;
+ var sortDefinition = Builders.Sort.Ascending(o => o.Name);
+ return collection.Find(session, filterDefinition).Sort(sortDefinition).ToList();
+ }
+
+ void AssertOneObj(List objs)
+ {
+ Assert.Equal(1, objs.Count);
+ Assert.Equal("obj1", objs[0].Name);
+ }
+
+ void AssertTwoObjs(List objs)
+ {
+ Assert.Equal(2, objs.Count);
+ Assert.Equal("obj1", objs[0].Name);
+ Assert.Equal("obj2", objs[1].Name);
+ }
+
+ public class ClassFixture : MongoCollectionFixture
+ {
+ public override bool InitializeDataBeforeEachTestCase => true;
+ protected override IEnumerable InitialData => [new() { Name = "obj1" }] ;
+ }
+
+ public class TestObject
+ {
+ [BsonId]
+ public ObjectId Id { get; set; }
+ public string Name { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/tests/MongoDB.Driver.Tests/Specifications/sessions/SessionsProseTests.cs b/tests/MongoDB.Driver.Tests/Specifications/sessions/SessionsProseTests.cs
index 649e2ae2df9..40244a88523 100644
--- a/tests/MongoDB.Driver.Tests/Specifications/sessions/SessionsProseTests.cs
+++ b/tests/MongoDB.Driver.Tests/Specifications/sessions/SessionsProseTests.cs
@@ -357,6 +357,24 @@ public void Ensure_cluster_times_are_not_gossiped_on_SDAM_commands()
commandStartedEvents[0].Command["$clusterTime"].Should().Be(clusterTime);
}
+ [Fact]
+ public void If_SnapshotTime_is_set_Snapshot_must_be_true()
+ {
+ RequireServer.Check();
+
+ var sessionOptions = new ClientSessionOptions
+ {
+ Snapshot = false,
+ SnapshotTime = new BsonTimestamp(1, 1)
+ };
+
+ var mongoClient = DriverTestConfiguration.Client;
+
+ var exception = Record.Exception(() => mongoClient.StartSession(sessionOptions));
+ exception.Should().BeOfType();
+ }
+
+
private sealed class MongocryptdContext : IDisposable
{
public IMongoClient MongoClient { get; }
diff --git a/tests/MongoDB.Driver.Tests/UnifiedTestOperations/UnifiedEntityMap.cs b/tests/MongoDB.Driver.Tests/UnifiedTestOperations/UnifiedEntityMap.cs
index e5ea553ff15..cea7cc56391 100644
--- a/tests/MongoDB.Driver.Tests/UnifiedTestOperations/UnifiedEntityMap.cs
+++ b/tests/MongoDB.Driver.Tests/UnifiedTestOperations/UnifiedEntityMap.cs
@@ -1056,6 +1056,9 @@ private IClientSessionHandle CreateSession(BsonDocument entity, Dictionary GetSnapshotTime();
+
+ //TODO Do we necessarily need an async version of this...?
+ public Task ExecuteAsync(CancellationToken cancellationToken) => Task.FromResult(GetSnapshotTime());
+
+ private OperationResult GetSnapshotTime()
+ {
+ try
+ {
+ return OperationResult.FromResult(_session.GetSnapshotTime());
+ }
+ catch (Exception exception)
+ {
+ return OperationResult.FromException(exception);
+ }
+ }
+}
+
+public class UnifiedGetSnapshotOperationBuilder
+{
+ private readonly UnifiedEntityMap _entityMap;
+
+ public UnifiedGetSnapshotOperationBuilder(UnifiedEntityMap entityMap)
+ {
+ _entityMap = entityMap;
+ }
+
+ public UnifiedGetSnapshotOperation Build(string targetSessionId, BsonDocument arguments)
+ {
+ if (arguments != null)
+ {
+ throw new FormatException("GetSnapshotTime is not expected to contain arguments.");
+ }
+
+ var session = _entityMap.Sessions[targetSessionId];
+ return new UnifiedGetSnapshotOperation(session);
+ }
+}
\ No newline at end of file
diff --git a/tests/MongoDB.Driver.Tests/UnifiedTestOperations/UnifiedTestOperationFactory.cs b/tests/MongoDB.Driver.Tests/UnifiedTestOperations/UnifiedTestOperationFactory.cs
index 32e6ec08f3d..099ae07359f 100644
--- a/tests/MongoDB.Driver.Tests/UnifiedTestOperations/UnifiedTestOperationFactory.cs
+++ b/tests/MongoDB.Driver.Tests/UnifiedTestOperations/UnifiedTestOperationFactory.cs
@@ -133,6 +133,7 @@ public IUnifiedTestOperation CreateOperation(string operationName, string target
"abortTransaction" => new UnifiedAbortTransactionOperationBuilder(_entityMap).Build(targetEntityId, operationArguments),
"commitTransaction" => new UnifiedCommitTransactionOperationBuilder(_entityMap).Build(targetEntityId, operationArguments),
"endSession" => new UnifiedEndSessionOperationBuilder(_entityMap).Build(targetEntityId, operationArguments),
+ "getSnapshotTime" => new UnifiedGetSnapshotOperationBuilder(_entityMap).Build(targetEntityId, operationArguments),
"startTransaction" => new UnifiedStartTransactionOperationBuilder(_entityMap).Build(targetEntityId, operationArguments),
"withTransaction" => new UnifiedWithTransactionOperationBuilder(_entityMap).Build(targetEntityId, operationArguments),
_ => throw new FormatException($"Invalid method name: '{operationName}'."),