Skip to content

Commit 5d53ed9

Browse files
David JuliaDavid Julia
David Julia
authored and
David Julia
committed
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. There certainly is room to refactor this according to the maintainers' preference. fixes spring-projects#3688
1 parent 82d67c1 commit 5d53ed9

File tree

3 files changed

+56
-4
lines changed

3 files changed

+56
-4
lines changed

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

+25-4
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

@@ -1381,6 +1388,11 @@ public KeyMapper(String key,
13811388
* @param property
13821389
* @return
13831390
*/
1391+
//properties will be OBJ-outerMap, OBJ-map, OBJ-stringParameter objs
1392+
//PARTS in iterator are [outerMap, 1, map, 2, stringParameter]
1393+
//IF in a map, THEN next part SHOULD be added...
1394+
// up until the part represented by the next property (how do we identify that?!) basically until the next param.
1395+
//
13841396
protected String mapPropertyName(MongoPersistentProperty property) {
13851397

13861398
StringBuilder mappedName = new StringBuilder(PropertyToFieldNameConverter.INSTANCE.convert(property));
@@ -1389,16 +1401,25 @@ protected String mapPropertyName(MongoPersistentProperty property) {
13891401
while (inspect) {
13901402

13911403
String partial = iterator.next();
1404+
currentIndex++;
13921405

1393-
boolean isPositional = isPositionalParameter(partial) && property.isCollectionLike();
1406+
boolean isPositional = isPositionalParameter(partial) && property.isCollectionLike() ;
1407+
if(property.isMap() && currentPropertyRoot.equals(partial)){
1408+
partial = iterator.next();
1409+
currentIndex++;
1410+
}
13941411

1395-
if (isPositional || property.isMap()) {
1412+
if (isPositional || property.isMap()) {
13961413
mappedName.append(".").append(partial);
13971414
}
13981415

1399-
inspect = isPositional && iterator.hasNext();
1416+
inspect = (isPositional && iterator.hasNext());
14001417
}
14011418

1419+
if(currentIndex + 1 < pathParts.size()) {
1420+
currentIndex++;
1421+
currentPropertyRoot = pathParts.get(currentIndex);
1422+
}
14021423
return mappedName.toString();
14031424
}
14041425

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

+16
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,17 @@ 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+
733745
@Test // DATAMONGO-1269
734746
void mappingShouldRetainNumericPositionInList() {
735747

@@ -1467,6 +1479,10 @@ static class EntityWithComplexValueTypeMap {
14671479
Map<Integer, SimpleEntityWithoutId> map;
14681480
}
14691481

1482+
static class EntityWithIntKeyedMapOfMap{
1483+
Map<Integer, EntityWithComplexValueTypeMap> outerMap;
1484+
}
1485+
14701486
static class EntityWithComplexValueTypeList {
14711487
List<SimpleEntityWithoutId> list;
14721488
}

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)