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.
This commit is contained in:
Christoph Strobl
2016-12-19 08:24:05 +01:00
parent 779145645d
commit 9a0241992e
14 changed files with 437 additions and 54 deletions

View File

@@ -55,6 +55,7 @@ import org.springframework.data.mapping.context.MappingContextIsNewStrategyFacto
import org.springframework.data.mapping.model.CamelCaseAbbreviatingFieldNamingStrategy;
import org.springframework.data.mongodb.core.convert.CustomConversions;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.QueryMapper;
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
@@ -125,6 +126,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
BeanDefinitionBuilder indexOperationsProviderBuilder = BeanDefinitionBuilder.genericBeanDefinition("org.springframework.data.mongodb.core.DefaultIndexOperationsProvider");
indexOperationsProviderBuilder.addConstructorArgReference(dbFactoryRef);
indexOperationsProviderBuilder.addConstructorArgValue(BeanDefinitionBuilder.genericBeanDefinition(QueryMapper.class).addConstructorArgReference(id).getBeanDefinition());
parserContext.registerBeanComponent(new BeanComponentDefinition(indexOperationsProviderBuilder.getBeanDefinition(), "indexOperationsProvider"));
}

View File

@@ -18,13 +18,17 @@ package org.springframework.data.mongodb.core;
import static org.springframework.data.mongodb.core.MongoTemplate.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.bson.Document;
import org.springframework.dao.DataAccessException;
import org.springframework.data.mongodb.core.convert.QueryMapper;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.index.IndexDefinition;
import org.springframework.data.mongodb.core.index.IndexInfo;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.util.Assert;
import com.mongodb.MongoException;
@@ -43,22 +47,45 @@ import com.mongodb.client.model.IndexOptions;
*/
public class DefaultIndexOperations implements IndexOperations {
public static final String PARTIAL_FILTER_EXPRESSION_KEY = "partialFilterExpression";
private final MongoDbFactory mongoDbFactory;
private final String collectionName;
private final QueryMapper mapper;
private final Class<?> type;
/**
* Creates a new {@link DefaultIndexOperations}.
*
* @param mongoDbFactory must not be {@literal null}.
* @param collectionName must not be {@literal null}.
* @param queryMapper must not be {@literal null}.
*/
public DefaultIndexOperations(MongoDbFactory mongoDbFactory, String collectionName) {
public DefaultIndexOperations(MongoDbFactory mongoDbFactory, String collectionName, QueryMapper queryMapper) {
this(mongoDbFactory, collectionName, queryMapper, null);
}
/**
* Creates a new {@link DefaultIndexOperations}.
*
* @param mongoDbFactory must not be {@literal null}.
* @param collectionName must not be {@literal null}.
* @param queryMapper must not be {@literal null}.
* @param type Type used for mapping potential partial index filter expression. Can be {@literal null}.
* @since 1.10
*/
public DefaultIndexOperations(MongoDbFactory mongoDbFactory, String collectionName, QueryMapper queryMapper,
Class<?> type) {
Assert.notNull(mongoDbFactory, "MongoDbFactory must not be null!");
Assert.notNull(collectionName, "Collection name can not be null!");
Assert.notNull(queryMapper, "QueryMapper must not be null!");
this.mongoDbFactory = mongoDbFactory;
this.collectionName = collectionName;
this.mapper = queryMapper;
this.type = type;
}
/*
@@ -74,10 +101,38 @@ public class DefaultIndexOperations implements IndexOperations {
if (indexOptions != null) {
IndexOptions ops = IndexConverters.indexDefinitionToIndexOptionsConverter().convert(indexDefinition);
if (indexOptions.containsKey(PARTIAL_FILTER_EXPRESSION_KEY)) {
Assert.isInstanceOf(Document.class, indexOptions.get(PARTIAL_FILTER_EXPRESSION_KEY));
ops.partialFilterExpression( mapper.getMappedObject(
(Document) indexOptions.get(PARTIAL_FILTER_EXPRESSION_KEY), lookupPersistentEntity(type, collectionName)));
}
return collection.createIndex(indexDefinition.getIndexKeys(), ops);
}
return collection.createIndex(indexDefinition.getIndexKeys());
});
}
);
}
private MongoPersistentEntity<?> lookupPersistentEntity(Class<?> entityType, String collection) {
if (entityType != null) {
return mapper.getMappingContext().getPersistentEntity(entityType);
}
Collection<? extends MongoPersistentEntity<?>> entities = mapper.getMappingContext().getPersistentEntities();
for (MongoPersistentEntity<?> entity : entities) {
if (entity.getCollection().equals(collection)) {
return entity;
}
}
return null;
}
/*

View File

@@ -16,6 +16,7 @@
package org.springframework.data.mongodb.core;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.convert.QueryMapper;
/**
* {@link IndexOperationsProvider} to obtain {@link IndexOperations} from a given {@link MongoDbFactory}. TODO: Review
@@ -27,12 +28,13 @@ import org.springframework.data.mongodb.MongoDbFactory;
class DefaultIndexOperationsProvider implements IndexOperationsProvider {
private final MongoDbFactory mongoDbFactory;
private final QueryMapper mapper;
/**
* @param mongoDbFactory must not be {@literal null}.
*/
DefaultIndexOperationsProvider(MongoDbFactory mongoDbFactory) {
this.mongoDbFactory = mongoDbFactory;
DefaultIndexOperationsProvider(MongoDbFactory mongoDbFactory, QueryMapper mapper) {
this.mongoDbFactory = mongoDbFactory; this.mapper = mapper;
}
/* (non-Javadoc)
@@ -40,6 +42,6 @@ class DefaultIndexOperationsProvider implements IndexOperationsProvider {
*/
@Override
public IndexOperations indexOps(String collectionName) {
return new DefaultIndexOperations(mongoDbFactory, collectionName);
return new DefaultIndexOperations(mongoDbFactory, collectionName, mapper);
}
}

View File

@@ -117,6 +117,10 @@ abstract class IndexConverters {
}
}
if(indexOptions.containsKey("partialFilterExpression")) {
ops = ops.partialFilterExpression((org.bson.Document)indexOptions.get("partialFilterExpression"));
}
return ops;
};
}
@@ -124,43 +128,7 @@ abstract class IndexConverters {
private static Converter<Document, IndexInfo> getDocumentIndexInfoConverter() {
return ix -> {
Document keyDocument = (Document) ix.get("key");
int numberOfElements = keyDocument.keySet().size();
List<IndexField> indexFields = new ArrayList<IndexField>(numberOfElements);
for (String key : keyDocument.keySet()) {
Object value = keyDocument.get(key);
if (TWO_D_IDENTIFIERS.contains(value)) {
indexFields.add(IndexField.geo(key));
} else if ("text".equals(value)) {
Document weights = (Document) ix.get("weights");
for (String fieldName : weights.keySet()) {
indexFields.add(IndexField.text(fieldName, Float.valueOf(weights.get(fieldName).toString())));
}
} else {
Double keyValue = new Double(value.toString());
if (ONE.equals(keyValue)) {
indexFields.add(IndexField.create(key, ASC));
} else if (MINUS_ONE.equals(keyValue)) {
indexFields.add(IndexField.create(key, DESC));
}
}
}
String name = ix.get("name").toString();
boolean unique = ix.containsKey("unique") ? (Boolean) ix.get("unique") : false;
boolean sparse = ix.containsKey("sparse") ? (Boolean) ix.get("sparse") : false;
String language = ix.containsKey("default_language") ? (String) ix.get("default_language") : "";
return new IndexInfo(indexFields, name, unique, sparse, language);
return IndexInfo.indexInfoOf(ix);
};
}
}

