Skip to content

Commit 9a02419

Browse files
DATAMONGO-1467 - Add support for MongoDB 3.2 partialFilterExpression for index creation.
We now support partial filter expression on indexes via Index.partial(…). This allows to create partial indexes that only index the documents in a collection that meet a specified filter expression. new Index().named("idx").on("k3y", ASC).partial(filter(where("age").gte(10))) The filter expression can be set via a plain DBObject or a CriteriaDefinition and is mapped against the associated domain type. Original pull request: #431.
1 parent 7791456 commit 9a02419

File tree

14 files changed

+437
-54
lines changed

14 files changed

+437
-54
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MappingMongoConverterParser.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import org.springframework.data.mapping.model.CamelCaseAbbreviatingFieldNamingStrategy;
5656
import org.springframework.data.mongodb.core.convert.CustomConversions;
5757
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
58+
import org.springframework.data.mongodb.core.convert.QueryMapper;
5859
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator;
5960
import org.springframework.data.mongodb.core.mapping.Document;
6061
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
@@ -125,6 +126,7 @@ public BeanDefinition parse(Element element, ParserContext parserContext) {
125126

126127
BeanDefinitionBuilder indexOperationsProviderBuilder = BeanDefinitionBuilder.genericBeanDefinition("org.springframework.data.mongodb.core.DefaultIndexOperationsProvider");
127128
indexOperationsProviderBuilder.addConstructorArgReference(dbFactoryRef);
129+
indexOperationsProviderBuilder.addConstructorArgValue(BeanDefinitionBuilder.genericBeanDefinition(QueryMapper.class).addConstructorArgReference(id).getBeanDefinition());
128130
parserContext.registerBeanComponent(new BeanComponentDefinition(indexOperationsProviderBuilder.getBeanDefinition(), "indexOperationsProvider"));
129131
}
130132

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

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,17 @@
1818
import static org.springframework.data.mongodb.core.MongoTemplate.*;
1919

2020
import java.util.ArrayList;
21+
import java.util.Arrays;
22+
import java.util.Collection;
2123
import java.util.List;
2224

2325
import org.bson.Document;
2426
import org.springframework.dao.DataAccessException;
27+
import org.springframework.data.mongodb.core.convert.QueryMapper;
2528
import org.springframework.data.mongodb.MongoDbFactory;
2629
import org.springframework.data.mongodb.core.index.IndexDefinition;
2730
import org.springframework.data.mongodb.core.index.IndexInfo;
31+
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
2832
import org.springframework.util.Assert;
2933

