diff --git a/pom.xml b/pom.xml index 828f56af2e..e8c9cb8f5d 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 3.2.0-SNAPSHOT + 3.2.0-GH-3590-SNAPSHOT pom Spring Data MongoDB diff --git a/spring-data-mongodb-benchmarks/pom.xml b/spring-data-mongodb-benchmarks/pom.xml index f0fbb601c8..9a137b81fa 100644 --- a/spring-data-mongodb-benchmarks/pom.xml +++ b/spring-data-mongodb-benchmarks/pom.xml @@ -7,7 +7,7 @@ org.springframework.data spring-data-mongodb-parent - 3.2.0-SNAPSHOT + 3.2.0-GH-3590-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml index 1a17321782..133908285a 100644 --- a/spring-data-mongodb-distribution/pom.xml +++ b/spring-data-mongodb-distribution/pom.xml @@ -14,7 +14,7 @@ org.springframework.data spring-data-mongodb-parent - 3.2.0-SNAPSHOT + 3.2.0-GH-3590-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index 0248517caf..3c5c4af594 100644 --- a/spring-data-mongodb/pom.xml +++ b/spring-data-mongodb/pom.xml @@ -11,7 +11,7 @@ org.springframework.data spring-data-mongodb-parent - 3.2.0-SNAPSHOT + 3.2.0-GH-3590-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/QueryOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/QueryOperations.java index 89e7d29783..327d282193 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/QueryOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/QueryOperations.java @@ -689,7 +689,8 @@ Document applyShardKey(MongoPersistentEntity domainType, Document filter, : mappedDocument != null ? mappedDocument.getDocument() : getMappedUpdate(domainType); Document filterWithShardKey = new Document(filter); - getMappedShardKeyFields(domainType).forEach(key -> filterWithShardKey.putIfAbsent(key, shardKeySource.get(key))); + getMappedShardKeyFields(domainType) + .forEach(key -> filterWithShardKey.putIfAbsent(key, BsonUtils.resolveValue(shardKeySource, key))); return filterWithShardKey; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/BsonUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/BsonUtils.java index 85525b13b4..9be81f2bd2 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/BsonUtils.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/BsonUtils.java @@ -385,6 +385,41 @@ public static Document parse(String json, @Nullable CodecRegistryProvider codecR .orElseGet(() -> new DocumentCodec(codecRegistryProvider.getCodecRegistry()))); } + /** + * Resolve a the value for a given key. If the given {@link Bson} value contains the key the value is immediately + * returned. If not and the key contains a path using the dot ({@code .}) notation it will try to resolve the path by + * inspecting the individual parts. If one of the intermediate ones is {@literal null} or cannot be inspected further + * (wrong) type, {@literal null} is returned. + * + * @param bson the source to inspect. Must not be {@literal null}. + * @param key the key to lookup. Must not be {@literal null}. + * @return can be {@literal null}. + */ + @Nullable + public static Object resolveValue(Bson bson, String key) { + + Map source = asMap(bson); + + if (source.containsKey(key) || !key.contains(".")) { + return source.get(key); + } + + String[] parts = key.split("\\."); + + for (int i = 1; i < parts.length; i++) { + + Object result = source.get(parts[i - 1]); + + if (result == null || !(result instanceof Bson)) { + return null; + } + + source = asMap((Bson) result); + } + + return source.get(parts[parts.length - 1]); + } + /** * Returns given object as {@link Collection}. Will return the {@link Collection} as is if the source is a * {@link Collection} already, will convert an array into a {@link Collection} or simply create a single element diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java index 7006091d11..abf2903105 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java @@ -78,6 +78,7 @@ import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator; import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; +import org.springframework.data.mongodb.core.mapping.Sharded; import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener; import org.springframework.data.mongodb.core.mapping.event.AfterConvertCallback; import org.springframework.data.mongodb.core.mapping.event.AfterSaveCallback; @@ -1954,6 +1955,24 @@ void saveShouldAppendDefaultShardKeyIfNotPresentInFilter() { verify(findIterable, never()).first(); } + @Test // GH-3590 + void shouldIncludeValueFromNestedShardKeyPath() { + + WithShardKeyPoitingToNested source = new WithShardKeyPoitingToNested(); + source.id = "id-1"; + source.value = "v1"; + source.nested = new WithNamedFields(); + source.nested.customName = "cname"; + source.nested.name = "name"; + + template.save(source); + + ArgumentCaptor filter = ArgumentCaptor.forClass(Bson.class); + verify(collection).replaceOne(filter.capture(), any(), any()); + + assertThat(filter.getValue()).isEqualTo(new Document("_id", "id-1").append("value", "v1").append("nested.custom-named-field", "cname")); + } + @Test // DATAMONGO-2341 void saveShouldProjectOnShardKeyWhenLoadingExistingDocument() { @@ -2313,6 +2332,13 @@ static class Sith { @Field("firstname") String name; } + @Sharded(shardKey = {"value", "nested.customName"}) + static class WithShardKeyPoitingToNested { + String id; + String value; + WithNamedFields nested; + } + static class TypeImplementingIterator implements Iterator { @Override