Skip to content

Commit 2e2e076

Browse files
dajulia3christophstrobl
authored andcommitted
Fix Regression in generating queries with nested maps with numeric keys.
While maps that have numeric keys work if there is only one map with an integer key, when there are multiple maps with numeric keys in a given query, it fails. Take the following example for a map called outer with numeric keys holding reference to another object with a map called inner with numeric keys: Updates that are meant to generate {"$set": {"outerMap.1234.inner.5678": "hello"}} are instead generating {"$set": {"outerMap.1234.inner.inner": "hello"}}, repeating the later map property name instead of using the integer key value. This commit adds unit tests both for the UpdateMapper and QueryMapper, which check multiple consecutive maps with numeric keys, and adds a fix in the KeyMapper. Because we cannot easily change the path parsing to somehow parse path parts corresponding to map keys differently, we address the issue in the KeyMapper. We keep track of the partial path corresponding to the current property and use it to skip adding the duplicated property name for the map to the query, and instead add the key. This is a bit redundant in that we now have both an iterator and an index-based way of accessing the path parts, but it gets the tests passing and fixes the issue without making a large change to the current approach. Fixes: #3688 Original Pull Request: #3689
1 parent 0c50d97 commit 2e2e076

File tree

3 files changed

+61
-3
lines changed

3 files changed

+61
-3
lines changed

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

+19-3
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
* @author Thomas Darimont
6969
* @author Christoph Strobl
7070
* @author Mark Paluch
71+
* @author David Julia
7172
*/
7273
public class QueryMapper {
7374

@@ -1361,11 +1362,17 @@ public TypeInformation<?> getTypeHint() {
13611362
static class KeyMapper {
13621363

13631364
private final Iterator<String> iterator;
1365+
private int currentIndex;
1366+
private String currentPropertyRoot;
1367+
private final List<String> pathParts;
13641368

13651369
public KeyMapper(String key,
13661370
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
13671371

1368-
this.iterator = Arrays.asList(key.split("\\.")).iterator();
1372+
this.pathParts = Arrays.asList(key.split("\\."));
1373+
this.currentPropertyRoot = pathParts.get(0);
1374+
this.currentIndex = 0;
1375+
this.iterator = pathParts.iterator();
13691376
this.iterator.next();
13701377
}
13711378

@@ -1383,16 +1390,25 @@ protected String mapPropertyName(MongoPersistentProperty property) {
13831390
while (inspect) {
13841391

13851392
String partial = iterator.next();
1393+
currentIndex++;
13861394

1387-
boolean isPositional = isPositionalParameter(partial) && property.isCollectionLike();
1395+
boolean isPositional = isPositionalParameter(partial) && property.isCollectionLike() ;
1396+
if(property.isMap() && currentPropertyRoot.equals(partial) && iterator.hasNext()){
1397+
partial = iterator.next();
1398+
currentIndex++;
1399+
}
13881400

1389-
if (isPositional || property.isMap()) {
1401+
if (isPositional || property.isMap() && !currentPropertyRoot.equals(partial)) {
13901402
mappedName.append(".").append(partial);
13911403
}
13921404

13931405
inspect = isPositional && iterator.hasNext();
13941406
}
13951407

1408+
if(currentIndex + 1 < pathParts.size()) {
1409+
currentIndex++;
1410+
currentPropertyRoot = pathParts.get(currentIndex);
1411+
}
13961412
return mappedName.toString();
13971413
}
13981414

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

+27
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
* @author Thomas Darimont
7878
* @author Christoph Strobl
7979
* @author Mark Paluch
80+
* @author David Julia
8081
*/
8182
@ExtendWith(MockitoExtension.class)
8283
@MockitoSettings(strictness = Strictness.LENIENT)
@@ -739,6 +740,28 @@ void mappingShouldRetainNumericMapKey() {
739740
assertThat(document).containsKey("map.1.stringProperty");
740741
}
741742

743+
@Test // GH-3688
744+
void mappingShouldRetainNestedNumericMapKeys() {
745+
746+
Query query = query(where("outerMap.1.map.2.stringProperty").is("ba'alzamon"));
747+
748+
org.bson.Document document = mapper.getMappedObject(query.getQueryObject(),
749+
context.getPersistentEntity(EntityWithIntKeyedMapOfMap.class));
750+
751+
assertThat(document).containsKey("outerMap.1.map.2.stringProperty");
752+
}
753+
754+
@Test // GH-3688
755+
void mappingShouldAllowSettingEntireNestedNumericKeyedMapValue() {
756+
757+
Query query = query(where("outerMap.1.map").is(null)); //newEntityWithComplexValueTypeMap()
758+
759+
org.bson.Document document = mapper.getMappedObject(query.getQueryObject(),
760+
context.getPersistentEntity(EntityWithIntKeyedMapOfMap.class));
761+
762+
assertThat(document).containsKey("outerMap.1.map");
763+
}
764+
742765
@Test // DATAMONGO-1269
743766
void mappingShouldRetainNumericPositionInList() {
744767

@@ -1465,6 +1488,10 @@ static class EntityWithComplexValueTypeMap {
14651488
Map<Integer, SimpleEntityWithoutId> map;
14661489
}
14671490

1491+
static class EntityWithIntKeyedMapOfMap{
1492+
Map<Integer, EntityWithComplexValueTypeMap> outerMap;
1493+
}
1494+
14681495
static class EntityWithComplexValueTypeList {
14691496
List<SimpleEntityWithoutId> list;
14701497
}

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

+15
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
* @author Thomas Darimont
6767
* @author Mark Paluch
6868
* @author Pavel Vodrazka
69+
* @author David Julia
6970
*/
7071
@ExtendWith(MockitoExtension.class)
7172
class UpdateMapperUnitTests {
@@ -1179,6 +1180,16 @@ void numericKeyInMapOfNestedPath() {
11791180
assertThat(mappedUpdate).isEqualTo("{\"$set\": {\"map.601218778970110001827396.value\": \"testing\"}}");
11801181
}
11811182

1183+
@Test // GH-3688
1184+
void multipleNumericKeysInNestedPath() {
1185+
1186+
Update update = new Update().set("intKeyedMap.12345.map.0", "testing");
1187+
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
1188+
context.getPersistentEntity(EntityWithIntKeyedMap.class));
1189+
1190+
assertThat(mappedUpdate).isEqualTo("{\"$set\": {\"intKeyedMap.12345.map.0\": \"testing\"}}");
1191+
}
1192+
11821193
@Test // GH-3566
11831194
void mapsObjectClassPropertyFieldInMapValueTypeAsKey() {
11841195

@@ -1425,6 +1436,10 @@ static class EntityWithObjectMap {
14251436
Map<Object, NestedDocument> concreteMap;
14261437
}
14271438

1439+
static class EntityWithIntKeyedMap{
1440+
Map<Integer, EntityWithObjectMap> intKeyedMap;
1441+
}
1442+
14281443
static class ClassWithEnum {
14291444

14301445
Allocation allocation;

0 commit comments

Comments
 (0)