3034
import com.mongodb.MongoException;
@@ -43,22 +47,45 @@
4347
*/
4448
public class DefaultIndexOperations implements IndexOperations {
4549

50+
public static final String PARTIAL_FILTER_EXPRESSION_KEY = "partialFilterExpression";
51+
4652
private final MongoDbFactory mongoDbFactory;
4753
private final String collectionName;
54+
private final QueryMapper mapper;
55+
private final Class<?> type;
4856

4957
/**
5058
* Creates a new {@link DefaultIndexOperations}.
5159
*
5260
* @param mongoDbFactory must not be {@literal null}.
5361
* @param collectionName must not be {@literal null}.
62+
* @param queryMapper must not be {@literal null}.
5463
*/
55-
public DefaultIndexOperations(MongoDbFactory mongoDbFactory, String collectionName) {
64+
public DefaultIndexOperations(MongoDbFactory mongoDbFactory, String collectionName, QueryMapper queryMapper) {
65+
66+
this(mongoDbFactory, collectionName, queryMapper, null);
67+
}
68+
69+
/**
70+
* Creates a new {@link DefaultIndexOperations}.
71+
*
72+
* @param mongoDbFactory must not be {@literal null}.
73+
* @param collectionName must not be {@literal null}.
74+
* @param queryMapper must not be {@literal null}.
75+
* @param type Type used for mapping potential partial index filter expression. Can be {@literal null}.
76+
* @since 1.10
77+
*/
78+
public DefaultIndexOperations(MongoDbFactory mongoDbFactory, String collectionName, QueryMapper queryMapper,
79+
Class<?> type) {
5680

5781
Assert.notNull(mongoDbFactory, "MongoDbFactory must not be null!");
5882
Assert.notNull(collectionName, "Collection name can not be null!");
83+
Assert.notNull(queryMapper, "QueryMapper must not be null!");
5984

6085
this.mongoDbFactory = mongoDbFactory;
6186
this.collectionName = collectionName;
87+
this.mapper = queryMapper;
88+
this.type = type;
6289
}
6390

6491
/*
@@ -74,10 +101,38 @@ public String ensureIndex(final IndexDefinition indexDefinition) {
74101
if (indexOptions != null) {
75102

76103
IndexOptions ops = IndexConverters.indexDefinitionToIndexOptionsConverter().convert(indexDefinition);
104+
105+
if (indexOptions.containsKey(PARTIAL_FILTER_EXPRESSION_KEY)) {
106+
107+
Assert.isInstanceOf(Document.class, indexOptions.get(PARTIAL_FILTER_EXPRESSION_KEY));
108+
109+
ops.partialFilterExpression( mapper.getMappedObject(
110+
(Document) indexOptions.get(PARTIAL_FILTER_EXPRESSION_KEY), lookupPersistentEntity(type, collectionName)));
111+
}
112+
77113
return collection.createIndex(indexDefinition.getIndexKeys(), ops);
78114
}
79115
return collection.createIndex(indexDefinition.getIndexKeys());
80-
});
116+
}
117+
118+
);
119+
}
120+
121+
private MongoPersistentEntity<?> lookupPersistentEntity(Class<?> entityType, String collection) {
122+
123+
if (entityType != null) {
124+
return mapper.getMappingContext().getPersistentEntity(entityType);
125+
}
126+
127+
Collection<? extends MongoPersistentEntity<?>> entities = mapper.getMappingContext().getPersistentEntities();
128+
129+
for (MongoPersistentEntity<?> entity : entities) {
130+
if (entity.getCollection().equals(collection)) {
131+
return entity;
132+
}
133+
}
134+
135+
return null;
81136
}
82137

83138
/*

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.mongodb.core;
1717

1818
import org.springframework.data.mongodb.MongoDbFactory;
19+
import org.springframework.data.mongodb.core.convert.QueryMapper;
1920

2021
/**
2122
* {@link IndexOperationsProvider} to obtain {@link IndexOperations} from a given {@link MongoDbFactory}. TODO: Review
@@ -27,19 +28,20 @@
2728
class DefaultIndexOperationsProvider implements IndexOperationsProvider {
2829

2930
private final MongoDbFactory mongoDbFactory;
31+
private final QueryMapper mapper;
3032

3133
/**
3234
* @param mongoDbFactory must not be {@literal null}.
3335
*/
34-
DefaultIndexOperationsProvider(MongoDbFactory mongoDbFactory) {
35-
this.mongoDbFactory = mongoDbFactory;
36+
DefaultIndexOperationsProvider(MongoDbFactory mongoDbFactory, QueryMapper mapper) {
37+
this.mongoDbFactory = mongoDbFactory; this.mapper = mapper;
3638
}
3739

3840
/* (non-Javadoc)
3941
* @see org.springframework.data.mongodb.core.IndexOperationsProvider#reactiveIndexOps(java.lang.String)
4042
*/
4143
@Override
4244
public IndexOperations indexOps(String collectionName) {
43-
return new DefaultIndexOperations(mongoDbFactory, collectionName);
45+
return new DefaultIndexOperations(mongoDbFactory, collectionName, mapper);
4446
}
4547
}

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

Lines changed: 5 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -117,50 +117,18 @@ private static Converter<IndexDefinition, IndexOptions> getIndexDefinitionIndexO
117117
}
118118
}
119119

120+
if(indexOptions.containsKey("partialFilterExpression")) {
121+
ops = ops.partialFilterExpression((org.bson.Document)indexOptions.get("partialFilterExpression"));
122+
}
123+
120124
return ops;
121125
};
122126
}
123127

124128
private static Converter<Document, IndexInfo> getDocumentIndexInfoConverter() {
125129

126130
return ix -> {
127-
Document keyDocument = (Document) ix.get("key");
128-
int numberOfElements = keyDocument.keySet().size();
129-
130-
List<IndexField> indexFields = new ArrayList<IndexField>(numberOfElements);
131-
132-
for (String key : keyDocument.keySet()) {
133-
134-
Object value = keyDocument.get(key);
135-
136-
if (TWO_D_IDENTIFIERS.contains(value)) {
137-
indexFields.add(IndexField.geo(key));
138-
} else if ("text".equals(value)) {
139-
140-
Document weights = (Document) ix.get("weights");
141-
for (String fieldName : weights.keySet()) {
142-
indexFields.add(IndexField.text(fieldName, Float.valueOf(weights.get(fieldName).toString())));
143-
}
144-
145-
} else {
146-
147-
Double keyValue = new Double(value.toString());
148-
149-
if (ONE.equals(keyValue)) {
150-
indexFields.add(IndexField.create(key, ASC));
151-
} else if (MINUS_ONE.equals(keyValue)) {
152-
indexFields.add(IndexField.create(key, DESC));
153-
}
154-
}
155-
}
156-
157-
String name = ix.get("name").toString();
158-
159-
boolean unique = ix.containsKey("unique") ? (Boolean) ix.get("unique") : false;
160-
boolean sparse = ix.containsKey("sparse") ? (Boolean) ix.get("sparse") : false;
161-
162-
String language = ix.containsKey("default_language") ? (String) ix.get("default_language") : "";
163-
return new IndexInfo(indexFields, name, unique, sparse, language);
131+
return IndexInfo.indexInfoOf(ix);
164132
};
165133
}
166134
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.data.mongodb.core;
1818

