Skip to content

Commit 67d6bb8

Browse files
CSHARP-3635: Require passing Versioned API options to getMore and transaction-continuing commands (#531)
1 parent 89f8f20 commit 67d6bb8

File tree

9 files changed

+148
-293
lines changed

9 files changed

+148
-293
lines changed

src/MongoDB.Driver.Core/Core/WireProtocol/CommandUsingCommandMessageWireProtocol.cs

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -376,16 +376,20 @@ private Type0CommandMessageSection<BsonDocument> CreateType0Section(ConnectionDe
376376
{
377377
AddIfNotAlreadyAdded("readConcern", readConcern);
378378
}
379-
if (_serverApi != null)
380-
{
381-
AddServerApiElementsIfNecessaryAndNotAlreadyAdded();
382-
}
383379
}
384380
AddIfNotAlreadyAdded("autocommit", false);
385381
}
386-
else if (_serverApi != null)
382+
if (_serverApi != null)
387383
{
388-
AddServerApiElementsIfNecessaryAndNotAlreadyAdded();
384+
AddIfNotAlreadyAdded("apiVersion", _serverApi.Version.ToString());
385+
if (_serverApi.Strict.HasValue)
386+
{
387+
AddIfNotAlreadyAdded("apiStrict", _serverApi.Strict.Value);
388+
}
389+
if (_serverApi.DeprecationErrors.HasValue)
390+
{
391+
AddIfNotAlreadyAdded("apiDeprecationErrors", _serverApi.DeprecationErrors.Value);
392+
}
389393
}
390394

391395
var elementAppendingSerializer = new ElementAppendingSerializer<BsonDocument>(BsonDocumentSerializer.Instance, extraElements, writerSettingsConfigurator);
@@ -411,25 +415,6 @@ bool IsSessionAcknowledged()
411415
return true;
412416
}
413417
}
414-
415-
void AddServerApiElementsIfNecessaryAndNotAlreadyAdded()
416-
{
417-
var commandName = _command.GetElement(0).Name;
418-
if (commandName == "getMore")
419-
{
420-
return;
421-
}
422-
423-
AddIfNotAlreadyAdded("apiVersion", _serverApi.Version.ToString());
424-
if (_serverApi.Strict.HasValue)
425-
{
426-
AddIfNotAlreadyAdded("apiStrict", _serverApi.Strict.Value);
427-
}
428-
if (_serverApi.DeprecationErrors.HasValue)
429-
{
430-
AddIfNotAlreadyAdded("apiDeprecationErrors", _serverApi.DeprecationErrors.Value);
431-
}
432-
}
433418
}
434419

435420
private bool IsRetryableWriteExceptionAndDeploymentDoesNotSupportRetryableWrites(MongoCommandException exception)