View File

@@ -16,6 +16,8 @@
package org.springframework.data.mongodb.core;
import org.springframework.data.mongodb.core.convert.QueryMapper;
/**
* TODO: Revisit for a better pattern.
* @author Mark Paluch

View File

@@ -539,11 +539,11 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
}
public IndexOperations indexOps(String collectionName) {
return new DefaultIndexOperations(getMongoDbFactory(), collectionName);
return new DefaultIndexOperations(getMongoDbFactory(), collectionName, queryMapper);
}
public IndexOperations indexOps(Class<?> entityClass) {
return new DefaultIndexOperations(getMongoDbFactory(), determineCollectionName(entityClass));
return new DefaultIndexOperations(getMongoDbFactory(), determineCollectionName(entityClass), queryMapper);
}
public BulkOperations bulkOps(BulkMode bulkMode, String collectionName) {
@@ -2176,7 +2176,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
*
* @param ids
* @param documents
* @return
* @return
* TODO: Remove for 2.0 and change method signature of {@link #insertDBObjectList(String, List)}.
*/
private static List<Object> consolidateIdentifiers(List<ObjectId> ids, List<Document> documents) {

View File

@@ -1092,4 +1092,8 @@ public class QueryMapper {
return source.getFieldName();
}
}
public MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> getMappingContext() {
return mappingContext;
}
}

