Skip to content

Commit 8033b05

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 a1a0675 commit 8033b05

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -689,7 +689,8 @@ <T> Document applyShardKey(MongoPersistentEntity<T> domainType, Document filter,
689689
: mappedDocument != null ? mappedDocument.getDocument() : getMappedUpdate(domainType);
690690

691691
Document filterWithShardKey = new Document(filter);
692-
getMappedShardKeyFields(domainType).forEach(key -> filterWithShardKey.putIfAbsent(key, shardKeySource.get(key)));
692+
getMappedShardKeyFields(domainType)
693+
.forEach(key -> filterWithShardKey.putIfAbsent(key, BsonUtils.resolveValue(shardKeySource, key)));
693694

694695
return filterWithShardKey;
695696
}

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,41 @@ public static Document parse(String json, @Nullable CodecRegistryProvider codecR
385385
.orElseGet(() -> new DocumentCodec(codecRegistryProvider.getCodecRegistry())));
386386
}
387387

388+
/**
389+
* Resolve a the value for a given key. If the given {@link Bson} value contains the key the value is immediately
390+
* returned. If not and the key contains a path using the dot ({@code .}) notation it will try to resolve the path by
391+
* inspecting the individual parts. If one of the intermediate ones is {@literal null} or cannot be inspected further
392+
* (wrong) type, {@literal null} is returned.
393+
*
394+
* @param bson the source to inspect. Must not be {@literal null}.
395+
* @param key the key to lookup. Must not be {@literal null}.
396+
* @return can be {@literal null}.
397+
*/
398+
@Nullable
399+
public static Object resolveValue(Bson bson, String key) {
400+
401+
Map<String, Object> source = asMap(bson);
402+
403+
if (source.containsKey(key) || !key.contains(".")) {
404+
return source.get(key);
405+
}
406+
407+
String[] parts = key.split("\\.");
408+
409+
for (int i = 1; i < parts.length; i++) {
410+
411+
Object result = source.get(parts[i - 1]);
412+
413+
if (result == null || !(result instanceof Bson)) {
414+
return null;
415+
}
416+
417+
source = asMap((Bson) result);
418+
}
419+
420+
return source.get(parts[parts.length - 1]);
421+
}
422+
388423
/**
389424
* Returns given object as {@link Collection}. Will return the {@link Collection} as is if the source is a
390425
* {@link Collection} already, will convert an array into a {@link Collection} or simply create a single element

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator;
7979
import org.springframework.data.mongodb.core.mapping.Field;
8080
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
81+
import org.springframework.data.mongodb.core.mapping.Sharded;
8182
import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener;
8283
import org.springframework.data.mongodb.core.mapping.event.AfterConvertCallback;
8384
import org.springframework.data.mongodb.core.mapping.event.AfterSaveCallback;
@@ -1954,6 +1955,24 @@ void saveShouldAppendDefaultShardKeyIfNotPresentInFilter() {
19541955
verify(findIterable, never()).first();
19551956
}
19561957

1958+
@Test // GH-3590
1959+
void shouldIncludeValueFromNestedShardKeyPath() {
1960+
1961+
WithShardKeyPoitingToNested source = new WithShardKeyPoitingToNested();
1962+
source.id = "id-1";
1963+
source.value = "v1";
1964+
source.nested = new WithNamedFields();
1965+
source.nested.customName = "cname";
1966+
source.nested.name = "name";
1967+
1968+
template.save(source);
1969+
1970+
ArgumentCaptor<Bson> filter = ArgumentCaptor.forClass(Bson.class);
1971+
verify(collection).replaceOne(filter.capture(), any(), any());
1972+
1973+
assertThat(filter.getValue()).isEqualTo(new Document("_id", "id-1").append("value", "v1").append("nested.custom-named-field", "cname"));
1974+
}
1975+
19571976
@Test // DATAMONGO-2341
19581977
void saveShouldProjectOnShardKeyWhenLoadingExistingDocument() {
19591978

@@ -2313,6 +2332,13 @@ static class Sith {
23132332
@Field("firstname") String name;
23142333
}
23152334

2335+
@Sharded(shardKey = {"value", "nested.customName"})
2336+
static class WithShardKeyPoitingToNested {
2337+
String id;
2338+
String value;
2339+
WithNamedFields nested;
2340+
}
2341+
23162342
static class TypeImplementingIterator implements Iterator {
23172343

23182344
@Override

0 commit comments

Comments
 (0)