src/MongoDB.Driver.Core/Core/WireProtocol/CommandUsingQueryMessageWireProtocol.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ private BsonDocument WrapCommandForQueryMessage(BsonDocument command, Connection
356356
}
357357
}
358358
}
359-
if (!_session.IsInTransaction && _serverApi != null)
359+
if (_serverApi != null)
360360
{
361361
extraElements.Add(new BsonElement("apiVersion", _serverApi.Version.ToString()));
362362
if (_serverApi.Strict.HasValue)

tests/MongoDB.Driver.Core.Tests/Core/WireProtocol/CommandWriteProtocolTests.cs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ namespace MongoDB.Driver.Core.WireProtocol
4040
{
4141
public class CommandWriteProtocolTests
4242
{
43+
private static readonly ClusterId __clusterId = new ClusterId();
44+
private static readonly ServerId __serverId = new ServerId(__clusterId, new DnsEndPoint("localhost", 27017));
45+
private static readonly ConnectionDescription __connectionDescription = new ConnectionDescription(
46+
new ConnectionId(__serverId),
47+
new IsMasterResult(new BsonDocument("ok", 1).Add("ismaster", 1)),
48+
new BuildInfoResult(new BsonDocument("version", "4.9.0")));
49+
4350
[Theory]
4451
[ParameterAttributeData]
4552
public void Execute_should_use_cached_IWireProtocol_if_available([Values(false, true)] bool withSameConnection)
@@ -121,6 +128,106 @@ ConnectionId SetupConnection(Mock<IConnection> connection, ConnectionId id = nul
121128
}
122129
}
123130

131+
[Theory]
132+
[ParameterAttributeData]
133+
public void Execute_should_use_serverApi_with_getMoreCommand(
134+
[Values(false, true)] bool useServerApi,
135+
[Values(false, true)] bool async)
136+
{
137+
var serverApi = useServerApi ? new ServerApi(ServerApiVersion.V1, true, true) : null;
138+
139+
var connection = new MockConnection();
140+
connection.Description = __connectionDescription;
141+
var commandResponse = MessageHelper.BuildCommandResponse(CreateRawBsonDocument(new BsonDocument("ok", 1)));
142+
connection.EnqueueCommandResponseMessage(commandResponse);
143+
var subject = new CommandWireProtocol<BsonDocument>(
144+
NoCoreSession.Instance,
145+
ReadPreference.Primary,
146+
new DatabaseNamespace("test"),
147+
new BsonDocument("getMore", 1),
148+
commandPayloads: null,
149+
NoOpElementNameValidator.Instance,
150+
additionalOptions: null,
151+
postWriteAction: null,
152+
CommandResponseHandling.Return,
153+
BsonDocumentSerializer.Instance,
154+
new MessageEncoderSettings(),
155+
serverApi);
156+
157+
if (async)
158+
{
159+
subject.ExecuteAsync(connection, CancellationToken.None).GetAwaiter().GetResult();
160+
}
161+
else
162+
{
163+
subject.Execute(connection, CancellationToken.None);
164+
}
165+
166+
SpinWait.SpinUntil(() => connection.GetSentMessages().Count >= 1, TimeSpan.FromSeconds(4)).Should().BeTrue();
167+
168+
var sentMessages = MessageHelper.TranslateMessagesToBsonDocuments(connection.GetSentMessages());
169+
sentMessages.Count.Should().Be(1);
170+
var actualRequestId = sentMessages[0]["requestId"].AsInt32;
171+
var expectedServerApiString = useServerApi ? ", apiVersion : \"1\", apiStrict : true, apiDeprecationErrors : true" : "";
172+
sentMessages[0].Should().Be($"{{ opcode : \"opmsg\", requestId : {actualRequestId}, responseTo : 0, sections : [ {{ payloadType : 0, document : {{ getMore : 1, $db : \"test\"{expectedServerApiString} }} }} ] }}");
173+
}
174+
175+
[Theory]
176+
[ParameterAttributeData]
177+
public void Execute_should_use_serverApi_in_transaction(
178+
[Values(false, true)] bool useServerApi,
179+
[Values(false, true)] bool async)
180+
{
181+
var serverApi = useServerApi ? new ServerApi(ServerApiVersion.V1, true, true) : null;
182+
183+
var connection = new MockConnection();
184+
connection.Description = __connectionDescription;
185+
var commandResponse = MessageHelper.BuildCommandResponse(CreateRawBsonDocument(new BsonDocument("ok", 1)));
186+
connection.EnqueueCommandResponseMessage(commandResponse);
187+
var subject = new CommandWireProtocol<BsonDocument>(
188+
CreateMockSessionInTransaction(),
189+
ReadPreference.Primary,
190+
new DatabaseNamespace("test"),
191+
new BsonDocument("moreGet", 1),
192+
commandPayloads: null,
193+
NoOpElementNameValidator.Instance,
194+
additionalOptions: null,
195+
postWriteAction: null,
196+
CommandResponseHandling.Return,
197+
BsonDocumentSerializer.Instance,
198+
new MessageEncoderSettings(),
199+
serverApi);
200+
201+
if (async)
202+
{
203+
subject.ExecuteAsync(connection, CancellationToken.None).GetAwaiter().GetResult();
204+
}
205+
else
206+
{
207+
subject.Execute(connection, CancellationToken.None);
208+
}
209+
210+
SpinWait.SpinUntil(() => connection.GetSentMessages().Count >= 1, TimeSpan.FromSeconds(4)).Should().BeTrue();
211+
212+
var sentMessages = MessageHelper.TranslateMessagesToBsonDocuments(connection.GetSentMessages());
213+
sentMessages.Count.Should().Be(1);
214+
var actualRequestId = sentMessages[0]["requestId"].AsInt32;
215+
var expectedServerApiString = useServerApi ? ", apiVersion : \"1\", apiStrict : true, apiDeprecationErrors : true" : "";
216+
sentMessages[0].Should().Be($"{{ opcode : \"opmsg\", requestId : {actualRequestId}, responseTo : 0, sections : [ {{ payloadType : 0, document : {{ moreGet : 1, $db : \"test\", txnNumber : NumberLong(1), autocommit : false{expectedServerApiString} }} }} ] }}");
217+
218+
ICoreSession CreateMockSessionInTransaction()
219+
{
220+
var transaction = new CoreTransaction(1, new TransactionOptions());
221+
transaction.SetState(CoreTransactionState.InProgress);
222+
223+
var mockSession = new Mock<ICoreSession>();
224+
mockSession.SetupGet(m => m.CurrentTransaction).Returns(transaction);
225+
mockSession.SetupGet(m => m.IsInTransaction).Returns(true);
226+
227+
return mockSession.Object;
228+
}
229+
}
230+
124231
[Fact]
125232
public void Execute_should_wait_for_response_when_CommandResponseHandling_is_Return()
126233
{

tests/MongoDB.Driver.Tests/Specifications/versioned-api/tests/crud-api-version-1-strict.json

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,7 @@
651651
]
652652
},
653653
{
654-
"description": "find command with declared API version appends to the command, but getMore does not",
654+
"description": "find and getMore append API version",
655655
"operations": [
656656
{
657657
"name": "find",
@@ -712,14 +712,10 @@
712712
"long"
713713
]
714714
},
715-
"apiVersion": {
716-
"$$exists": false
717-
},
718-
"apiStrict": {
719-
"$$exists": false
720-
},
715+
"apiVersion": "1",
716+
"apiStrict": true,
721717
"apiDeprecationErrors": {
722-
"$$exists": false
718+
"$$unsetOrMatches": false
723719
}
724720
}
725721
}