19+
import org.springframework.data.mongodb.core.convert.QueryMapper;
20+
1921
/**
2022
* TODO: Revisit for a better pattern.
2123
* @author Mark Paluch

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -539,11 +539,11 @@ public Void doInCollection(MongoCollection<Document> collection) throws MongoExc
539539
}
540540

541541
public IndexOperations indexOps(String collectionName) {
542-
return new DefaultIndexOperations(getMongoDbFactory(), collectionName);
542+
return new DefaultIndexOperations(getMongoDbFactory(), collectionName, queryMapper);
543543
}
544544

545545
public IndexOperations indexOps(Class<?> entityClass) {
546-
return new DefaultIndexOperations(getMongoDbFactory(), determineCollectionName(entityClass));
546+
return new DefaultIndexOperations(getMongoDbFactory(), determineCollectionName(entityClass), queryMapper);
547547
}
548548

549549
public BulkOperations bulkOps(BulkMode bulkMode, String collectionName) {
@@ -2176,7 +2176,7 @@ static RuntimeException potentiallyConvertRuntimeException(RuntimeException ex,
21762176
*
21772177
* @param ids
21782178
* @param documents
2179-
* @return
2179+
* @return
21802180
* TODO: Remove for 2.0 and change method signature of {@link #insertDBObjectList(String, List)}.
21812181
*/
21822182
private static List<Object> consolidateIdentifiers(List<ObjectId> ids, List<Document> documents) {

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1092,4 +1092,8 @@ public String convert(MongoPersistentProperty source) {
10921092
return source.getFieldName();
10931093
}
10941094
}
1095+
1096+
public MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> getMappingContext() {
1097+
return mappingContext;
1098+
}
10951099
}

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public class GeospatialIndex implements IndexDefinition {
3737
private GeoSpatialIndexType type = GeoSpatialIndexType.GEO_2D;
3838
private Double bucketSize = 1.0;
3939
private String additionalField;
40+
private IndexFilter filter;
4041

4142
/**
4243
* Creates a new {@link GeospatialIndex} for the given field.
@@ -117,6 +118,21 @@ public GeospatialIndex withAdditionalField(String fieldName) {
117118
return this;
118119
}
119120

121+
/**
122+
* Only index the documents in a collection that meet a specified {@link IndexFilter filter expression}.
123+
*
124+
* @param filter can be {@literal null}.
125+
* @return
126+
* @see <a href=
127+
* "https://docs.mongodb.com/manual/core/index-partial/">https://docs.mongodb.com/manual/core/index-partial/</a>
128+
* @since 1.10
129+
*/
130+
public GeospatialIndex partial(IndexFilter filter) {
131+
132+
this.filter = filter;
133+
return this;
134+
}
135+
120136
public Document getIndexKeys() {
121137

122138
Document document = new Document();
@@ -184,6 +200,10 @@ public Document getIndexOptions() {
184200
break;
185201
}
186202

203+
if (filter != null) {
204+
dbo.put("partialFilterExpression", filter.getFilterObject());
205+
}
206+
187207
return dbo;
188208
}
189209

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ public enum Duplicates {
5050

5151
private long expire = -1;
5252

53+
private IndexFilter filter;
54+
5355
public Index() {}
5456

5557
public Index(String key, Direction direction) {
@@ -126,6 +128,21 @@ public Index expire(long value, TimeUnit unit) {
126128
return this;
127129
}
128130

131+
/**
132+
* Only index the documents in a collection that meet a specified {@link IndexFilter filter expression}.
133+
*
134+
* @param filter can be {@literal null}.
135+
* @return
136+
* @see <a href=
137+
* "https://docs.mongodb.com/manual/core/index-partial/">https://docs.mongodb.com/manual/core/index-partial/</a>
138+
* @since 1.10
139+
*/
140+
public Index partial(IndexFilter filter) {
141+
142+
this.filter = filter;
143+
return this;
144+
}
145+
129146
/*
130147
* (non-Javadoc)
131148
* @see org.springframework.data.mongodb.core.index.IndexDefinition#getIndexKeys()
@@ -163,6 +180,9 @@ public Document getIndexOptions() {
163180
document.put("expireAfterSeconds", expire);
164181
}
165182

183+
if (filter != null) {
184+
document.put("partialFilterExpression", filter.getFilterObject());
185+
}
166186
return document;
167187
}
168188

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2016. the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mongodb.core.index;
17+
18+
import org.bson.Document;
19+
20+
/**
21+
* Use {@link IndexFilter} to create the partial filter expression used when creating
22+
* <a href="https://docs.mongodb.com/manual/core/index-partial/">Partial Indexes</a>.
23+
*
24+
* @author Christoph Strobl
25+
* @since 1.10
26+
*/
27+
public interface IndexFilter {
28+
29+
/**
30+
* Get the raw (unmapped) filter expression.
31+
*
32+
* @return
33+
*/
34+
Document getFilterObject();
35+
36+
}

0 commit comments

Comments
 (0)