Skip to content

Commit 1ef76fb

Browse files
JamesKovacsdnickless
authored andcommitted
CSHARP-4274: Added DisambiguatedPaths to ChangeStreamDocument. (mongodb#881)
1 parent a5031dc commit 1ef76fb

File tree

6 files changed

+412
-1
lines changed

6 files changed

+412
-1
lines changed

src/MongoDB.Driver.Core/ChangeStreamDocument.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,37 @@ public ChangeStreamDocument(
9292
/// </value>
9393
public DatabaseNamespace DatabaseNamespace => GetValue<DatabaseNamespace>(nameof(DatabaseNamespace), null);
9494

95+
/// <summary>
96+
/// Gets the disambiguated paths if present.
97+
/// </summary>
98+
/// <value>
99+
/// The disambiguated paths.
100+
/// </value>
101+
/// <remarks>
102+
/// <para>
103+
/// A document containing a map that associates an update path to an array containing the path components used in the update document. This data
104+
/// can be used in combination with the other fields in an <see cref="ChangeStreamDocument{TDocument}.UpdateDescription"/> to determine the
105+
/// actual path in the document that was updated. This is necessary in cases where a key contains dot-separated strings (i.e. <c>{ "a.b": "c" }</c>) or
106+
/// a document contains a numeric literal string key (i.e. <c>{ "a": { "0": "a" } }</c>). Note that in this scenario, the numeric key can't be the top
107+
/// level key because <c>{ "0": "a" }</c> is not ambiguous - update paths would simply be <c>'0'</c> which is unambiguous because BSON documents cannot have
108+
/// arrays at the top level. Each entry in the document maps an update path to an array which contains the actual path used when the document
109+
/// was updated. For example, given a document with the following shape <c>{ "a": { "0": 0 } }</c> and an update of <c>{ $inc: { "a.0": 1 } }</c>,
110+
/// <see cref="ChangeStreamDocument{TDocument}.DisambiguatedPaths"/> would look like the following:
111+
/// </para>
112+
/// <code>
113+
/// {
114+
/// "a.0": ["a", "0"]
115+
/// }
116+
/// </code>
117+
/// <para>
118+
/// In each array, all elements will be returned as strings with the exception of array indices, which will be returned as 32-bit integers.
119+
/// </para>
120+
/// <para>
121+
/// Added in MongoDB version 6.1.0.
122+
/// </para>
123+
/// </remarks>
124+
public BsonDocument DisambiguatedPaths => GetValue<BsonDocument>(nameof(DisambiguatedPaths), null);
125+
95126
/// <summary>
96127
/// Gets the document key.
97128
/// </summary>

src/MongoDB.Driver.Core/ChangeStreamDocumentSerializer.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public ChangeStreamDocumentSerializer(
4444
RegisterMember("CollectionNamespace", "ns", ChangeStreamDocumentCollectionNamespaceSerializer.Instance);
4545
RegisterMember("CollectionUuid", "ui", GuidSerializer.StandardInstance);
4646
RegisterMember("DatabaseNamespace", "ns", ChangeStreamDocumentDatabaseNamespaceSerializer.Instance);
47+
RegisterMember("DisambiguatedPaths", "disambiguatedPaths", BsonDocumentSerializer.Instance);
4748
RegisterMember("DocumentKey", "documentKey", BsonDocumentSerializer.Instance);
4849
RegisterMember("FullDocument", "fullDocument", _documentSerializer);
4950
RegisterMember("FullDocumentBeforeChange", "fullDocumentBeforeChange", _documentSerializer);

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,12 @@ public void constructor_should_initialize_instance()
3636
var result = new ChangeStreamDocumentSerializer<BsonDocument>(documentSerializer);
3737

3838
result._documentSerializer().Should().BeSameAs(documentSerializer);
39-
result._memberSerializationInfo().Count.Should().Be(13);
39+
result._memberSerializationInfo().Count.Should().Be(14);
4040
AssertRegisteredMember(result, "ClusterTime", "clusterTime", BsonTimestampSerializer.Instance);
4141
AssertRegisteredMember(result, "CollectionNamespace", "ns", ChangeStreamDocumentCollectionNamespaceSerializer.Instance);
4242
AssertRegisteredMember(result, "CollectionUuid", "ui", GuidSerializer.StandardInstance);
4343
AssertRegisteredMember(result, "DatabaseNamespace", "ns", ChangeStreamDocumentDatabaseNamespaceSerializer.Instance);
44+
AssertRegisteredMember(result, "DisambiguatedPaths", "disambiguatedPaths", BsonDocumentSerializer.Instance);
4445
AssertRegisteredMember(result, "DocumentKey", "documentKey", BsonDocumentSerializer.Instance);
4546
AssertRegisteredMember(result, "FullDocument", "fullDocument", documentSerializer);
4647
AssertRegisteredMember(result, "FullDocumentBeforeChange", "fullDocumentBeforeChange", documentSerializer);

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,29 @@ public void DatabaseNamespace_should_return_null_if_invalid(string databaseName)
246246
result.Should().BeNull();
247247
}
248248

249+
[Fact]
250+
public void DisambiguatedPaths_should_return_expected_result()
251+
{
252+
var value = new BsonDocument("a.0", new BsonArray {"a", "0"});
253+
var backingDocument = new BsonDocument { { "other", 1 }, { "disambiguatedPaths", value } };
254+
var subject = CreateSubject(backingDocument: backingDocument);
255+
256+
var result = subject.DisambiguatedPaths;
257+
258+
result.Should().Be(value);
259+
}
260+
261+
[Fact]
262+
public void DisambiguatedPaths_should_return_null_if_not_present()
263+
{
264+
var backingDocument = new BsonDocument { { "other", 1 } };
265+
var subject = CreateSubject(backingDocument: backingDocument);
266+
267+
var result = subject.DisambiguatedPaths;
268+
269+
result.Should().BeNull();
270+
}
271+
249272
[Fact]
250273
public void DocumentKey_should_return_expected_result()
251274
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
{
2+
"description": "disambiguatedPaths",
3+
"schemaVersion": "1.4",
4+
"createEntities": [
5+
{
6+
"client": {
7+
"id": "client0",
8+
"useMultipleMongoses": false
9+
}
10+
},
11+
{
12+
"database": {
13+
"id": "database0",
14+
"client": "client0",
15+
"databaseName": "database0"
16+
}
17+
},
18+
{
19+
"collection": {
20+
"id": "collection0",
21+
"database": "database0",
22+
"collectionName": "collection0"
23+
}
24+
}
25+
],
26+
"runOnRequirements": [
27+
{
28+
"minServerVersion": "6.1.0",
29+
"topologies": [
30+
"replicaset",
31+
"sharded-replicaset",
32+
"load-balanced",
33+
"sharded"
34+
],
35+
"serverless": "forbid"
36+
}
37+
],
38+
"initialData": [
39+
{
40+
"collectionName": "collection0",
41+
"databaseName": "database0",
42+
"documents": []
43+
}
44+
],
45+
"tests": [
46+
{
47+
"description": "disambiguatedPaths is not present when showExpandedEvents is false/unset",
48+
"operations": [
49+
{
50+
"name": "insertOne",
51+
"object": "collection0",
52+
"arguments": {
53+
"document": {
54+
"_id": 1,
55+
"a": {
56+
"1": 1
57+
}
58+
}
59+
}
60+
},
61+
{
62+
"name": "createChangeStream",
63+
"object": "collection0",
64+
"arguments": {
65+
"pipeline": []
66+
},
67+
"saveResultAsEntity": "changeStream0"
68+
},
69+
{
70+
"name": "updateOne",
71+
"object": "collection0",
72+
"arguments": {
73+
"filter": {
74+
"_id": 1
75+
},
76+
"update": {
77+
"$set": {
78+
"a.1": 2
79+
}
80+
}
81+
}
82+
},
83+
{
84+
"name": "iterateUntilDocumentOrError",
85+
"object": "changeStream0",
86+
"expectResult": {
87+
"operationType": "update",
88+
"ns": {
89+
"db": "database0",
90+
"coll": "collection0"
91+
},
92+
"updateDescription": {
93+
"updatedFields": {
94+
"$$exists": true
95+
},
96+
"removedFields": {
97+
"$$exists": true
98+
},
99+
"truncatedArrays": {
100+
"$$exists": true
101+
},
102+
"disambiguatedPaths": {
103+
"$$exists": false
104+
}
105+
}
106+
}
107+
}
108+
]
109+
},
110+
{
111+
"description": "disambiguatedPaths is present on updateDescription when an ambiguous path is present",
112+
"operations": [
113+
{
114+
"name": "insertOne",
115+
"object": "collection0",
116+
"arguments": {
117+
"document": {
118+
"_id": 1,
119+
"a": {
120+
"1": 1
121+
}
122+
}
123+
}
124+
},
125+
{
126+
"name": "createChangeStream",
127+
"object": "collection0",
128+
"arguments": {
129+
"pipeline": [],
130+
"showExpandedEvents": true
131+
},
132+
"saveResultAsEntity": "changeStream0"
133+
},
134+
{
135+
"name": "updateOne",
136+
"object": "collection0",
137+
"arguments": {
138+
"filter": {
139+
"_id": 1
140+
},
141+
"update": {
142+
"$set": {
143+
"a.1": 2
144+
}
145+
}
146+
}
147+
},
148+
{
149+
"name": "iterateUntilDocumentOrError",
150+
"object": "changeStream0",
151+
"expectResult": {
152+
"operationType": "update",
153+
"ns": {
154+
"db": "database0",
155+
"coll": "collection0"
156+
},
157+
"updateDescription": {
158+
"updatedFields": {
159+
"$$exists": true
160+
},
161+
"removedFields": {
162+
"$$exists": true
163+
},
164+
"truncatedArrays": {
165+
"$$exists": true
166+
},
167+
"disambiguatedPaths": {
168+
"a.1": [
169+
"a",
170+
"1"
171+
]
172+
}
173+
}
174+
}
175+
}
176+
]
177+
},
178+
{
179+
"description": "disambiguatedPaths returns array indices as integers",
180+
"operations": [
181+
{
182+
"name": "insertOne",
183+
"object": "collection0",
184+
"arguments": {
185+
"document": {
186+
"_id": 1,
187+
"a": [
188+
{
189+
"1": 1
190+
}
191+
]
192+
}
193+
}
194+
},
195+
{
196+
"name": "createChangeStream",
197+
"object": "collection0",
198+
"arguments": {
199+
"pipeline": [],
200+
"showExpandedEvents": true
201+
},
202+
"saveResultAsEntity": "changeStream0"
203+
},
204+
{
205+
"name": "updateOne",
206+
"object": "collection0",
207+
"arguments": {
208+
"filter": {
209+
"_id": 1
210+
},
211+
"update": {
212+
"$set": {
213+
"a.0.1": 2
214+
}
215+
}
216+
}
217+
},
218+
{
219+
"name": "iterateUntilDocumentOrError",
220+
"object": "changeStream0",
221+
"expectResult": {
222+
"operationType": "update",
223+
"ns": {
224+
"db": "database0",
225+
"coll": "collection0"
226+
},
227+
"updateDescription": {
228+
"updatedFields": {
229+
"$$exists": true
230+
},
231+
"removedFields": {
232+
"$$exists": true
233+
},
234+
"truncatedArrays": {
235+
"$$exists": true
236+
},
237+
"disambiguatedPaths": {
238+
"a.0.1": [
239+
"a",
240+
{
241+
"$$type": "int"
242+
},
243+
"1"
244+
]
245+
}
246+
}
247+
}
248+
}
249+
]
250+
}
251+
]
252+
}

0 commit comments

Comments
 (0)