Skip to content

Commit ec1a6b5

Browse files
christophstroblodrotbohm
authored andcommitted
DATAMONGO-1025 - Fix creation of nested named index.
We new prefix explicitly named indexes on nested types (eg. for embedded properties) with the path pointing to the property. This avoids errors having equally named index definitions on different paths pointing to the same type within one collection. Along the way we harmonized index naming for geospatial index definitions where only the properties field name was taken into account where it should have been the full property path. Original pull request: #219.
1 parent adc5485 commit ec1a6b5

File tree

6 files changed

+244
-22
lines changed

6 files changed

+244
-22
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/CompoundIndex.java

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,43 @@
7373
boolean dropDups() default false;
7474

7575
/**
76-
* The name of the index to be created.
76+
* The name of the index to be created. <br />
77+
* <br />
78+
* The name will only be applied as is when defined on root level. For usage on nested or embedded structures the
79+
* provided name will be prefixed with the path leading to the entity. <br />
80+
* <br />
81+
* The structure below
82+
*
83+
* <pre>
84+
* <code>
85+
* &#64;Document
86+
* class Root {
87+
* Hybrid hybrid;
88+
* Nested nested;
89+
* }
90+
*
91+
* &#64;Document
92+
* &#64;CompoundIndex(name = "compound_index", def = "{'h1': 1, 'h2': 1}")
93+
* class Hybrid {
94+
* String h1, h2;
95+
* }
96+
*
97+
* &#64;CompoundIndex(name = "compound_index", def = "{'n1': 1, 'n2': 1}")
98+
* class Nested {
99+
* String n1, n2;
100+
* }
101+
* </code>
102+
* </pre>
103+
*
104+
* resolves in the following index structures
105+
*
106+
* <pre>
107+
* <code>
108+
* db.root.ensureIndex( { hybrid.h1: 1, hybrid.h2: 1 } , { name: "hybrid.compound_index" } )
109+
* db.root.ensureIndex( { nested.n1: 1, nested.n2: 1 } , { name: "nested.compound_index" } )
110+
* db.hybrid.ensureIndex( { h1: 1, h2: 1 } , { name: "compound_index" } )
111+
* </code>
112+
* </pre>
77113
*
78114
* @return
79115
*/

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/GeoSpatialIndexed.java

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,41 @@
3232
public @interface GeoSpatialIndexed {
3333

3434
/**
35-
* Name of the property in the document that contains the [x, y] or radial coordinates to index.
35+
* Index name. <br />
36+
* <br />
37+
* The name will only be applied as is when defined on root level. For usage on nested or embedded structures the
38+
* provided name will be prefixed with the path leading to the entity. <br />
39+
* <br />
40+
* The structure below
41+
*
42+
* <pre>
43+
* <code>
44+
* &#64;Document
45+
* class Root {
46+
* Hybrid hybrid;
47+
* Nested nested;
48+
* }
49+
*
50+
* &#64;Document
51+
* class Hybrid {
52+
* &#64;GeoSpatialIndexed(name="index") Point h1;
53+
* }
54+
*
55+
* class Nested {
56+
* &#64;GeoSpatialIndexed(name="index") Point n1;
57+
* }
58+
* </code>
59+
* </pre>
60+
*
61+
* resolves in the following index structures
62+
*
63+
* <pre>
64+
* <code>
65+
* db.root.ensureIndex( { hybrid.h1: "2d" } , { name: "hybrid.index" } )
66+
* db.root.ensureIndex( { nested.n1: "2d" } , { name: "nested.index" } )
67+
* db.hybrid.ensureIndex( { h1: "2d" } , { name: "index" } )
68+
* </code>
69+
* </pre>
3670
*
3771
* @return
3872
*/

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Indexed.java

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,41 @@
5858
boolean dropDups() default false;
5959

6060
/**
61-
* Index name.
61+
* Index name. <br />
62+
* <br />
63+
* The name will only be applied as is when defined on root level. For usage on nested or embedded structures the
64+
* provided name will be prefixed with the path leading to the entity. <br />
65+
* <br />
66+
* The structure below
67+
*
68+
* <pre>
69+
* <code>
70+
* &#64;Document
71+
* class Root {
72+
* Hybrid hybrid;
73+
* Nested nested;
74+
* }
75+
*
76+
* &#64;Document
77+
* class Hybrid {
78+
* &#64;Indexed(name="index") String h1;
79+
* }
80+
*
81+
* class Nested {
82+
* &#64;Indexed(name="index") String n1;
83+
* }
84+
* </code>
85+
* </pre>
86+
*
87+
* resolves in the following index structures
88+
*
89+
* <pre>
90+
* <code>
91+
* db.root.ensureIndex( { hybrid.h1: 1 } , { name: "hybrid.index" } )
92+
* db.root.ensureIndex( { nested.n1: 1 } , { name: "nested.index" } )
93+
* db.hybrid.ensureIndex( { h1: 1} , { name: "index" } )
94+
* </code>
95+
* </pre>
6296
*
6397
* @return
6498
*/

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolver.java

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525

2626
import org.slf4j.Logger;
2727
import org.slf4j.LoggerFactory;
28-
import org.springframework.core.annotation.AnnotationUtils;
2928
import org.springframework.dao.InvalidDataAccessApiUsageException;
3029
import org.springframework.data.domain.Sort;
3130
import org.springframework.data.mapping.PropertyHandler;
@@ -96,7 +95,7 @@ public List<IndexDefinitionHolder> resolveIndexForEntity(final MongoPersistentEn
9695
Assert.notNull(document, "Given entity is not collection root.");
9796

9897
final List<IndexDefinitionHolder> indexInformation = new ArrayList<MongoPersistentEntityIndexResolver.IndexDefinitionHolder>();
99-
indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions("", root.getCollection(), root.getType()));
98+
indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions("", root.getCollection(), root));
10099
indexInformation.addAll(potentiallyCreateTextIndexDefinition(root));
101100