View File

@@ -37,6 +37,7 @@ public class GeospatialIndex implements IndexDefinition {
private GeoSpatialIndexType type = GeoSpatialIndexType.GEO_2D;
private Double bucketSize = 1.0;
private String additionalField;
private IndexFilter filter;
/**
* Creates a new {@link GeospatialIndex} for the given field.
@@ -117,6 +118,21 @@ public class GeospatialIndex implements IndexDefinition {
return this;
}
/**
* Only index the documents in a collection that meet a specified {@link IndexFilter filter expression}.
*
* @param filter can be {@literal null}.
* @return
* @see <a href=
* "https://docs.mongodb.com/manual/core/index-partial/">https://docs.mongodb.com/manual/core/index-partial/</a>
* @since 1.10
*/
public GeospatialIndex partial(IndexFilter filter) {
this.filter = filter;
return this;
}
public Document getIndexKeys() {
Document document = new Document();
@@ -184,6 +200,10 @@ public class GeospatialIndex implements IndexDefinition {
break;
}
if (filter != null) {
dbo.put("partialFilterExpression", filter.getFilterObject());
}
return dbo;
}

View File

@@ -50,6 +50,8 @@ public class Index implements IndexDefinition {
private long expire = -1;
private IndexFilter filter;
public Index() {}
public Index(String key, Direction direction) {
@@ -126,6 +128,21 @@ public class Index implements IndexDefinition {
return this;
}
/**
* Only index the documents in a collection that meet a specified {@link IndexFilter filter expression}.
*
* @param filter can be {@literal null}.
* @return
* @see <a href=
* "https://docs.mongodb.com/manual/core/index-partial/">https://docs.mongodb.com/manual/core/index-partial/</a>
* @since 1.10
*/
public Index partial(IndexFilter filter) {
this.filter = filter;
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.index.IndexDefinition#getIndexKeys()
@@ -163,6 +180,9 @@ public class Index implements IndexDefinition {
document.put("expireAfterSeconds", expire);
}
if (filter != null) {
document.put("partialFilterExpression", filter.getFilterObject());
}
return document;
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2016. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.index;
import org.bson.Document;
/**
* Use {@link IndexFilter} to create the partial filter expression used when creating
* <a href="https://docs.mongodb.com/manual/core/index-partial/">Partial Indexes</a>.
*
* @author Christoph Strobl
* @since 1.10
*/
public interface IndexFilter {
/**
* Get the raw (unmapped) filter expression.
*
* @return
*/
Document getFilterObject();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,11 +15,16 @@
*/
package org.springframework.data.mongodb.core.index;
import static org.springframework.data.domain.Sort.Direction.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import com.mongodb.DBObject;
import org.bson.Document;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
@@ -30,12 +35,17 @@ import org.springframework.util.ObjectUtils;
*/
public class IndexInfo {
private static final Double ONE = Double.valueOf(1);
private static final Double MINUS_ONE = Double.valueOf(-1);
private static final Collection<String> TWO_D_IDENTIFIERS = Arrays.asList("2d", "2dsphere");
private final List<IndexField> indexFields;
private final String name;
private final boolean unique;
private final boolean sparse;
private final String language;
private String partialFilterExpression;
public IndexInfo(List<IndexField> indexFields, String name, boolean unique, boolean sparse,
String language) {
@@ -47,6 +57,59 @@ public class IndexInfo {
this.language = language;
}
/**
* Creates new {@link IndexInfo} parsing required properties from the given {@literal sourceDocument}.
*
* @param sourceDocument
* @return
* @since 1.10
*/
public static IndexInfo indexInfoOf(Document sourceDocument) {
Document keyDbObject = (Document) sourceDocument.get("key");
int numberOfElements = keyDbObject.keySet().size();
List<IndexField> indexFields = new ArrayList<IndexField>(numberOfElements);
for (String key : keyDbObject.keySet()) {
Object value = keyDbObject.get(key);
if (TWO_D_IDENTIFIERS.contains(value)) {
indexFields.add(IndexField.geo(key));
} else if ("text".equals(value)) {
Document weights = (Document) sourceDocument.get("weights");
for (String fieldName : weights.keySet()) {
indexFields.add(IndexField.text(fieldName, Float.valueOf(weights.get(fieldName).toString())));
}
} else {
Double keyValue = new Double(value.toString());
if (ONE.equals(keyValue)) {
indexFields.add(IndexField.create(key, ASC));
} else if (MINUS_ONE.equals(keyValue)) {
indexFields.add(IndexField.create(key, DESC));
}
}
}
String name = sourceDocument.get("name").toString();
boolean unique = sourceDocument.containsKey("unique") ? (Boolean) sourceDocument.get("unique") : false;
boolean sparse = sourceDocument.containsKey("sparse") ? (Boolean) sourceDocument.get("sparse") : false;
String language = sourceDocument.containsKey("default_language") ? (String) sourceDocument.get("default_language")
: "";
String partialFilter = sourceDocument.containsKey("partialFilterExpression")
? ((Document)sourceDocument.get("partialFilterExpression")).toJson() : "";
IndexInfo info = new IndexInfo(indexFields, name, unique, sparse, language);
info.partialFilterExpression = partialFilter;
return info;
}
/**
* Returns the individual index fields of the index.
*
@@ -94,9 +157,18 @@ public class IndexInfo {
return language;
}
/**
* @return
* @since 1.0
*/
public String getPartialFilterExpression() {
return partialFilterExpression;
}
@Override
public String toString() {
return "IndexInfo [indexFields=" + indexFields + ", name=" + name + ", unique=" + unique + ", sparse=" + sparse + ", language=" + language + "]";
return "IndexInfo [indexFields=" + indexFields + ", name=" + name + ", unique=" + unique + ", sparse=" + sparse + ", language=" + language + ", partialFilterExpression="
+ partialFilterExpression + "]";
}
@Override
@@ -109,6 +181,7 @@ public class IndexInfo {
result = prime * result + (sparse ? 1231 : 1237);
result = prime * result + (unique ? 1231 : 1237);
result = prime * result + ObjectUtils.nullSafeHashCode(language);
result = prime * result + ObjectUtils.nullSafeHashCode(partialFilterExpression);
return result;
}
@@ -147,6 +220,9 @@ public class IndexInfo {
if (!ObjectUtils.nullSafeEquals(language, other.language)) {
return false;
}
if (!ObjectUtils.nullSafeEquals(partialFilterExpression, other.partialFilterExpression)) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2016. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.index;
import org.bson.Document;
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
import org.springframework.util.Assert;
import com.mongodb.DBObject;
/**
* {@link IndexFilter} implementation for usage with plain {@link DBObject} as well as {@link CriteriaDefinition} filter
* expressions.
*
* @author Christoph Strobl
* @since 1.10
*/
public class PartialIndexFilter implements IndexFilter {
private final Object filterExpression;
private PartialIndexFilter(Object filterExpression) {
Assert.notNull(filterExpression, "FilterExpression must not be null!");
this.filterExpression = filterExpression;
}
/**
* Create new {@link PartialIndexFilter} for given {@link DBObject filter expression}.
*
* @param where must not be {@literal null}.
* @return
*/
public static PartialIndexFilter filter(Document where) {
return new PartialIndexFilter(where);
}
/**
* Create new {@link PartialIndexFilter} for given {@link CriteriaDefinition filter expression}.
*
* @param where must not be {@literal null}.
* @return
*/
public static PartialIndexFilter filter(CriteriaDefinition where) {
return new PartialIndexFilter(where);
}
public Document getFilterObject() {
if (filterExpression instanceof Document) {
return (Document) filterExpression;
}
if (filterExpression instanceof CriteriaDefinition) {
return ((CriteriaDefinition) filterExpression).getCriteriaObject();
}
throw new IllegalArgumentException(
String.format("Unknown type %s used as filter expression.", filterExpression.getClass()));
}
}

View File

@@ -37,6 +37,7 @@ public class TextIndexDefinition implements IndexDefinition {
private Set<TextIndexedFieldSpec> fieldSpecs;
private String defaultLanguage;
private String languageOverride;
private IndexFilter filter;
TextIndexDefinition() {
fieldSpecs = new LinkedHashSet<TextIndexedFieldSpec>();
@@ -127,6 +128,10 @@ public class TextIndexDefinition implements IndexDefinition {
options.put("language_override", languageOverride);
}
if (filter != null) {
options.put("partialFilterExpression", filter.getFilterObject());
}
return options;
}
@@ -325,6 +330,21 @@ public class TextIndexDefinition implements IndexDefinition {
return this;
}
/**
* Only index the documents that meet the specified {@link IndexFilter filter expression}.
*
* @param filter can be {@literal null}.
* @return
* @see <a href=
* "https://docs.mongodb.com/manual/core/index-partial/">https://docs.mongodb.com/manual/core/index-partial/</a>
* @since 1.10
*/
public TextIndexDefinitionBuilder partial(IndexFilter filter) {
this.instance.filter = filter;
return this;
}
public TextIndexDefinition build() {
return this.instance;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,14 +17,24 @@ package org.springframework.data.mongodb.core;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.junit.Assume.*;
import static org.springframework.data.mongodb.core.ReflectiveDBCollectionInvoker.*;
import static org.springframework.data.mongodb.core.index.PartialIndexFilter.*;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import org.bson.Document;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.convert.QueryMapper;
import org.springframework.data.mongodb.core.index.Index;
import org.springframework.data.mongodb.core.index.IndexDefinition;
import org.springframework.data.mongodb.core.index.IndexInfo;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.util.Version;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.ObjectUtils;
@@ -41,20 +51,32 @@ import com.mongodb.client.MongoCollection;
@ContextConfiguration("classpath:infrastructure.xml")
public class DefaultIndexOperationsIntegrationTests {
static final Document GEO_SPHERE_2D = new Document("loaction", "2dsphere");
private static final Version THREE_DOT_TWO = new Version(3, 2);
private static Version mongoVersion;
static final org.bson.Document GEO_SPHERE_2D = new org.bson.Document("loaction", "2dsphere");
@Autowired MongoTemplate template;
DefaultIndexOperations indexOps;
MongoCollection<Document> collection;
MongoCollection<org.bson.Document> collection;
@Before
public void setUp() {
queryMongoVersionIfNecessary();
String collectionName = this.template.getCollectionName(DefaultIndexOperationsIntegrationTestsSample.class);
this.collection = this.template.getDb().getCollection(collectionName, Document.class);
this.collection.dropIndexes();
this.indexOps = new DefaultIndexOperations(template.getMongoDbFactory(), collectionName);
this.indexOps = new DefaultIndexOperations(template.getMongoDbFactory(), collectionName,
new QueryMapper(template.getConverter()));
}
private void queryMongoVersionIfNecessary() {
if (mongoVersion == null) {
Document result = template.executeCommand("{ buildInfo: 1 }");
mongoVersion = Version.parse(result.get("version").toString());
}
}
/**
@@ -69,11 +91,83 @@ public class DefaultIndexOperationsIntegrationTests {
assertThat(info.getIndexFields().get(0).isGeo(), is(true));
}
private IndexInfo findAndReturnIndexInfo(Document keys) {
/**
* @see DATAMONGO-1467
*/
@Test
public void shouldApplyPartialFilterCorrectly() {
assumeThat(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO), is(true));
IndexDefinition id = new Index().named("partial-with-criteria").on("k3y", Direction.ASC)
.partial(filter(where("q-t-y").gte(10)));
indexOps.ensureIndex(id);
IndexInfo info = findAndReturnIndexInfo(indexOps.getIndexInfo(), "partial-with-criteria");
assertThat(info.getPartialFilterExpression(), is(equalTo("{ \"q-t-y\" : { \"$gte\" : 10 } }")));
}
/**
* @see DATAMONGO-1467
*/
@Test
public void shouldApplyPartialFilterWithMappedPropertyCorrectly() {
assumeThat(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO), is(true));
IndexDefinition id = new Index().named("partial-with-mapped-criteria").on("k3y", Direction.ASC)
.partial(filter(where("quantity").gte(10)));
indexOps.ensureIndex(id);
IndexInfo info = findAndReturnIndexInfo(indexOps.getIndexInfo(), "partial-with-mapped-criteria");
assertThat(info.getPartialFilterExpression(), is(equalTo("{ \"qty\" : { \"$gte\" : 10 } }")));
}
/**
* @see DATAMONGO-1467
*/
@Test
public void shouldApplyPartialDBOFilterCorrectly() {
assumeThat(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO), is(true));
IndexDefinition id = new Index().named("partial-with-dbo").on("k3y", Direction.ASC)
.partial(filter(new org.bson.Document("qty", new org.bson.Document("$gte", 10))));
indexOps.ensureIndex(id);
IndexInfo info = findAndReturnIndexInfo(indexOps.getIndexInfo(), "partial-with-dbo");
assertThat(info.getPartialFilterExpression(), is(equalTo("{ \"qty\" : { \"$gte\" : 10 } }")));
}
/**
* @see DATAMONGO-1467
*/
@Test
public void shouldFavorExplicitMappingHintViaClass() {
assumeThat(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO), is(true));
IndexDefinition id = new Index().named("partial-with-inheritance").on("k3y", Direction.ASC)
.partial(filter(where("age").gte(10)));
indexOps = new DefaultIndexOperations(template.getMongoDbFactory(),
this.template.getCollectionName(DefaultIndexOperationsIntegrationTestsSample.class),
new QueryMapper(template.getConverter()), MappingToSameCollection.class);
indexOps.ensureIndex(id);
IndexInfo info = findAndReturnIndexInfo(indexOps.getIndexInfo(), "partial-with-inheritance");
assertThat(info.getPartialFilterExpression(), is(equalTo("{ \"a_g_e\" : { \"$gte\" : 10 } }")));
}
private IndexInfo findAndReturnIndexInfo(org.bson.Document keys) {
return findAndReturnIndexInfo(indexOps.getIndexInfo(), keys);
}
private static IndexInfo findAndReturnIndexInfo(Iterable<IndexInfo> candidates, Document keys) {
private static IndexInfo findAndReturnIndexInfo(Iterable<IndexInfo> candidates, org.bson.Document keys) {
return findAndReturnIndexInfo(candidates, generateIndexName(keys));
}
@@ -87,5 +181,15 @@ public class DefaultIndexOperationsIntegrationTests {
throw new AssertionError(String.format("Index with %s was not found", name));
}
static class DefaultIndexOperationsIntegrationTestsSample {}
@org.springframework.data.mongodb.core.mapping.Document(collection = "default-index-operations-tests")
static class DefaultIndexOperationsIntegrationTestsSample {
@Field("qty") Integer quantity;
}
@org.springframework.data.mongodb.core.mapping.Document(collection = "default-index-operations-tests")
static class MappingToSameCollection extends DefaultIndexOperationsIntegrationTestsSample {
@Field("a_g_e") Integer age;
}
}