Skip to content

Commit af40f15

Browse files
christophstroblmp911de
authored andcommitted
Fix ShardKey lookup for nested paths.
This commit fixes the lookup of shard key values for nested paths using the dot (.) notation. Closes: #3590 Original pull request: #3591.
1 parent 193b7de commit af40f15

File tree

3 files changed

+63
-1
lines changed

3 files changed

+63
-1
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/QueryOperations.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -658,7 +658,8 @@ <T> Document applyShardKey(MongoPersistentEntity<T> domainType, Document filter,
658658
: mappedDocument != null ? mappedDocument.getDocument() : getMappedUpdate(domainType);
659659

660660
Document filterWithShardKey = new Document(filter);
661-
getMappedShardKeyFields(domainType).forEach(key -> filterWithShardKey.putIfAbsent(key, shardKeySource.get(key)));
661+
getMappedShardKeyFields(domainType)
662+
.forEach(key -> filterWithShardKey.putIfAbsent(key, BsonUtils.resolveValue(shardKeySource, key)));
662663

663664
return filterWithShardKey;
664665
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/BsonUtils.java

+35
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,41 @@ public static Document parse(String json, @Nullable CodecRegistryProvider codecR
282282
.orElseGet(() -> new DocumentCodec(codecRegistryProvider.getCodecRegistry())));
283283
}
284284

285+
/**
286+
* Resolve a the value for a given key. If the given {@link Bson} value contains the key the value is immediately
287+
* returned. If not and the key contains a path using the dot ({@code .}) notation it will try to resolve the path by
288+
* inspecting the individual parts. If one of the intermediate ones is {@literal null} or cannot be inspected further
289+
* (wrong) type, {@literal null} is returned.
290+
*
291+
* @param bson the source to inspect. Must not be {@literal null}.
292+
* @param key the key to lookup. Must not be {@literal null}.
293+
* @return can be {@literal null}.
294+
*/
295+
@Nullable
296+
public static Object resolveValue(Bson bson, String key) {
297+
298+
Map<String, Object> source = asMap(bson);
299+
300+
if (source.containsKey(key) || !key.contains(".")) {
301+
return source.get(key);
302+
}
303+
304+
String[] parts = key.split("\\.");
305+
306+
for (int i = 1; i < parts.length; i++) {
307+
308+
Object result = source.get(parts[i - 1]);
309+
310+
if (result == null || !(result instanceof Bson)) {
311+
return null;
312+
}
313+
314+
source = asMap((Bson) result);
315+
}
316+
317+
return source.get(parts[parts.length - 1]);
318+
}
319+
285320
@Nullable
286321
private static String toJson(@Nullable Object value) {
287322

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java

+26
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator;
8585
import org.springframework.data.mongodb.core.mapping.Field;
8686
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
87+
import org.springframework.data.mongodb.core.mapping.Sharded;
8788
import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener;
8889
import org.springframework.data.mongodb.core.mapping.event.AfterConvertCallback;
8990
import org.springframework.data.mongodb.core.mapping.event.AfterSaveCallback;
@@ -1910,6 +1911,24 @@ void saveShouldAppendDefaultShardKeyIfNotPresentInFilter() {
19101911
verify(findIterable, never()).first();
19111912
}
19121913

1914+
@Test // GH-3590
1915+
void shouldIncludeValueFromNestedShardKeyPath() {
1916+
1917+
WithShardKeyPoitingToNested source = new WithShardKeyPoitingToNested();
1918+
source.id = "id-1";
1919+
source.value = "v1";
1920+
source.nested = new WithNamedFields();
1921+
source.nested.customName = "cname";
1922+
source.nested.name = "name";
1923+
1924+
template.save(source);
1925+
1926+
ArgumentCaptor<Bson> filter = ArgumentCaptor.forClass(Bson.class);
1927+
verify(collection).replaceOne(filter.capture(), any(), any());
1928+
1929+
assertThat(filter.getValue()).isEqualTo(new Document("_id", "id-1").append("value", "v1").append("nested.custom-named-field", "cname"));
1930+
}
1931+
19131932
@Test // DATAMONGO-2341
19141933
void saveShouldProjectOnShardKeyWhenLoadingExistingDocument() {
19151934

@@ -2246,6 +2265,13 @@ static class Sith {
22462265
@Field("firstname") String name;
22472266
}
22482267

2268+
@Sharded(shardKey = {"value", "nested.customName"})
2269+
static class WithShardKeyPoitingToNested {
2270+
String id;
2271+
String value;
2272+
WithNamedFields nested;
2273+
}
2274+
22492275
/**
22502276
* Mocks out the {@link MongoTemplate#getDb()} method to return the {@link DB} mock instead of executing the actual
22512277
* behaviour.

0 commit comments

Comments
 (0)