Skip to content

Commit fb69310

Browse files
committed
CSHARP-1437: Detect write concern errors in FindAndModify operations.
1 parent c177452 commit fb69310

File tree

6 files changed

+145
-8
lines changed

6 files changed

+145
-8
lines changed

src/MongoDB.Bson/ObjectModel/RawBsonDocument.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,22 @@ public override void InsertAt(int index, BsonElement element)
689689
throw new NotSupportedException("RawBsonDocument instances are immutable.");
690690
}
691691

692+
/// <summary>
693+
/// Materializes the RawBsonDocument into a regular BsonDocument.
694+
/// </summary>
695+
/// <param name="binaryReaderSettings">The binary reader settings.</param>
696+
/// <returns>A BsonDocument.</returns>
697+
public BsonDocument Materialize(BsonBinaryReaderSettings binaryReaderSettings)
698+
{
699+
ThrowIfDisposed();
700+
using (var stream = new ByteBufferStream(_slice, ownsBuffer: false))
701+
using (var reader = new BsonBinaryReader(stream, binaryReaderSettings))
702+
{
703+
var context = BsonDeserializationContext.CreateRoot(reader);
704+
return BsonDocumentSerializer.Instance.Deserialize(context);
705+
}
706+
}
707+
692708
/// <summary>
693709
/// Merges another document into this one. Existing elements are not overwritten.
694710
/// </summary>

