Skip to content

Commit d5006bb

Browse files
christophstroblmp911de
authored andcommitted
DATAMONGO-1682 - Add support partialFilterExpression for reactive index creation.
We now support partial filter expression on indexes via Index.partial(…) on the reactive API. This allows to create partial indexes that only index the documents in a collection that meet a specified filter expression. Original pull request: #474.
1 parent 82fdbe8 commit d5006bb

File tree

4 files changed

+169
-18
lines changed

4 files changed

+169
-18
lines changed

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

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,61 +15,115 @@
1515
*/
1616
package org.springframework.data.mongodb.core;
1717

18+
import reactor.core.publisher.Flux;
19+
import reactor.core.publisher.Mono;
20+
21+
import java.util.Collection;
22+
import java.util.Optional;
23+
1824
import org.bson.Document;
25+
import org.springframework.data.mongodb.core.convert.QueryMapper;
1926
import org.springframework.data.mongodb.core.index.IndexDefinition;
2027
import org.springframework.data.mongodb.core.index.IndexInfo;
2128
import org.springframework.data.mongodb.core.index.IndexOperations;
29+
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
2230
import org.springframework.util.Assert;
2331

32+
import com.mongodb.client.model.IndexOptions;
2433
import com.mongodb.reactivestreams.client.ListIndexesPublisher;
2534

