Skip to content

Commit e480ceb

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 9fedd8d commit e480ceb

File tree

3 files changed

+61
-3
lines changed

3 files changed

+61
-3
lines changed

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

+19-3
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
* @author Thomas Darimont
6767
* @author Christoph Strobl
6868
* @author Mark Paluch
69+
* @author David Julia
6970
*/
7071
public class QueryMapper {
7172

@@ -1273,11 +1274,17 @@ public TypeInformation<?> getTypeHint() {
12731274
static class KeyMapper {
12741275

12751276
private final Iterator<String> iterator;
1277+
private int currentIndex;
1278+
private String currentPropertyRoot;
1279+
private final List<String> pathParts;
12761280

12771281
public KeyMapper(String key,
12781282
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
12791283

1280-
this.iterator = Arrays.asList(key.split("\\.")).iterator();
1284+
this.pathParts = Arrays.asList(key.split("\\."));
1285+
this.currentPropertyRoot = pathParts.get(0);
1286+
this.currentIndex = 0;
1287+
this.iterator = pathParts.iterator();
12811288
this.iterator.next();
12821289
}
12831290

@@ -1295,16 +1302,25 @@ protected String mapPropertyName(MongoPersistentProperty property) {
12951302
while (inspect) {
12961303

12971304
String partial = iterator.next();
1305+
currentIndex++;
12981306

1299-
boolean isPositional = isPositionalParameter(partial) && property.isCollectionLike();
1307+
boolean isPositional = isPositionalParameter(partial) && property.isCollectionLike() ;
1308+
if(property.isMap() && currentPropertyRoot.equals(partial) && iterator.hasNext()){
1309+
partial = iterator.next();
1310+
currentIndex++;
1311+
}
13001312

1301-
if (isPositional || property.isMap()) {
1313+
if (isPositional || property.isMap() && !currentPropertyRoot.equals(partial)) {
13021314
mappedName.append(".").append(partial);
13031315
}
13041316

13051317
inspect = isPositional && iterator.hasNext();
13061318
}
13071319

1320+
if(currentIndex + 1 < pathParts.size()) {
1321+
currentIndex++;
1322+
currentPropertyRoot = pathParts.get(currentIndex);
1323+
}
13081324
return mappedName.toString();
13091325
}
13101326

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

+27
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
* @author Thomas Darimont
7676
* @author Christoph Strobl
7777
* @author Mark Paluch
78+
* @author David Julia
7879
*/
7980
@ExtendWith(MockitoExtension.class)
8081
@MockitoSettings(strictness = Strictness.LENIENT)
@@ -737,6 +738,28 @@ void mappingShouldRetainNumericMapKey() {
737738
assertThat(document).containsKey("map.1.stringProperty");
738739
}
739740

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

@@ -1278,6 +1301,10 @@ static class EntityWithComplexValueTypeMap {
12781301
Map<Integer, SimpleEntityWithoutId> map;
12791302
}
12801303

1304+
static class EntityWithIntKeyedMapOfMap{
1305+
Map<Integer, EntityWithComplexValueTypeMap> outerMap;
1306+
}
1307+
12811308
static class EntityWithComplexValueTypeList {
12821309
List<SimpleEntityWithoutId> list;
12831310
}

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

+15
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
* @author Thomas Darimont
6666
* @author Mark Paluch
6767
* @author Pavel Vodrazka
68+
* @author David Julia
6869
*/
6970
@ExtendWith(MockitoExtension.class)
7071
class UpdateMapperUnitTests {
@@ -1110,6 +1111,16 @@ void numericKeyInMapOfNestedPath() {
11101111
.isEqualTo("{\"$set\": {\"map.601218778970110001827396.value\": \"testing\"}}");
11111112
}
11121113

1114+
@Test // GH-3688
1115+
void multipleNumericKeysInNestedPath() {
1116+
1117+
Update update = new Update().set("intKeyedMap.12345.map.0", "testing");
1118+
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
1119+
context.getPersistentEntity(EntityWithIntKeyedMap.class));
1120+
1121+
assertThat(mappedUpdate).isEqualTo("{\"$set\": {\"intKeyedMap.12345.map.0\": \"testing\"}}");
1122+
}
1123+
11131124
@Test // GH-3566
11141125
void mapsObjectClassPropertyFieldInMapValueTypeAsKey() {
11151126

@@ -1357,6 +1368,10 @@ static class EntityWithObjectMap {
13571368
Map<Object, NestedDocument> concreteMap;
13581369
}
13591370

1371+
static class EntityWithIntKeyedMap{
1372+
Map<Integer, EntityWithObjectMap> intKeyedMap;
1373+
}
1374+
13601375
static class ClassWithEnum {
13611376

13621377
Allocation allocation;

0 commit comments

Comments
 (0)