src/MongoDB.Driver.Core.Tests/Core/Operations/FindOneAndDeleteOperationTests.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright 2013-2014 MongoDB Inc.
1+
/* Copyright 2013-2015 MongoDB Inc.
22
*
33
* Licensed under the Apache License, Version 2.0 (the "License");
44
* you may not use this file except in compliance with the License.
@@ -136,6 +136,30 @@ public void Execute_against_an_existing_document(
136136
serverList.Should().BeEmpty();
137137
}
138138

139+
[Test]
140+
[RequiresServer("EnsureTestData", MinimumVersion = "3.2.0-rc0", ClusterTypes = ClusterTypes.ReplicaSet)]
141+
public void Execute_should_throw_when_there_is_a_write_concern_error(
142+
[Values(false, true)] bool async)
143+
{
144+
var subject = new FindOneAndDeleteOperation<BsonDocument>(
145+
_collectionNamespace,
146+
BsonDocument.Parse("{ x : 1 }"),
147+
new FindAndModifyValueDeserializer<BsonDocument>(BsonDocumentSerializer.Instance),
148+
_messageEncoderSettings)
149+
{
150+
WriteConcern = new WriteConcern(9)
151+
};
152+
153+
Action action = () => ExecuteOperation(subject, async);
154+
155+
var exception = action.ShouldThrow<MongoWriteConcernException>().Which;
156+
var commandResult = exception.Result;
157+
var result = commandResult["value"].AsBsonDocument;
158+
result.Should().Be("{_id: 10, x: 1}");
159+
var serverList = ReadAllFromCollection(async);
160+
serverList.Should().BeEmpty();
161+
}
162+
139163
[Test]
140164
[RequiresServer("EnsureTestData")]
141165
public void Execute_when_document_does_not_exist(

src/MongoDB.Driver.Core.Tests/Core/Operations/FindOneAndReplaceOperationTests.cs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright 2013-2014 MongoDB Inc.
1+
/* Copyright 2013-2015 MongoDB Inc.
22
*
33
* Licensed under the Apache License, Version 2.0 (the "License");
44
* you may not use this file except in compliance with the License.
@@ -292,6 +292,34 @@ public void Execute_against_a_non_existing_document_returning_the_replacement_wi
292292

293293
serverList[0].Should().Be("{_id: 10, a: 2}");
294294
}
295+
[Test]
296+
[RequiresServer("EnsureTestData", MinimumVersion = "3.2.0-rc0", ClusterTypes = ClusterTypes.ReplicaSet)]
297+
public void Execute_should_throw_when_there_is_a_write_concern_error(
298+
[Values(false, true)]
299+
bool async)
300+
{
301+
var subject = new FindOneAndReplaceOperation<BsonDocument>(
302+
_collectionNamespace,
303+
_filter,
304+
_replacement,
305+
new FindAndModifyValueDeserializer<BsonDocument>(BsonDocumentSerializer.Instance),
306+
_messageEncoderSettings)
307+
{
308+
BypassDocumentValidation = true,
309+
ReturnDocument = ReturnDocument.Before,
310+
WriteConcern = new WriteConcern(9)
311+
};
312+
313+
Action action = () => ExecuteOperation(subject, async);
314+
315+
var exception = action.ShouldThrow<MongoWriteConcernException>().Which;
316+
var commandResult = exception.Result;
317+
var result = commandResult["value"].AsBsonDocument;
318+
result.Should().Be("{_id: 10, x: 1}");
319+
var serverList = ReadAllFromCollection(async);
320+
serverList[0].Should().Be("{_id: 10, a: 2}");
321+
}
322+
295323

296324
private void EnsureTestData()
297325
{

src/MongoDB.Driver.Core.Tests/Core/Operations/FindOneAndUpdateOperationTests.cs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright 2013-2014 MongoDB Inc.
1+
/* Copyright 2013-2015 MongoDB Inc.
22
*
33
* Licensed under the Apache License, Version 2.0 (the "License");
44
* you may not use this file except in compliance with the License.
@@ -293,6 +293,34 @@ public void Execute_against_a_non_existing_document_returning_the_updated_with_u
293293
serverList[0].Should().Be("{_id: 10, x: 2}");
294294
}
295295

296+
[Test]
297+
[RequiresServer("EnsureTestData", MinimumVersion = "3.2.0-rc0", ClusterTypes = ClusterTypes.ReplicaSet)]
298+
public void Execute_should_throw_when_there_is_a_write_concern_error(
299+
[Values(false, true)]
300+
bool async)
301+
{
302+
var subject = new FindOneAndUpdateOperation<BsonDocument>(
303+
_collectionNamespace,
304+
_filter,
305+
_update,
306+
new FindAndModifyValueDeserializer<BsonDocument>(BsonDocumentSerializer.Instance),
307+
_messageEncoderSettings)
308+
{
309+
BypassDocumentValidation = true,
310+
ReturnDocument = ReturnDocument.Before,
311+
WriteConcern = new WriteConcern(9)
312+
};
313+
314+
Action action = () => ExecuteOperation(subject, async);
315+
316+
var exception = action.ShouldThrow<MongoWriteConcernException>().Which;
317+
var commandResult = exception.Result;
318+
var result = commandResult["value"].AsBsonDocument;
319+
result.Should().Be("{_id: 10, x: 1}");
320+
var serverList = ReadAllFromCollection(async);
321+
serverList[0].Should().Be("{_id: 10, x: 2}");
322+
}
323+
296324
private void EnsureTestData()
297325
{
298326
DropCollection();

src/MongoDB.Driver.Core/Core/Operations/FindAndModifyOperationBase.cs

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
using MongoDB.Bson.Serialization;
2525
using MongoDB.Bson.Serialization.Serializers;
2626
using MongoDB.Driver.Core.Bindings;
27+
using MongoDB.Driver.Core.Connections;
2728
using MongoDB.Driver.Core.Misc;
2829
using MongoDB.Driver.Core.WireProtocol.Messages.Encoders;
2930

@@ -109,7 +110,10 @@ public TResult Execute(IWriteBinding binding, CancellationToken cancellationToke
109110
using (var channelBinding = new ChannelReadWriteBinding(channelSource.Server, channel))
110111
{
111112
var operation = CreateOperation(channel.ConnectionDescription.ServerVersion);
112-
return operation.Execute(channelBinding, cancellationToken);
113+
using (var rawBsonDocument = operation.Execute(channelBinding, cancellationToken))
114+
{
115+
return ProcessCommandResult(channel.ConnectionDescription.ConnectionId, rawBsonDocument);
116+
}
113117
}
114118
}
115119

@@ -123,17 +127,20 @@ public async Task<TResult> ExecuteAsync(IWriteBinding binding, CancellationToken
123127
using (var channelBinding = new ChannelReadWriteBinding(channelSource.Server, channel))
124128
{
125129
var operation = CreateOperation(channel.ConnectionDescription.ServerVersion);
126-
return await operation.ExecuteAsync(channelBinding, cancellationToken).ConfigureAwait(false);
130+
using (var rawBsonDocument = await operation.ExecuteAsync(channelBinding, cancellationToken).ConfigureAwait(false))
131+
{
132+
return ProcessCommandResult(channel.ConnectionDescription.ConnectionId, rawBsonDocument);
133+
}
127134
}
128135
}
129136

130137
// private methods
131138
internal abstract BsonDocument CreateCommand(SemanticVersion serverVersion);
132139

133-
private WriteCommandOperation<TResult> CreateOperation(SemanticVersion serverVersion)
140+
private WriteCommandOperation<RawBsonDocument> CreateOperation(SemanticVersion serverVersion)
134141
{
135142
var command = CreateCommand(serverVersion);
136-
return new WriteCommandOperation<TResult>(_collectionNamespace.DatabaseNamespace, command, _resultSerializer, _messageEncoderSettings)
143+
return new WriteCommandOperation<RawBsonDocument>(_collectionNamespace.DatabaseNamespace, command, RawBsonDocumentSerializer.Instance, _messageEncoderSettings)
137144
{
138145
CommandValidator = GetCommandValidator()
139146
};
@@ -144,5 +151,31 @@ private WriteCommandOperation<TResult> CreateOperation(SemanticVersion serverVer
144151
/// </summary>
145152
/// <returns>An element name validator for the command.</returns>
146153
protected abstract IElementNameValidator GetCommandValidator();
154+
155+
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
156+
private TResult ProcessCommandResult(ConnectionId connectionId, RawBsonDocument rawBsonDocument)
157+
{
158+
var binaryReaderSettings = new BsonBinaryReaderSettings
159+
{
160+
Encoding = _messageEncoderSettings.GetOrDefault<UTF8Encoding>(MessageEncoderSettingsName.ReadEncoding, Utf8Encodings.Strict),
161+
GuidRepresentation = _messageEncoderSettings.GetOrDefault<GuidRepresentation>(MessageEncoderSettingsName.GuidRepresentation, GuidRepresentation.CSharpLegacy)
162+
};
163+
164+
BsonValue writeConcernError;
165+
if (rawBsonDocument.TryGetValue("writeConcernError", out writeConcernError))
166+
{
167+
var message = writeConcernError["errmsg"].AsString;
168+
var response = rawBsonDocument.Materialize(binaryReaderSettings);
169+
var writeConcernResult = new WriteConcernResult(response);
170+
throw new MongoWriteConcernException(connectionId, message, writeConcernResult);
171+
}
172+
173+
using (var stream = new ByteBufferStream(rawBsonDocument.Slice, ownsBuffer: false))
174+
using (var reader = new BsonBinaryReader(stream, binaryReaderSettings))
175+
{
176+
var context = BsonDeserializationContext.CreateRoot(reader);
177+
return _resultSerializer.Deserialize(context);
178+
}
179+
}
147180
}
148181
}

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
*/
1515

1616
using System;
17+
using System.Text;
1718
using System.Threading;
1819
using System.Threading.Tasks;
1920
using MongoDB.Bson;
@@ -154,7 +155,14 @@ private TCommandResult ProcessReply(ConnectionId connectionId, ReplyMessage<RawB
154155
{
155156
if (!rawDocument.GetValue("ok", false).ToBoolean())
156157
{
157-
var materializedDocument = new BsonDocument(rawDocument);
158+
var binaryReaderSettings = new BsonBinaryReaderSettings();
159+
if (_messageEncoderSettings != null)
160+
{
161+
binaryReaderSettings.Encoding = _messageEncoderSettings.GetOrDefault<UTF8Encoding>(MessageEncoderSettingsName.ReadEncoding, Utf8Encodings.Strict);
162+
binaryReaderSettings.GuidRepresentation = _messageEncoderSettings.GetOrDefault<GuidRepresentation>(MessageEncoderSettingsName.GuidRepresentation, GuidRepresentation.CSharpLegacy);
163+
};
164+
var materializedDocument = rawDocument.Materialize(binaryReaderSettings);
165+
158166
var commandName = _command.GetElement(0).Name;
159167
if (commandName == "$query")
160168
{

0 commit comments

Comments
 (0)