Skip to content

Commit 9f972a3

Browse files
committed
CSHARP-2918: Watch method should work even when documents contain duplicate element names.
1 parent 091afa6 commit 9f972a3

File tree

5 files changed

+114
-1
lines changed

5 files changed

+114
-1
lines changed

src/MongoDB.Driver.Core/ChangeStreamDocument.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,21 @@ public ChangeStreamDocument(
7575
/// <value>
7676
/// The full document.
7777
/// </value>
78-
public TDocument FullDocument => GetValue<TDocument>(nameof(FullDocument), default(TDocument));
78+
public TDocument FullDocument
79+
{
80+
get
81+
{
82+
// if TDocument is BsonDocument avoid deserializing it again to prevent possible duplicate element name errors
83+
if (typeof(TDocument) == typeof(BsonDocument) && BackingDocument.TryGetValue("fullDocument", out var fullDocument))
84+
{
85+
return (TDocument)(object)fullDocument.AsBsonDocument;
86+
}
87+
else
88+
{
89+
return GetValue<TDocument>(nameof(FullDocument), default(TDocument));
90+
}
91+
}
92+
}
7993

8094
/// <summary>
8195
/// Gets the type of the operation.

src/MongoDB.Driver.Core/ChangeStreamDocumentSerializer.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ public ChangeStreamDocumentSerializer(
5050
RegisterMember("UpdateDescription", "updateDescription", ChangeStreamUpdateDescriptionSerializer.Instance);
5151
}
5252

53+
// public methods
54+
/// <inheritdoc />
55+
public override ChangeStreamDocument<TDocument> Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
56+
{
57+
context = context.With(b => b.AllowDuplicateElementNames = true);
58+
return base.Deserialize(context, args);
59+
}
60+
5361
// protected methods
5462
/// <inheritdoc />
5563
protected override ChangeStreamDocument<TDocument> CreateInstance(BsonDocument backingDocument)

tests/MongoDB.Driver.Core.Tests/ChangeStreamDocumentSerializerTests.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,29 @@ public void Deserialize_should_return_expected_result_when_value_is_null()
8888
result.Should().BeNull();
8989
}
9090

91+
[Fact]
92+
public void Deserialize_should_support_duplicate_element_names_in_full_document()
93+
{
94+
var json = "{ fullDocument : { x : 1, x : 2 } }";
95+
var subject = CreateSubject();
96+
97+
ChangeStreamDocument<BsonDocument> result;
98+
using (var reader = new JsonReader(json))
99+
{
100+
var context = BsonDeserializationContext.CreateRoot(reader);
101+
result = subject.Deserialize(context);
102+
}
103+
104+
var fullDocument = result.FullDocument;
105+
fullDocument.ElementCount.Should().Be(2);
106+
var firstElement = fullDocument.GetElement(0);
107+
firstElement.Name.Should().Be("x");
108+
firstElement.Value.Should().Be(1);
109+
var secondElement = fullDocument.GetElement(1);
110+
secondElement.Name.Should().Be("x");
111+
secondElement.Value.Should().Be(2);
112+
}
113+
91114
[Fact]
92115
public void Serialize_should_have_expected_result()
93116
{

tests/MongoDB.Driver.Core.Tests/ChangeStreamDocumentTests.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,18 @@ public void BackingDocument_should_return_expected_result()
7272
result.Should().BeSameAs(backingDocument);
7373
}
7474

75+
[Fact]
76+
public void BackingDocument_should_allow_duplicate_elements_in_full_document()
77+
{
78+
var fullDocument = new BsonDocument(allowDuplicateNames: true) { { "x", 1 }, { "x", 2 } };
79+
var backingDocument = new BsonDocument("fullDocument", fullDocument);
80+
var subject = CreateSubject(backingDocument: backingDocument);
81+
82+
var result = subject.BackingDocument;
83+
84+
result.Should().BeSameAs(backingDocument);
85+
}
86+
7587
[Theory]
7688
[InlineData(1, 2)]
7789
[InlineData(3, 4)]
@@ -159,6 +171,24 @@ public void FullDocument_should_return_expected_result()
159171
result.Should().Be(value);
160172
}
161173