26-
import reactor.core.publisher.Flux;
27-
import reactor.core.publisher.Mono;
28-
2935
/**
3036
* Default implementation of {@link IndexOperations}.
3137
*
3238
* @author Mark Paluch
33-
* @since 1.11
39+
* @author Christoph Strobl
40+
* @since 2.0
3441
*/
3542
public class DefaultReactiveIndexOperations implements ReactiveIndexOperations {
3643

44+
private static final String PARTIAL_FILTER_EXPRESSION_KEY = "partialFilterExpression";
45+
3746
private final ReactiveMongoOperations mongoOperations;
3847
private final String collectionName;
48+
private final QueryMapper queryMapper;
49+
private final Optional<Class<?>> type;
50+
51+
/**
52+
* Creates a new {@link DefaultReactiveIndexOperations}.
53+
*
54+
* @param mongoOperations must not be {@literal null}.
55+
* @param collectionName must not be {@literal null}.
56+
*/
57+
public DefaultReactiveIndexOperations(ReactiveMongoOperations mongoOperations, String collectionName,
58+
QueryMapper queryMapper) {
59+
60+
this(mongoOperations, collectionName, queryMapper, null);
61+
}
3962

4063
/**
4164
* Creates a new {@link DefaultReactiveIndexOperations}.
4265
*
4366
* @param mongoOperations must not be {@literal null}.
4467
* @param collectionName must not be {@literal null}.
4568
*/
46-
public DefaultReactiveIndexOperations(ReactiveMongoOperations mongoOperations, String collectionName) {
69+
public DefaultReactiveIndexOperations(ReactiveMongoOperations mongoOperations, String collectionName,
70+
QueryMapper queryMapper, Class<?> type) {
4771

4872
Assert.notNull(mongoOperations, "ReactiveMongoOperations must not be null!");
4973
Assert.notNull(collectionName, "Collection must not be null!");
74+
Assert.notNull(queryMapper, "QueryMapper must not be null!");
5075

5176
this.mongoOperations = mongoOperations;
5277
this.collectionName = collectionName;
78+
this.queryMapper = queryMapper;
79+
this.type = Optional.ofNullable(type);
5380
}
5481

5582
/* (non-Javadoc)
5683
* @see org.springframework.data.mongodb.core.ReactiveIndexOperations#ensureIndex(org.springframework.data.mongodb.core.index.IndexDefinition)
5784
*/
5885
public Mono<String> ensureIndex(final IndexDefinition indexDefinition) {
5986

60-
return mongoOperations.execute(collectionName, (ReactiveCollectionCallback<String>) collection -> {
87+
return mongoOperations.execute(collectionName, collection -> {
6188

6289
Document indexOptions = indexDefinition.getIndexOptions();
6390

6491
if (indexOptions != null) {
65-
return collection.createIndex(indexDefinition.getIndexKeys(),
66-
IndexConverters.indexDefinitionToIndexOptionsConverter().convert(indexDefinition));
92+
93+
IndexOptions ops = IndexConverters.indexDefinitionToIndexOptionsConverter().convert(indexDefinition);
94+
95+
if (indexOptions.containsKey(PARTIAL_FILTER_EXPRESSION_KEY)) {
96+
97+
Assert.isInstanceOf(Document.class, indexOptions.get(PARTIAL_FILTER_EXPRESSION_KEY));
98+
99+
MongoPersistentEntity<?> entity = type
100+
.map(val -> (MongoPersistentEntity) queryMapper.getMappingContext().getRequiredPersistentEntity(val))
101+
.orElseGet(() -> lookupPersistentEntity(collectionName));
102+
103+
ops = ops.partialFilterExpression(
104+
queryMapper.getMappedObject((Document) indexOptions.get(PARTIAL_FILTER_EXPRESSION_KEY), entity));
105+
}
106+
107+
return collection.createIndex(indexDefinition.getIndexKeys(), ops);
67108
}
68109

69110
return collection.createIndex(indexDefinition.getIndexKeys());
70111
}).next();
71112
}
72113

114+
private MongoPersistentEntity<?> lookupPersistentEntity(String collection) {
115+
116+
Collection<? extends MongoPersistentEntity<?>> entities = queryMapper.getMappingContext().getPersistentEntities();
117+
118+
for (MongoPersistentEntity<?> entity : entities) {
119+
if (entity.getCollection().equals(collection)) {
120+
return entity;
121+
}
122+
}
123+
124+
return null;
125+
}
126+
73127
/* (non-Javadoc)
74128
* @see org.springframework.data.mongodb.core.ReactiveIndexOperations#dropIndex(java.lang.String)
75129
*/
@@ -78,7 +132,7 @@ public Mono<Void> dropIndex(final String name) {
78132
return mongoOperations.execute(collectionName, collection -> {
79133

80134
return Mono.from(collection.dropIndex(name));
81-
}).flatMap(success -> Mono.<Void>empty()).next();
135+
}).flatMap(success -> Mono.<Void> empty()).next();
82136
}
83137

84138
/* (non-Javadoc)

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016 the original author or authors.
2+
* Copyright 2016-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,8 +15,6 @@
1515
*/
1616
package org.springframework.data.mongodb.core;
1717

18-
import java.util.List;
19-
2018
import org.springframework.data.mongodb.core.index.IndexDefinition;
2119
import org.springframework.data.mongodb.core.index.IndexInfo;
2220

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -302,14 +302,14 @@ public MongoConverter getConverter() {
302302
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#reactiveIndexOps(java.lang.String)
303303
*/
304304
public ReactiveIndexOperations indexOps(String collectionName) {
305-
return new DefaultReactiveIndexOperations(this, collectionName);
305+
return new DefaultReactiveIndexOperations(this, collectionName, this.queryMapper);
306306
}
307307

308308
/* (non-Javadoc)
309309
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#reactiveIndexOps(java.lang.Class)
310310
*/
311311
public ReactiveIndexOperations indexOps(Class<?> entityClass) {
312-
return new DefaultReactiveIndexOperations(this, determineCollectionName(entityClass));
312+
return new DefaultReactiveIndexOperations(this, determineCollectionName(entityClass), this.queryMapper);
313313
}
314314

315315
public String getCollectionName(Class<?> entityClass) {

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

Lines changed: 103 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,14 @@
1818
import static org.assertj.core.api.Assertions.*;
1919
import static org.hamcrest.core.Is.*;
2020
import static org.junit.Assume.*;
21+
import static org.springframework.data.mongodb.core.index.PartialIndexFilter.*;
22+
import static org.springframework.data.mongodb.core.query.Criteria.*;
2123

24+
import reactor.core.publisher.Mono;
2225
import reactor.test.StepVerifier;
2326

27+
import java.util.function.Predicate;
28+
2429
import org.bson.Document;
2530
import org.junit.Before;
2631
import org.junit.Test;
@@ -31,9 +36,11 @@
3136
import org.springframework.data.mongodb.config.AbstractReactiveMongoConfiguration;
3237
import org.springframework.data.mongodb.core.query.Collation;
3338
import org.springframework.data.mongodb.core.query.Collation.CaseFirst;
34-
import org.springframework.data.mongodb.core.DefaultIndexOperationsIntegrationTests.DefaultIndexOperationsIntegrationTestsSample;
39+
import org.springframework.data.mongodb.core.convert.QueryMapper;
3540
import org.springframework.data.mongodb.core.index.Index;
3641
import org.springframework.data.mongodb.core.index.IndexDefinition;
42+
import org.springframework.data.mongodb.core.index.IndexInfo;
43+
import org.springframework.data.mongodb.core.mapping.Field;
3744
import org.springframework.data.util.Version;
3845
import org.springframework.test.context.ContextConfiguration;
3946
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -64,6 +71,7 @@ protected String getDatabaseName() {
6471
}
6572
}
6673

74+
private static final Version THREE_DOT_TWO = new Version(3, 2);
6775
private static final Version THREE_DOT_FOUR = new Version(3, 4);
6876
private static Version mongoVersion;
6977

@@ -79,8 +87,10 @@ public void setUp() {
7987
String collectionName = this.template.getCollectionName(DefaultIndexOperationsIntegrationTestsSample.class);
8088

8189
this.collection = this.template.getMongoDatabase().getCollection(collectionName, Document.class);
82-
this.collection.dropIndexes();
83-
this.indexOps = new DefaultReactiveIndexOperations(template, collectionName);
90+
Mono.from(this.collection.dropIndexes()).subscribe();
91+
92+
this.indexOps = new DefaultReactiveIndexOperations(template, collectionName,
93+
new QueryMapper(template.getConverter()));
8494
}
8595

8696
private void queryMongoVersionIfNecessary() {
@@ -111,7 +121,7 @@ public void shouldCreateIndexWithCollationCorrectly() {
111121
.append("normalization", false) //
112122
.append("backwards", false);
113123

114-
StepVerifier.create(indexOps.getIndexInfo().filter(val -> val.getName().equals("with-collation")))
124+
StepVerifier.create(indexOps.getIndexInfo().filter(this.indexByName("with-collation"))) //
115125
.consumeNextWith(indexInfo -> {
116126

117127
assertThat(indexInfo.getCollation()).isPresent();
@@ -122,7 +132,96 @@ public void shouldCreateIndexWithCollationCorrectly() {
122132

123133
assertThat(result).isEqualTo(expected);
124134
}) //
135+
.thenAwait();
136+
}
137+
138+
@Test // DATAMONGO-1682
139+
public void shouldApplyPartialFilterCorrectly() {
140+
141+
assumeThat(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO), is(true));
142+
143+
IndexDefinition id = new Index().named("partial-with-criteria").on("k3y", Direction.ASC)
144+
.partial(of(where("q-t-y").gte(10)));
145+
146+
indexOps.ensureIndex(id).subscribe();
147+
148+
StepVerifier.create(indexOps.getIndexInfo().filter(this.indexByName("partial-with-criteria"))) //
149+
.consumeNextWith(indexInfo -> {
150+
assertThat(indexInfo.getPartialFilterExpression()).isEqualTo("{ \"q-t-y\" : { \"$gte\" : 10 } }");
151+
}) //
152+
.thenAwait();
153+
}
154+
155+
@Test // DATAMONGO-1682
156+
public void shouldApplyPartialFilterWithMappedPropertyCorrectly() {
157+
158+
assumeThat(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO), is(true));
159+
160+
IndexDefinition id = new Index().named("partial-with-mapped-criteria").on("k3y", Direction.ASC)
161+
.partial(of(where("quantity").gte(10)));
162+
163+
indexOps.ensureIndex(id).subscribe();
164+
165+
StepVerifier.create(indexOps.getIndexInfo().filter(this.indexByName("partial-with-mapped-criteria"))) //
166+
.consumeNextWith(indexInfo -> {
167+
assertThat(indexInfo.getPartialFilterExpression()).isEqualTo("{ \"qty\" : { \"$gte\" : 10 } }");
168+
}).thenAwait();
169+
}
170+
171+
@Test // DATAMONGO-1682
172+
public void shouldApplyPartialDBOFilterCorrectly() {
173+
174+
assumeThat(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO), is(true));
175+
176+
IndexDefinition id = new Index().named("partial-with-dbo").on("k3y", Direction.ASC)
177+
.partial(of(new org.bson.Document("qty", new org.bson.Document("$gte", 10))));
178+
179+
indexOps.ensureIndex(id).subscribe();
180+
181+
StepVerifier.create(indexOps.getIndexInfo().filter(this.indexByName("partial-with-dbo"))) //
182+
.consumeNextWith(indexInfo -> {
183+
assertThat(indexInfo.getPartialFilterExpression()).isEqualTo("{ \"qty\" : { \"$gte\" : 10 } }");
184+
}) //
185+
.thenAwait();
186+
187+
}
188+
189+
@Test // DATAMONGO-1682
190+
public void shouldFavorExplicitMappingHintViaClass() {
191+
192+
assumeThat(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO), is(true));
193+
194+
IndexDefinition id = new Index().named("partial-with-inheritance").on("k3y", Direction.ASC)
195+
.partial(of(where("age").gte(10)));
196+
197+
indexOps = new DefaultReactiveIndexOperations(template,
198+
this.template.getCollectionName(DefaultIndexOperationsIntegrationTestsSample.class),
199+
new QueryMapper(template.getConverter()), MappingToSameCollection.class);
200+
201+
indexOps.ensureIndex(id).subscribe();
202+
203+
StepVerifier.create(indexOps.getIndexInfo().filter(this.indexByName("partial-with-inheritance"))) //
204+
.consumeNextWith(indexInfo -> {
205+
assertThat(indexInfo.getPartialFilterExpression()).isEqualTo("{ \"a_g_e\" : { \"$gte\" : 10 } }");
206+
}) //
125207
.verifyComplete();
126208
}
127209

210+
Predicate<IndexInfo> indexByName(String name) {
211+
return indexInfo -> indexInfo.getName().equals(name);
212+
}
213+
214+
@org.springframework.data.mongodb.core.mapping.Document(collection = "default-index-operations-tests")
215+
static class DefaultIndexOperationsIntegrationTestsSample {
216+
217+
@Field("qty") Integer quantity;
218+
}
219+
220+
@org.springframework.data.mongodb.core.mapping.Document(collection = "default-index-operations-tests")
221+
static class MappingToSameCollection
222+
extends DefaultIndexOperationsIntegrationTests.DefaultIndexOperationsIntegrationTestsSample {
223+
224+
@Field("a_g_e") Integer age;
225+
}
226+
128227
}

0 commit comments

Comments
 (0)