Skip to content

Commit 5cffb3c

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 61d3a0b commit 5cffb3c

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
@@ -69,6 +69,7 @@
6969
* @author Thomas Darimont
7070
* @author Christoph Strobl
7171
* @author Mark Paluch
72+
* @author David Julia
7273
*/
7374
public class QueryMapper {
7475

@@ -1367,11 +1368,17 @@ public TypeInformation<?> getTypeHint() {
13671368
static class KeyMapper {
13681369

13691370
private final Iterator<String> iterator;
1371+
private int currentIndex;
1372+
private String currentPropertyRoot;
1373+
private final List<String> pathParts;
13701374

13711375
public KeyMapper(String key,
13721376
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
13731377

1374-
this.iterator = Arrays.asList(key.split("\\.")).iterator();
1378+
this.pathParts = Arrays.asList(key.split("\\."));
1379+
this.currentPropertyRoot = pathParts.get(0);
1380+
this.currentIndex = 0;
1381+
this.iterator = pathParts.iterator();
13751382
this.iterator.next();
13761383
}
13771384

@@ -1389,16 +1396,25 @@ protected String mapPropertyName(MongoPersistentProperty property) {
13891396
while (inspect) {
13901397

13911398
String partial = iterator.next();
1399+
currentIndex++;
13921400

1393-
boolean isPositional = isPositionalParameter(partial) && property.isCollectionLike();
1401+
boolean isPositional = isPositionalParameter(partial) && property.isCollectionLike() ;
1402+
if(property.isMap() && currentPropertyRoot.equals(partial) && iterator.hasNext()){
1403+
partial = iterator.next();
1404+
currentIndex++;
1405+
}
13941406

1395-
if (isPositional || property.isMap()) {
1407+
if (isPositional || property.isMap() && !currentPropertyRoot.equals(partial)) {
13961408
mappedName.append(".").append(partial);
13971409
}
13981410

13991411
inspect = isPositional && iterator.hasNext();
14001412
}
14011413

1414+
if(currentIndex + 1 < pathParts.size()) {
1415+
currentIndex++;
1416+
currentPropertyRoot = pathParts.get(currentIndex);
1417+
}
14021418
return mappedName.toString();
14031419
}
14041420

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

+27
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
* @author Thomas Darimont
7373
* @author Christoph Strobl
7474
* @author Mark Paluch
75+
* @author David Julia
7576
*/
7677
public class QueryMapperUnitTests {
7778

@@ -730,6 +731,28 @@ void mappingShouldRetainNumericMapKey() {
730731
assertThat(document).containsKey("map.1.stringProperty");
731732
}
732733

734+
@Test // GH-3688
735+
void mappingShouldRetainNestedNumericMapKeys() {
736+
737+
Query query = query(where("outerMap.1.map.2.stringProperty").is("ba'alzamon"));
738+
739+
org.bson.Document document = mapper.getMappedObject(query.getQueryObject(),
740+
context.getPersistentEntity(EntityWithIntKeyedMapOfMap.class));
741+
742+
assertThat(document).containsKey("outerMap.1.map.2.stringProperty");
743+
}
744+
745+
@Test // GH-3688
746+
void mappingShouldAllowSettingEntireNestedNumericKeyedMapValue() {
747+
748+
Query query = query(where("outerMap.1.map").is(null)); //newEntityWithComplexValueTypeMap()
749+
750+
org.bson.Document document = mapper.getMappedObject(query.getQueryObject(),
751+
context.getPersistentEntity(EntityWithIntKeyedMapOfMap.class));
752+
753+
assertThat(document).containsKey("outerMap.1.map");
754+
}
755+
733756
@Test // DATAMONGO-1269
734757
void mappingShouldRetainNumericPositionInList() {
735758

@@ -1467,6 +1490,10 @@ static class EntityWithComplexValueTypeMap {
14671490
Map<Integer, SimpleEntityWithoutId> map;
14681491
}
14691492

1493+
static class EntityWithIntKeyedMapOfMap{
1494+
Map<Integer, EntityWithComplexValueTypeMap> outerMap;
1495+
}
1496+
14701497
static class EntityWithComplexValueTypeList {
14711498
List<SimpleEntityWithoutId> list;
14721499
}

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

+15
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
* @author Thomas Darimont
6868
* @author Mark Paluch
6969
* @author Pavel Vodrazka
70+
* @author David Julia
7071
*/
7172
@ExtendWith(MockitoExtension.class)
7273
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)