174+
[Fact]
175+
public void FullDocument_should_allow_duplicate_elements()
176+
{
177+
var fullDocument = new BsonDocument(allowDuplicateNames: true) { { "x", 1 }, { "x", 2 } };
178+
var backingDocument = new BsonDocument { { "other", 1 }, { "fullDocument", fullDocument } };
179+
var subject = CreateSubject(backingDocument: backingDocument);
180+
181+
var result = subject.FullDocument;
182+
183+
result.ElementCount.Should().Be(2);
184+
var firstElement = result.GetElement(0);
185+
firstElement.Name.Should().Be("x");
186+
firstElement.Value.Should().Be(1);
187+
var secondElement = result.GetElement(1);
188+
secondElement.Name.Should().Be("x");
189+
secondElement.Value.Should().Be(2);
190+
}
191+
162192
[Fact]
163193
public void FullDocument_should_return_null_when_not_present()
164194
{

tests/MongoDB.Driver.Tests/MongoCollectionImplTests.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@
2828
using MongoDB.Driver.Core.Bindings;
2929
using MongoDB.Driver.Core.Clusters;
3030
using MongoDB.Driver.Core.Connections;
31+
using MongoDB.Driver.Core.Misc;
3132
using MongoDB.Driver.Core.Operations;
3233
using MongoDB.Driver.Core.Servers;
34+
using MongoDB.Driver.Core.TestHelpers.XunitExtensions;
3335
using MongoDB.Driver.Tests;
3436
using Moq;
3537
using Xunit;
@@ -3206,6 +3208,42 @@ public void Watch_should_throw_when_pipeline_is_null(
32063208
e.ParamName.Should().Be("pipeline");
32073209
}
32083210

3211+
[SkippableFact]
3212+
public void Watch_should_support_full_document_with_duplicate_elements()
3213+
{
3214+
RequireServer.Check().Supports(Feature.ChangeStreamStage).ClusterTypes(ClusterType.ReplicaSet, ClusterType.Sharded);
3215+
3216+
var client = DriverTestConfiguration.Client;
3217+
var database = client.GetDatabase(DriverTestConfiguration.DatabaseNamespace.DatabaseName);
3218+
var collection = database.GetCollection<BsonDocument>(DriverTestConfiguration.CollectionNamespace.CollectionName);
3219+
database.DropCollection(collection.CollectionNamespace.CollectionName);
3220+
3221+
try
3222+
{
3223+
var cursor = collection.Watch();
3224+
3225+
ChangeStreamDocument<BsonDocument> changeStreamDocument = null;
3226+
var document = new BsonDocument(allowDuplicateNames: true) { { "_id", 1 }, { "x", 2 }, { "x", 3 } };
3227+
collection.InsertOne(document);
3228+
SpinWait.SpinUntil(() => cursor.MoveNext() && (changeStreamDocument = cursor.Current.FirstOrDefault()) != null, TimeSpan.FromSeconds(5)).Should().BeTrue();
3229+
var fullDocument = changeStreamDocument.FullDocument;
3230+
fullDocument.ElementCount.Should().Be(3);
3231+
var firstElement = fullDocument.GetElement(0);
3232+
firstElement.Name.Should().Be("_id");
3233+
firstElement.Value.Should().Be(1);
3234+
var secondElement = fullDocument.GetElement(1);
3235+
secondElement.Name.Should().Be("x");
3236+
secondElement.Value.Should().Be(2);
3237+
var thirdElement = fullDocument.GetElement(2);
3238+
thirdElement.Name.Should().Be("x");
3239+
thirdElement.Value.Should().Be(3);
3240+
}
3241+
finally
3242+
{
3243+
database.DropCollection(collection.CollectionNamespace.CollectionName);
3244+
}
3245+
}
3246+
32093247
[Fact]
32103248
public void WithReadPreference_should_return_a_new_collection_with_the_read_preference_changed()
32113249
{

0 commit comments

Comments
 (0)