Skip to content

Commit a318dc5

Browse files
authored
Add disambiguatedPaths field to UpdateDescription (#991)
This change supports disambiguatedPaths field for change streams, since ChangeStreamDocument has-a UpdateDescription. JAVA-4692
1 parent 39ec370 commit a318dc5

File tree

3 files changed

+312
-8
lines changed

3 files changed

+312
-8
lines changed

driver-core/src/main/com/mongodb/client/model/changestream/UpdateDescription.java

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public final class UpdateDescription {
3636
private final List<String> removedFields;
3737
private final BsonDocument updatedFields;
3838
private final List<TruncatedArray> truncatedArrays;
39+
private final BsonDocument disambiguatedPaths;
3940

4041
/**
4142
* Creates a new instance
@@ -57,14 +58,33 @@ public UpdateDescription(@Nullable final List<String> removedFields,
5758
* If {@code null}, then {@link #getTruncatedArrays()} returns an {@linkplain List#isEmpty() empty} {@link List}.
5859
* @since 4.3
5960
*/
61+
public UpdateDescription(
62+
@Nullable final List<String> removedFields,
63+
@Nullable final BsonDocument updatedFields,
64+
@Nullable final List<TruncatedArray> truncatedArrays) {
65+
this(removedFields, updatedFields, truncatedArrays, null);
66+
}
67+
68+
/**
69+
* @param removedFields Names of the fields that were removed.
70+
* @param updatedFields Information about the updated fields.
71+
* @param truncatedArrays Information about the updated fields of the {@linkplain org.bson.BsonType#ARRAY array} type
72+
* when the changes are reported as truncations. If {@code null}, then {@link #getTruncatedArrays()} returns
73+
* an {@linkplain List#isEmpty() empty} {@link List}.
74+
* @param disambiguatedPaths a document containing a map that associates an update path to an array containing the path components
75+
* used in the update document.
76+
* @since 4.8
77+
*/
6078
@BsonCreator
6179
public UpdateDescription(
6280
@Nullable @BsonProperty("removedFields") final List<String> removedFields,
6381
@Nullable @BsonProperty("updatedFields") final BsonDocument updatedFields,
64-
@Nullable @BsonProperty("truncatedArrays") final List<TruncatedArray> truncatedArrays) {
82+
@Nullable @BsonProperty("truncatedArrays") final List<TruncatedArray> truncatedArrays,
83+
@Nullable @BsonProperty("disambiguatedPaths") final BsonDocument disambiguatedPaths) {
6584
this.removedFields = removedFields;
6685
this.updatedFields = updatedFields;
6786
this.truncatedArrays = truncatedArrays == null ? emptyList() : truncatedArrays;
87+
this.disambiguatedPaths = disambiguatedPaths;
6888
}
6989

7090
/**
@@ -122,6 +142,35 @@ public List<TruncatedArray> getTruncatedArrays() {
122142
return truncatedArrays;
123143
}
124144

145+
/**
146+
* A document containing a map that associates an update path to an array containing the path components used in the update document.
147+
*
148+
* <p>
149+
* This data can be used in combination with the other fields in an `UpdateDescription` to determine the actual path in the document
150+
* that was updated. This is necessary in cases where a key contains dot-separated strings (i.e., <code>{"a.b": "c"}</code>) or a
151+
* document contains a numeric literal string key (i.e., <code>{ "a": { "0": "a" } }</code>. Note that in this
152+
* scenario, the numeric key can't be the top level key, because <code>{ "0": "a" }</code> is not ambiguous - update paths
153+
* would simply be <code>'0'</code> which is unambiguous because BSON documents cannot have arrays at the top level.).
154+
* </p>
155+
* <p>
156+
* Each entry in the document maps an update path to an array which contains the actual path used when the document was updated. For
157+
* example, given a document with the following shape <code>{ "a": { "0": 0 } }</code> and an update of
158+
* <code>{ $inc: { "a.0": 1 } }</code>, <code>disambiguatedPaths</code> would look like the following:
159+
* <code> { "a.0": ["a", "0"] }</code>.
160+
* </p>
161+
* <p>
162+
* In each array, all elements will be returned as strings, except for array indices, which will be returned as 32-bit integers.
163+
* </p>
164+
*
165+
* @return the disambiguated paths as a BSON document, which may be null
166+
* @since 4.8
167+
* @mongodb.server.release 6.1
168+
*/
169+
@Nullable
170+
public BsonDocument getDisambiguatedPaths() {
171+
return disambiguatedPaths;
172+
}
173+
125174
/**
126175
* @return {@code true} if and only if all of the following is true for the compared objects
127176
* <ul>
@@ -146,12 +195,13 @@ public boolean equals(final Object o) {
146195
UpdateDescription that = (UpdateDescription) o;
147196
return Objects.equals(removedFields, that.removedFields)
148197
&& Objects.equals(updatedFields, that.updatedFields)
149-
&& Objects.equals(truncatedArrays, that.truncatedArrays);
198+
&& Objects.equals(truncatedArrays, that.truncatedArrays)
199+
&& Objects.equals(disambiguatedPaths, that.disambiguatedPaths);
150200
}
151201

152202
@Override
153203
public int hashCode() {
154-
return Objects.hash(removedFields, updatedFields, truncatedArrays);
204+
return Objects.hash(removedFields, updatedFields, truncatedArrays, disambiguatedPaths);
155205
}
156206

157207
@Override
@@ -160,6 +210,7 @@ public String toString() {
160210
+ "removedFields=" + removedFields
161211
+ ", updatedFields=" + updatedFields
162212
+ ", truncatedArrays=" + truncatedArrays
213+
+ ", disambiguatedPaths=" + disambiguatedPaths
163214
+ "}";
164215
}
165216
}
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
{
2+
"description": "disambiguatedPaths",
3+
"schemaVersion": "1.3",
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+
}
36+
],
37+
"initialData": [
38+
{
39+
"collectionName": "collection0",
40+
"databaseName": "database0",
41+
"documents": []
42+
}
43+
],
44+
"tests": [
45+
{
46+
"description": "disambiguatedPaths is not present when showExpandedEvents is false/unset",
47+
"operations": [
48+
{
49+
"name": "insertOne",
50+
"object": "collection0",
51+
"arguments": {
52+
"document": {
53+
"_id": 1,
54+
"a": {
55+
"1": 1
56+
}
57+
}
58+
}
59+
},
60+
{
61+
"name": "createChangeStream",
62+
"object": "collection0",
63+
"arguments": {
64+
"pipeline": []
65+
},
66+
"saveResultAsEntity": "changeStream0"
67+
},
68+
{
69+
"name": "updateOne",
70+
"object": "collection0",
71+
"arguments": {
72+
"filter": {
73+
"_id": 1
74+
},
75+
"update": {
76+
"$set": {
77+
"a.1": 2
78+
}
79+
}
80+
}
81+
},
82+
{
83+
"name": "iterateUntilDocumentOrError",
84+
"object": "changeStream0",
85+
"expectResult": {
86+
"operationType": "update",
87+
"ns": {
88+
"db": "database0",
89+
"coll": "collection0"
90+
},
91+
"updateDescription": {
92+
"updatedFields": {
93+
"$$exists": true
94+
},
95+
"removedFields": {
96+
"$$exists": true
97+
},
98+
"truncatedArrays": {
99+
"$$exists": true
100+
},
101+
"disambiguatedPaths": {
102+
"$$exists": false
103+
}
104+
}
105+
}
106+
}
107+
]
108+
},
109+
{
110+
"description": "disambiguatedPaths is present on updateDescription when an ambiguous path is present",
111+
"operations": [
112+
{
113+
"name": "insertOne",
114+
"object": "collection0",
115+
"arguments": {
116+
"document": {
117+
"_id": 1,
118+
"a": {
119+
"1": 1
120+
}
121+
}
122+
}
123+
},
124+
{
125+
"name": "createChangeStream",
126+
"object": "collection0",
127+
"arguments": {
128+
"pipeline": [],
129+
"showExpandedEvents": true
130+
},
131+
"saveResultAsEntity": "changeStream0"
132+
},
133+
{
134+
"name": "updateOne",
135+
"object": "collection0",
136+
"arguments": {
137+
"filter": {
138+
"_id": 1
139+
},
140+
"update": {
141+
"$set": {
142+
"a.1": 2
143+
}
144+
}
145+
}
146+
},
147+
{
148+
"name": "iterateUntilDocumentOrError",
149+
"object": "changeStream0",
150+
"expectResult": {
151+
"operationType": "update",
152+
"ns": {
153+
"db": "database0",
154+
"coll": "collection0"
155+
},
156+
"updateDescription": {
157+
"updatedFields": {
158+
"$$exists": true
159+
},
160+
"removedFields": {
161+
"$$exists": true
162+
},
163+
"truncatedArrays": {
164+
"$$exists": true
165+
},
166+
"disambiguatedPaths": {
167+
"a.1": [
168+
"a",
169+
"1"
170+
]
171+
}
172+
}
173+
}
174+
}
175+
]
176+
},
177+
{
178+
"description": "disambiguatedPaths returns array indices as integers",
179+
"operations": [
180+
{
181+
"name": "insertOne",
182+
"object": "collection0",
183+
"arguments": {
184+
"document": {
185+
"_id": 1,
186+
"a": [
187+
{
188+
"1": 1
189+
}
190+
]
191+
}
192+
}
193+
},
194+
{
195+
"name": "createChangeStream",
196+
"object": "collection0",
197+
"arguments": {
198+
"pipeline": [],
199+
"showExpandedEvents": true
200+
},
201+
"saveResultAsEntity": "changeStream0"
202+
},
203+
{
204+
"name": "updateOne",
205+
"object": "collection0",
206+
"arguments": {
207+
"filter": {
208+
"_id": 1
209+
},
210+
"update": {
211+
"$set": {
212+
"a.0.1": 2
213+
}
214+
}
215+
}
216+
},
217+
{
218+
"name": "iterateUntilDocumentOrError",
219+
"object": "changeStream0",
220+
"expectResult": {
221+
"operationType": "update",
222+
"ns": {
223+
"db": "database0",
224+
"coll": "collection0"
225+
},
226+
"updateDescription": {
227+
"updatedFields": {
228+
"$$exists": true
229+
},
230+
"removedFields": {
231+
"$$exists": true
232+
},
233+
"truncatedArrays": {
234+
"$$exists": true
235+
},
236+
"disambiguatedPaths": {
237+
"a.0.1": [
238+
"a",
239+
{
240+
"$$type": "int"
241+
},
242+
"1"
243+
]
244+
}
245+
}
246+
}
247+
}
248+
]
249+
}
250+
]
251+
}

driver-core/src/test/unit/com/mongodb/client/model/changestream/UpdateDescriptionSpecification.groovy

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,19 @@ class UpdateDescriptionSpecification extends Specification {
2626

2727
def 'should create the expected UpdateDescription'() {
2828
when:
29-
def description = new UpdateDescription(removedFields, updatedFields, truncatedArrays)
29+
def description = new UpdateDescription(removedFields, updatedFields, truncatedArrays, disambiguatedPaths)
3030

3131
then:
3232
description.getRemovedFields() == removedFields
3333
description.getUpdatedFields() == updatedFields
3434
description.getTruncatedArrays() == (truncatedArrays ?: emptyList())
35+
description.getDisambiguatedPaths() == disambiguatedPaths
3536

3637
where:
37-
removedFields | updatedFields | truncatedArrays
38-
['a', 'b'] | null | null
39-
null | BsonDocument.parse('{c: 1}') | []
40-
['a', 'b'] | BsonDocument.parse('{c: 1}') | singletonList(new TruncatedArray('d', 1))
38+
removedFields | updatedFields | truncatedArrays | disambiguatedPaths
39+
['a', 'b'] | null | null | null
40+
null | BsonDocument.parse('{c: 1}') | [] | null
41+
['a', 'b'] | BsonDocument.parse('{c: 1}') | singletonList(new TruncatedArray('d', 1)) | null
42+
['a', 'b'] | BsonDocument.parse('{c: 1}') | singletonList(new TruncatedArray('d', 1)) | BsonDocument.parse('{e: 1}')
4143
}
4244
}

0 commit comments

Comments
 (0)