tests/MongoDB.Driver.Tests/Specifications/versioned-api/tests/crud-api-version-1-strict.yml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ tests:
240240
- $group: { _id: 1, n: { $sum: $count }}
241241
<<: *expectedApiVersion
242242

243-
- description: "find command with declared API version appends to the command, but getMore does not"
243+
- description: "find and getMore append API version"
244244
operations:
245245
- name: find
246246
object: *collection
@@ -264,9 +264,7 @@ tests:
264264
- commandStartedEvent:
265265
command:
266266
getMore: { $$type: [ int, long ] }
267-
apiVersion: { $$exists: false }
268-
apiStrict: { $$exists: false }
269-
apiDeprecationErrors: { $$exists: false }
267+
<<: *expectedApiVersion
270268

271269
- description: "findOneAndDelete appends declared API version"
272270
operations:

tests/MongoDB.Driver.Tests/Specifications/versioned-api/tests/crud-api-version-1.json

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -643,7 +643,7 @@
643643
]
644644
},
645645
{
646-
"description": "find command with declared API version appends to the command, but getMore does not",
646+
"description": "find and getMore append API version",
647647
"operations": [
648648
{
649649
"name": "find",
@@ -704,15 +704,11 @@
704704
"long"
705705
]
706706
},
707-
"apiVersion": {
708-
"$$exists": false
709-
},
707+
"apiVersion": "1",
710708
"apiStrict": {
711-
"$$exists": false
709+
"$$unsetOrMatches": false
712710
},
713-
"apiDeprecationErrors": {
714-
"$$exists": false
715-
}
711+
"apiDeprecationErrors": true
716712
}
717713
}
718714
}

tests/MongoDB.Driver.Tests/Specifications/versioned-api/tests/crud-api-version-1.yml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ tests:
234234
- $group: { _id: 1, n: { $sum: $count }}
235235
<<: *expectedApiVersion
236236

237-
- description: "find command with declared API version appends to the command, but getMore does not"
237+
- description: "find and getMore append API version"
238238
operations:
239239
- name: find
240240
object: *collection
@@ -258,9 +258,7 @@ tests:
258258
- commandStartedEvent:
259259
command:
260260
getMore: { $$type: [ int, long ] }
261-
apiVersion: { $$exists: false }
262-
apiStrict: { $$exists: false }
263-
apiDeprecationErrors: { $$exists: false }
261+
<<: *expectedApiVersion
264262

265263
- description: "findOneAndDelete appends declared API version"
266264
operations:

0 commit comments

Comments
 (0)