Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
804 changes: 804 additions & 0 deletions specifications/sessions/tests/snapshot-sessions.json

Large diffs are not rendered by default.

410 changes: 409 additions & 1 deletion specifications/sessions/tests/snapshot-sessions.yml

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/MongoDB.Driver/ClientSessionHandle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ public IServerSession ServerSession
}
}

public BsonTimestamp SnapshotTime => _coreSession.SnapshotTime;

/// <inheritdoc />
public ICoreSessionHandle WrappedCoreSession => _coreSession;

Expand Down
10 changes: 9 additions & 1 deletion src/MongoDB.Driver/ClientSessionOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
*/

using System;
using MongoDB.Bson;
using MongoDB.Driver.Core.Bindings;

namespace MongoDB.Driver
Expand Down Expand Up @@ -46,6 +47,12 @@ public class ClientSessionOptions
/// </value>
public bool Snapshot { get; set;}

/// <summary>
/// Gets or sets the snapshot time. If set, Snapshot must be true.
/// <value> The snapshot time </value>
/// </summary>
public BsonTimestamp SnapshotTime { get; set; }

// internal methods
internal CoreSessionOptions ToCore(bool isImplicit = false)
{
Expand All @@ -55,7 +62,8 @@ internal CoreSessionOptions ToCore(bool isImplicit = false)
isCausallyConsistent: isCausallyConsistent,
isImplicit: isImplicit,
isSnapshot: Snapshot,
defaultTransactionOptions: DefaultTransactionOptions);
defaultTransactionOptions: DefaultTransactionOptions,
snapshotTime: SnapshotTime);
}
}
}
1 change: 1 addition & 0 deletions src/MongoDB.Driver/Core/Bindings/CoreSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ private CoreSession(
{
_cluster = Ensure.IsNotNull(cluster, nameof(cluster));
_options = Ensure.IsNotNull(options, nameof(options));
_snapshotTime = options.SnapshotTime;
}

// public properties
Expand Down
29 changes: 28 additions & 1 deletion src/MongoDB.Driver/Core/Bindings/CoreSessionOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
* limitations under the License.
*/

using MongoDB.Bson;

namespace MongoDB.Driver.Core.Bindings
{
/// <summary>
Expand All @@ -25,6 +27,7 @@ public class CoreSessionOptions
private readonly bool _isCausallyConsistent;
private readonly bool _isImplicit;
private readonly bool _isSnapshot;
private readonly BsonTimestamp _snapshotTime;

// constructors
/// <summary>
Expand All @@ -34,16 +37,35 @@ public class CoreSessionOptions
/// <param name="isImplicit">if set to <c>true</c> this session is an implicit session.</param>
/// <param name="isSnapshot">if set to <c>true</c> this session is a snapshot session.</param>
/// <param name="defaultTransactionOptions">The default transaction options.</param>
/// <param name="snapshotTime">The snapshot time. If this is set, isSnapshot must be true.</param>
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;
}

/// <summary>
/// Initializes a new instance of the <see cref="CoreSessionOptions" /> class.
/// </summary>
/// <param name="isCausallyConsistent">if set to <c>true</c> this session is causally consistent]</param>
/// <param name="isImplicit">if set to <c>true</c> this session is an implicit session.</param>
/// <param name="isSnapshot">if set to <c>true</c> this session is a snapshot session.</param>
/// <param name="defaultTransactionOptions">The default transaction options.</param>
public CoreSessionOptions(
bool isCausallyConsistent,
bool isImplicit,
TransactionOptions defaultTransactionOptions,
bool isSnapshot)
: this(isCausallyConsistent, isImplicit, defaultTransactionOptions, isSnapshot, null)
{
}

// public properties
Expand Down Expand Up @@ -78,5 +100,10 @@ public CoreSessionOptions(
/// <c>true</c> if this session is a snapshot session; otherwise, <c>false</c>.
/// </value>
public bool IsSnapshot => _isSnapshot;

/// <summary>
/// //TODO
/// </summary>
public BsonTimestamp SnapshotTime => _snapshotTime;
}
}
17 changes: 17 additions & 0 deletions src/MongoDB.Driver/IClientSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,23 @@

namespace MongoDB.Driver
{
/// <summary>
/// //TODO
/// </summary>
public static class ClientSessionExtensions
{
//TODO This will need to be moved somewhere else
/// <summary>
/// //TODO
/// </summary>
/// <param name="session"></param>
/// <returns></returns>
public static BsonTimestamp GetSnapshotTime(this IClientSessionHandle session)
{
return ((ClientSessionHandle)session).SnapshotTime;
}
}

/// <summary>
/// The interface for a client session.
/// </summary>
Expand Down
12 changes: 10 additions & 2 deletions src/MongoDB.Driver/MongoClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -622,9 +622,17 @@ private RenderArgs<BsonDocument> 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();
Expand Down
208 changes: 208 additions & 0 deletions tests/MongoDB.Driver.Tests/AtClusterTimeTests.cs
Original file line number Diff line number Diff line change
@@ -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<AtClusterTimeTests.ClassFixture>
{
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<TestObject> GetTestObjects(IMongoCollection<TestObject> collection, IClientSessionHandle session)
{
var filterDefinition = Builders<TestObject>.Filter.Empty;
var sortDefinition = Builders<TestObject>.Sort.Ascending(o => o.Name);
return collection.Find(session, filterDefinition).Sort(sortDefinition).ToList();
}

void AssertOneObj(List<TestObject> objs)
{
Assert.Equal(1, objs.Count);
Assert.Equal("obj1", objs[0].Name);
}

void AssertTwoObjs(List<TestObject> objs)
{
Assert.Equal(2, objs.Count);
Assert.Equal("obj1", objs[0].Name);
Assert.Equal("obj2", objs[1].Name);
}

public class ClassFixture : MongoCollectionFixture<TestObject>
{
public override bool InitializeDataBeforeEachTestCase => true;
protected override IEnumerable<TestObject> InitialData => [new() { Name = "obj1" }] ;
}

public class TestObject
{
[BsonId]
public ObjectId Id { get; set; }
public string Name { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<NotSupportedException>();
}


private sealed class MongocryptdContext : IDisposable
{
public IMongoClient MongoClient { get; }
Expand Down
Loading
Loading