102101
final CycleGuard guard = new CycleGuard();
@@ -138,10 +137,11 @@ public void doWithPersistentProperty(MongoPersistentProperty persistentProperty)
138137
private List<IndexDefinitionHolder> resolveIndexForClass(final Class<?> type, final String path,
139138
final String collection, final CycleGuard guard) {
140139

140+
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(type);
141+
141142
final List<IndexDefinitionHolder> indexInformation = new ArrayList<MongoPersistentEntityIndexResolver.IndexDefinitionHolder>();
142-
indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions(path, collection, type));
143+
indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions(path, collection, entity));
143144

144-
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(type);
145145
entity.doWithProperties(new PropertyHandler<MongoPersistentProperty>() {
146146

147147
@Override
@@ -183,14 +183,13 @@ private IndexDefinitionHolder createIndexDefinitionHolderForProperty(String dotP
183183
}
184184

185185
private List<IndexDefinitionHolder> potentiallyCreateCompoundIndexDefinitions(String dotPath, String collection,
186-
Class<?> type) {
186+
MongoPersistentEntity<?> entity) {
187187

188-
if (AnnotationUtils.findAnnotation(type, CompoundIndexes.class) == null
189-
&& AnnotationUtils.findAnnotation(type, CompoundIndex.class) == null) {
188+
if (entity.findAnnotation(CompoundIndexes.class) == null && entity.findAnnotation(CompoundIndex.class) == null) {
190189
return Collections.emptyList();
191190
}
192191

193-
return createCompoundIndexDefinitions(dotPath, collection, type);
192+
return createCompoundIndexDefinitions(dotPath, collection, entity);
194193
}
195194

196195
private Collection<? extends IndexDefinitionHolder> potentiallyCreateTextIndexDefinition(MongoPersistentEntity<?> root) {
@@ -278,35 +277,35 @@ public void doWithPersistentProperty(MongoPersistentProperty persistentProperty)
278277
* @return
279278
*/
280279
protected List<IndexDefinitionHolder> createCompoundIndexDefinitions(String dotPath, String fallbackCollection,
281-
Class<?> type) {
280+
MongoPersistentEntity<?> entity) {
282281

283282
List<IndexDefinitionHolder> indexDefinitions = new ArrayList<MongoPersistentEntityIndexResolver.IndexDefinitionHolder>();
284-
CompoundIndexes indexes = AnnotationUtils.findAnnotation(type, CompoundIndexes.class);
283+
CompoundIndexes indexes = entity.findAnnotation(CompoundIndexes.class);
285284

286285
if (indexes != null) {
287286
for (CompoundIndex index : indexes.value()) {
288-
indexDefinitions.add(createCompoundIndexDefinition(dotPath, fallbackCollection, index));
287+
indexDefinitions.add(createCompoundIndexDefinition(dotPath, fallbackCollection, index, entity));
289288
}
290289
}
291290

292-
CompoundIndex index = AnnotationUtils.findAnnotation(type, CompoundIndex.class);
291+
CompoundIndex index = entity.findAnnotation(CompoundIndex.class);
293292

294293
if (index != null) {
295-
indexDefinitions.add(createCompoundIndexDefinition(dotPath, fallbackCollection, index));
294+
indexDefinitions.add(createCompoundIndexDefinition(dotPath, fallbackCollection, index, entity));
296295
}
297296

298297
return indexDefinitions;
299298
}
300299

301300
@SuppressWarnings("deprecation")
302301
protected IndexDefinitionHolder createCompoundIndexDefinition(String dotPath, String fallbackCollection,
303-
CompoundIndex index) {
302+
CompoundIndex index, MongoPersistentEntity<?> entity) {
304303

305304
CompoundIndexDefinition indexDefinition = new CompoundIndexDefinition(resolveCompoundIndexKeyFromStringDefinition(
306305
dotPath, index.def()));
307306

308307
if (!index.useGeneratedName()) {
309-
indexDefinition.named(index.name());
308+
indexDefinition.named(pathAwareIndexName(index.name(), dotPath, null));
310309
}
311310

312311
if (index.unique()) {
@@ -377,7 +376,7 @@ protected IndexDefinitionHolder createIndexDefinition(String dotPath, String fal
377376
IndexDirection.ASCENDING.equals(index.direction()) ? Sort.Direction.ASC : Sort.Direction.DESC);
378377

379378
if (!index.useGeneratedName()) {
380-
indexDefinition.named(StringUtils.hasText(index.name()) ? index.name() : dotPath);
379+
indexDefinition.named(pathAwareIndexName(index.name(), dotPath, persitentProperty));
381380
}
382381

383382
if (index.unique()) {
@@ -419,14 +418,31 @@ protected IndexDefinitionHolder createGeoSpatialIndexDefinition(String dotPath,
419418
indexDefinition.withMin(index.min()).withMax(index.max());
420419

421420
if (!index.useGeneratedName()) {
422-
indexDefinition.named(StringUtils.hasText(index.name()) ? index.name() : persistentProperty.getName());
421+
indexDefinition.named(pathAwareIndexName(index.name(), dotPath, persistentProperty));
423422
}
424423

425424
indexDefinition.typed(index.type()).withBucketSize(index.bucketSize()).withAdditionalField(index.additionalField());
426425

427426
return new IndexDefinitionHolder(dotPath, indexDefinition, collection);
428427
}
429428

429+
private String pathAwareIndexName(String indexName, String dotPath, MongoPersistentProperty property) {
430+
431+
String nameToUse = StringUtils.hasText(indexName) ? indexName : "";
432+
433+
if (!StringUtils.hasText(dotPath) || (property != null && dotPath.equals(property.getFieldName()))) {
434+
return StringUtils.hasText(nameToUse) ? nameToUse : dotPath;
435+
}
436+
437+
if (StringUtils.hasText(dotPath)) {
438+
439+
nameToUse = StringUtils.hasText(nameToUse) ? (property != null ? dotPath.replace("." + property.getFieldName(),
440+
"") : dotPath) + "." + nameToUse : dotPath;
441+
}
442+
return nameToUse;
443+
444+
}
445+
430446
/**
431447
* {@link CycleGuard} holds information about properties and the paths for accessing those. This information is used
432448
* to detect potential cycles within the references.

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorUnitTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,8 @@ public void createsNotNestedGeoSpatialIndexCorrectly() {
163163
new MongoPersistentEntityIndexCreator(mappingContext, factory);
164164

165165
assertThat(keysCaptor.getValue(), equalTo(new BasicDBObjectBuilder().add("company.address.location", "2d").get()));
166-
assertThat(optionsCaptor.getValue(), equalTo(new BasicDBObjectBuilder().add("name", "location").add("min", -180)
167-
.add("max", 180).add("bits", 26).get()));
166+
assertThat(optionsCaptor.getValue(), equalTo(new BasicDBObjectBuilder().add("name", "company.address.location")
167+
.add("min", -180).add("max", 180).add("bits", 26).get()));
168168
}
169169

170170
/**

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolverUnitTests.java

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,60 @@ public String language() {
744744
.resolveIndexForEntity(dummy);
745745
}
746746

747+
/**
748+
* @see DATAMONGO-1025
749+
*/
750+
@Test
751+
public void shouldUsePathIndexAsIndexNameForDocumentsHavingNamedNestedCompoundIndexFixedOnCollection() {
752+
753+
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(DocumentWithNestedDocumentHavingNamedCompoundIndex.class);
754+
assertThat((String) indexDefinitions.get(0).getIndexOptions().get("name"),
755+
equalTo("propertyOfTypeHavingNamedCompoundIndex.c_index"));
756+
}
757+
758+
/**
759+
* @see DATAMONGO-1025
760+
*/
761+
@Test
762+
public void shouldUseIndexNameForNestedTypesWithNamedCompoundIndexDefinition() {
763+
764+
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(DocumentWithNestedTypeHavingNamedCompoundIndex.class);
765+
assertThat((String) indexDefinitions.get(0).getIndexOptions().get("name"),
766+
equalTo("propertyOfTypeHavingNamedCompoundIndex.c_index"));
767+
}
768+
769+
/**
770+
* @see DATAMONGO-1025
771+
*/
772+
@Test
773+
public void shouldUsePathIndexAsIndexNameForDocumentsHavingNamedNestedIndexFixedOnCollection() {
774+
775+
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(DocumentWithNestedDocumentHavingNamedIndex.class);
776+
assertThat((String) indexDefinitions.get(0).getIndexOptions().get("name"),
777+
equalTo("propertyOfTypeHavingNamedIndex.property_index"));
778+
}
779+
780+
/**
781+
* @see DATAMONGO-1025
782+
*/
783+
@Test
784+
public void shouldUseIndexNameForNestedTypesWithNamedIndexDefinition() {
785+
786+
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(DocumentWithNestedTypeHavingNamedIndex.class);
787+
assertThat((String) indexDefinitions.get(0).getIndexOptions().get("name"),
788+
equalTo("propertyOfTypeHavingNamedIndex.property_index"));
789+
}
790+
791+
/**
792+
* @see DATAMONGO-1025
793+
*/
794+
@Test
795+
public void shouldUseIndexNameOnRootLevel() {
796+
797+
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(DocumentWithNamedIndex.class);
798+
assertThat((String) indexDefinitions.get(0).getIndexOptions().get("name"), equalTo("property_index"));
799+
}
800+
747801
@Document
748802
static class MixedIndexRoot {
749803

@@ -836,6 +890,54 @@ static class SelfCyclingViaCollectionType {
836890
List<SelfCyclingViaCollectionType> cyclic;
837891

838892
}
893+
894+
@Document
895+
@CompoundIndex(name = "c_index", def = "{ foo:1, bar:1 }")
896+
static class DocumentWithNamedCompoundIndex {
897+
898+
String property;
899+
}
900+
901+
@Document
902+
static class DocumentWithNamedIndex {
903+
904+
@Indexed(name = "property_index") String property;
905+
}
906+
907+
static class TypeWithNamedIndex {
908+
909+
@Indexed(name = "property_index") String property;
910+
}
911+
912+
@Document
913+
static class DocumentWithNestedDocumentHavingNamedCompoundIndex {
914+
915+
DocumentWithNamedCompoundIndex propertyOfTypeHavingNamedCompoundIndex;
916+
}
917+
918+
@CompoundIndex(name = "c_index", def = "{ foo:1, bar:1 }")
919+
static class TypeWithNamedCompoundIndex {
920+
String property;
921+
}
922+
923+
@Document
924+
static class DocumentWithNestedTypeHavingNamedCompoundIndex {
925+
926+
TypeWithNamedCompoundIndex propertyOfTypeHavingNamedCompoundIndex;
927+
}
928+
929+
@Document
930+
static class DocumentWithNestedDocumentHavingNamedIndex {
931+
932+
DocumentWithNamedIndex propertyOfTypeHavingNamedIndex;
933+
}
934+
935+
@Document
936+
static class DocumentWithNestedTypeHavingNamedIndex {
937+
938+
TypeWithNamedIndex propertyOfTypeHavingNamedIndex;
939+
}
940+
839941
}
840942

841943
private static List<IndexDefinitionHolder> prepareMappingContextAndResolveIndexForType(Class<?> type) {

0 commit comments

Comments
 (0)