Browse Source

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.
pull/410/merge
Christoph Strobl 9 years ago committed by Oliver Gierke
parent
commit
46b119ce71
  1. 97
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java
  2. 2
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
  3. 23
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/GeospatialIndex.java
  4. 22
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Index.java
  5. 36
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexFilter.java
  6. 81
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java
  7. 73
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/PartialIndexFilter.java
  8. 30
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/TextIndexDefinition.java
  9. 108
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultIndexOperationsIntegrationTests.java

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

@ -1,5 +1,5 @@
/* /*
* Copyright 2011-2015 the original author or authors. * Copyright 2011-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -15,17 +15,15 @@
*/ */
package org.springframework.data.mongodb.core; package org.springframework.data.mongodb.core;
import static org.springframework.data.domain.Sort.Direction.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessException;
import org.springframework.data.mongodb.core.convert.QueryMapper;
import org.springframework.data.mongodb.core.index.IndexDefinition; import org.springframework.data.mongodb.core.index.IndexDefinition;
import org.springframework.data.mongodb.core.index.IndexField;
import org.springframework.data.mongodb.core.index.IndexInfo; import org.springframework.data.mongodb.core.index.IndexInfo;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import com.mongodb.DBCollection; import com.mongodb.DBCollection;
@ -42,12 +40,11 @@ import com.mongodb.MongoException;
*/ */
public class DefaultIndexOperations implements IndexOperations { public class DefaultIndexOperations implements IndexOperations {
private static final Double ONE = Double.valueOf(1); public static final String PARTIAL_FILTER_EXPRESSION_KEY = "partialFilterExpression";
private static final Double MINUS_ONE = Double.valueOf(-1);
private static final Collection<String> TWO_D_IDENTIFIERS = Arrays.asList("2d", "2dsphere");
private final MongoOperations mongoOperations; private final MongoOperations mongoOperations;
private final String collectionName; private final String collectionName;
private final QueryMapper mapper;
private final Class<?> type;
/** /**
* Creates a new {@link DefaultIndexOperations}. * Creates a new {@link DefaultIndexOperations}.
@ -56,12 +53,26 @@ public class DefaultIndexOperations implements IndexOperations {
* @param collectionName must not be {@literal null}. * @param collectionName must not be {@literal null}.
*/ */
public DefaultIndexOperations(MongoOperations mongoOperations, String collectionName) { public DefaultIndexOperations(MongoOperations mongoOperations, String collectionName) {
this(mongoOperations, collectionName, null);
}
/**
* Creates a new {@link DefaultIndexOperations}.
*
* @param mongoOperations must not be {@literal null}.
* @param collectionName 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(MongoOperations mongoOperations, String collectionName, Class<?> type) {
Assert.notNull(mongoOperations, "MongoOperations must not be null!"); Assert.notNull(mongoOperations, "MongoOperations must not be null!");
Assert.notNull(collectionName, "Collection name can not be null!"); Assert.notNull(collectionName, "Collection name can not be null!");
this.mongoOperations = mongoOperations; this.mongoOperations = mongoOperations;
this.collectionName = collectionName; this.collectionName = collectionName;
this.mapper = new QueryMapper(mongoOperations.getConverter());
this.type = type;
} }
/* /*
@ -69,9 +80,20 @@ public class DefaultIndexOperations implements IndexOperations {
* @see org.springframework.data.mongodb.core.IndexOperations#ensureIndex(org.springframework.data.mongodb.core.index.IndexDefinition) * @see org.springframework.data.mongodb.core.IndexOperations#ensureIndex(org.springframework.data.mongodb.core.index.IndexDefinition)
*/ */
public void ensureIndex(final IndexDefinition indexDefinition) { public void ensureIndex(final IndexDefinition indexDefinition) {
mongoOperations.execute(collectionName, new CollectionCallback<Object>() { mongoOperations.execute(collectionName, new CollectionCallback<Object>() {
public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException { public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException {
DBObject indexOptions = indexDefinition.getIndexOptions(); DBObject indexOptions = indexDefinition.getIndexOptions();
if (indexOptions != null && indexOptions.containsField(PARTIAL_FILTER_EXPRESSION_KEY)) {
Assert.isInstanceOf(DBObject.class, indexOptions.get(PARTIAL_FILTER_EXPRESSION_KEY));
indexOptions.put(PARTIAL_FILTER_EXPRESSION_KEY,
mapper.getMappedObject((DBObject) indexOptions.get(PARTIAL_FILTER_EXPRESSION_KEY),
lookupPersistentEntity(type, collectionName)));
}
if (indexOptions != null) { if (indexOptions != null) {
collection.createIndex(indexDefinition.getIndexKeys(), indexOptions); collection.createIndex(indexDefinition.getIndexKeys(), indexOptions);
} else { } else {
@ -79,6 +101,24 @@ public class DefaultIndexOperations implements IndexOperations {
} }
return null; return null;
} }
private MongoPersistentEntity<?> lookupPersistentEntity(Class<?> entityType, String collection) {
if (entityType != null) {
return mongoOperations.getConverter().getMappingContext().getPersistentEntity(entityType);
}
Collection<? extends MongoPersistentEntity<?>> entities = mongoOperations.getConverter().getMappingContext()
.getPersistentEntities();
for (MongoPersistentEntity<?> entity : entities) {
if (entity.getCollection().equals(collection)) {
return entity;
}
}
return null;
}
}); });
} }
@ -136,44 +176,7 @@ public class DefaultIndexOperations implements IndexOperations {
List<IndexInfo> indexInfoList = new ArrayList<IndexInfo>(); List<IndexInfo> indexInfoList = new ArrayList<IndexInfo>();
for (DBObject ix : dbObjectList) { for (DBObject ix : dbObjectList) {
indexInfoList.add(IndexInfo.indexInfoOf(ix));
DBObject keyDbObject = (DBObject) ix.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)) {
DBObject weights = (DBObject) 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.containsField("unique") ? (Boolean) ix.get("unique") : false;
boolean dropDuplicates = ix.containsField("dropDups") ? (Boolean) ix.get("dropDups") : false;
boolean sparse = ix.containsField("sparse") ? (Boolean) ix.get("sparse") : false;
String language = ix.containsField("default_language") ? (String) ix.get("default_language") : "";
indexInfoList.add(new IndexInfo(indexFields, name, unique, dropDuplicates, sparse, language));
} }
return indexInfoList; return indexInfoList;

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

@ -541,7 +541,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
} }
public IndexOperations indexOps(Class<?> entityClass) { public IndexOperations indexOps(Class<?> entityClass) {
return new DefaultIndexOperations(this, determineCollectionName(entityClass)); return new DefaultIndexOperations(this, determineCollectionName(entityClass), entityClass);
} }
public BulkOperations bulkOps(BulkMode bulkMode, String collectionName) { public BulkOperations bulkOps(BulkMode bulkMode, String collectionName) {

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

@ -1,5 +1,5 @@
/* /*
* Copyright 2010-2014 the original author or authors. * Copyright 2010-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -39,6 +39,7 @@ public class GeospatialIndex implements IndexDefinition {
private GeoSpatialIndexType type = GeoSpatialIndexType.GEO_2D; private GeoSpatialIndexType type = GeoSpatialIndexType.GEO_2D;
private Double bucketSize = 1.0; private Double bucketSize = 1.0;
private String additionalField; private String additionalField;
private IndexFilter filter;
/** /**
* Creates a new {@link GeospatialIndex} for the given field. * Creates a new {@link GeospatialIndex} for the given field.
@ -119,6 +120,22 @@ public class GeospatialIndex implements IndexDefinition {
return this; 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 DBObject getIndexKeys() { public DBObject getIndexKeys() {
DBObject dbo = new BasicDBObject(); DBObject dbo = new BasicDBObject();
@ -186,6 +203,10 @@ public class GeospatialIndex implements IndexDefinition {
break; break;
} }
if (filter != null) {
dbo.put("partialFilterExpression", filter.getFilterObject());
}
return dbo; return dbo;
} }

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

@ -1,5 +1,5 @@
/* /*
* Copyright 2010-2015 the original author or authors. * Copyright 2010-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -63,6 +63,8 @@ public class Index implements IndexDefinition {
private long expire = -1; private long expire = -1;
private IndexFilter filter;
public Index() {} public Index() {}
public Index(String key, Direction direction) { public Index(String key, Direction direction) {
@ -176,6 +178,21 @@ public class Index implements IndexDefinition {
return unique(); return unique();
} }
/**
* 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) * (non-Javadoc)
* @see org.springframework.data.mongodb.core.index.IndexDefinition#getIndexKeys() * @see org.springframework.data.mongodb.core.index.IndexDefinition#getIndexKeys()
@ -213,6 +230,9 @@ public class Index implements IndexDefinition {
dbo.put("expireAfterSeconds", expire); dbo.put("expireAfterSeconds", expire);
} }
if (filter != null) {
dbo.put("partialFilterExpression", filter.getFilterObject());
}
return dbo; return dbo;
} }

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

@ -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 com.mongodb.DBObject;
/**
* 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
*/
DBObject getFilterObject();
}

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

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -15,11 +15,15 @@
*/ */
package org.springframework.data.mongodb.core.index; package org.springframework.data.mongodb.core.index;
import static org.springframework.data.domain.Sort.Direction.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import com.mongodb.DBObject;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
@ -30,6 +34,10 @@ import org.springframework.util.ObjectUtils;
*/ */
public class IndexInfo { 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 List<IndexField> indexFields;
private final String name; private final String name;
@ -37,6 +45,7 @@ public class IndexInfo {
private final boolean dropDuplicates; private final boolean dropDuplicates;
private final boolean sparse; private final boolean sparse;
private final String language; private final String language;
private String partialFilterExpression;
/** /**
* @deprecated Will be removed in 1.7. Please use {@link #IndexInfo(List, String, boolean, boolean, boolean, String)} * @deprecated Will be removed in 1.7. Please use {@link #IndexInfo(List, String, boolean, boolean, boolean, String)}
@ -62,6 +71,61 @@ public class IndexInfo {
this.language = language; 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(DBObject sourceDocument) {
DBObject keyDbObject = (DBObject) 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)) {
DBObject weights = (DBObject) 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.containsField("unique") ? (Boolean) sourceDocument.get("unique") : false;
boolean dropDuplicates = sourceDocument.containsField("dropDups") ? (Boolean) sourceDocument.get("dropDups")
: false;
boolean sparse = sourceDocument.containsField("sparse") ? (Boolean) sourceDocument.get("sparse") : false;
String language = sourceDocument.containsField("default_language") ? (String) sourceDocument.get("default_language")
: "";
String partialFilter = sourceDocument.containsField("partialFilterExpression")
? sourceDocument.get("partialFilterExpression").toString() : "";
IndexInfo info = new IndexInfo(indexFields, name, unique, dropDuplicates, sparse, language);
info.partialFilterExpression = partialFilter;
return info;
}
/** /**
* Returns the individual index fields of the index. * Returns the individual index fields of the index.
* *
@ -113,10 +177,19 @@ public class IndexInfo {
return language; return language;
} }
/**
* @return
* @since 1.0
*/
public String getPartialFilterExpression() {
return partialFilterExpression;
}
@Override @Override
public String toString() { public String toString() {
return "IndexInfo [indexFields=" + indexFields + ", name=" + name + ", unique=" + unique + ", dropDuplicates=" return "IndexInfo [indexFields=" + indexFields + ", name=" + name + ", unique=" + unique + ", dropDuplicates="
+ dropDuplicates + ", sparse=" + sparse + ", language=" + language + "]"; + dropDuplicates + ", sparse=" + sparse + ", language=" + language + ", partialFilterExpression="
+ partialFilterExpression + "]";
} }
@Override @Override
@ -130,6 +203,7 @@ public class IndexInfo {
result = prime * result + (sparse ? 1231 : 1237); result = prime * result + (sparse ? 1231 : 1237);
result = prime * result + (unique ? 1231 : 1237); result = prime * result + (unique ? 1231 : 1237);
result = prime * result + ObjectUtils.nullSafeHashCode(language); result = prime * result + ObjectUtils.nullSafeHashCode(language);
result = prime * result + ObjectUtils.nullSafeHashCode(partialFilterExpression);
return result; return result;
} }
@ -171,6 +245,9 @@ public class IndexInfo {
if (!ObjectUtils.nullSafeEquals(language, other.language)) { if (!ObjectUtils.nullSafeEquals(language, other.language)) {
return false; return false;
} }
if (!ObjectUtils.nullSafeEquals(partialFilterExpression, other.partialFilterExpression)) {
return false;
}
return true; return true;
} }
} }

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

@ -0,0 +1,73 @@
/*
* 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.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(DBObject 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 DBObject getFilterObject() {
if (filterExpression instanceof DBObject) {
return (DBObject) filterExpression;
}
if (filterExpression instanceof CriteriaDefinition) {
return ((CriteriaDefinition) filterExpression).getCriteriaObject();
}
throw new IllegalArgumentException(
String.format("Unknown type %s used as filter expression.", filterExpression.getClass()));
}
}

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

@ -1,5 +1,5 @@
/* /*
* Copyright 2014 the original author or authors. * Copyright 2014-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -39,6 +39,7 @@ public class TextIndexDefinition implements IndexDefinition {
private Set<TextIndexedFieldSpec> fieldSpecs; private Set<TextIndexedFieldSpec> fieldSpecs;
private String defaultLanguage; private String defaultLanguage;
private String languageOverride; private String languageOverride;
private IndexFilter filter;
TextIndexDefinition() { TextIndexDefinition() {
fieldSpecs = new LinkedHashSet<TextIndexedFieldSpec>(); fieldSpecs = new LinkedHashSet<TextIndexedFieldSpec>();
@ -129,6 +130,10 @@ public class TextIndexDefinition implements IndexDefinition {
options.put("language_override", languageOverride); options.put("language_override", languageOverride);
} }
if (filter != null) {
options.put("partialFilterExpression", filter.getFilterObject());
}
return options; return options;
} }
@ -288,8 +293,8 @@ public class TextIndexDefinition implements IndexDefinition {
public TextIndexDefinitionBuilder onField(String fieldname, Float weight) { public TextIndexDefinitionBuilder onField(String fieldname, Float weight) {
if (this.instance.fieldSpecs.contains(ALL_FIELDS)) { if (this.instance.fieldSpecs.contains(ALL_FIELDS)) {
throw new InvalidDataAccessApiUsageException(String.format("Cannot add %s to field spec for all fields.", throw new InvalidDataAccessApiUsageException(
fieldname)); String.format("Cannot add %s to field spec for all fields.", fieldname));
} }
this.instance.fieldSpecs.add(new TextIndexedFieldSpec(fieldname, weight)); this.instance.fieldSpecs.add(new TextIndexedFieldSpec(fieldname, weight));
@ -318,8 +323,8 @@ public class TextIndexDefinition implements IndexDefinition {
public TextIndexDefinitionBuilder withLanguageOverride(String fieldname) { public TextIndexDefinitionBuilder withLanguageOverride(String fieldname) {
if (StringUtils.hasText(this.instance.languageOverride)) { if (StringUtils.hasText(this.instance.languageOverride)) {
throw new InvalidDataAccessApiUsageException(String.format( throw new InvalidDataAccessApiUsageException(
"Cannot set language override on %s as it is already defined on %s.", fieldname, String.format("Cannot set language override on %s as it is already defined on %s.", fieldname,
this.instance.languageOverride)); this.instance.languageOverride));
} }
@ -327,6 +332,21 @@ public class TextIndexDefinition implements IndexDefinition {
return this; 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() { public TextIndexDefinition build() {
return this.instance; return this.instance;
} }

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

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,18 +17,28 @@ package org.springframework.data.mongodb.core;
import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*; 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.ReflectiveDBCollectionInvoker.*;
import static org.springframework.data.mongodb.core.index.PartialIndexFilter.*;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort.Direction;
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.index.IndexInfo;
import org.springframework.data.mongodb.core.mapping.Document;
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.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import com.mongodb.BasicDBObject; import com.mongodb.BasicDBObject;
import com.mongodb.CommandResult;
import com.mongodb.DBCollection; import com.mongodb.DBCollection;
import com.mongodb.DBObject; import com.mongodb.DBObject;
@ -42,6 +52,9 @@ import com.mongodb.DBObject;
@ContextConfiguration("classpath:infrastructure.xml") @ContextConfiguration("classpath:infrastructure.xml")
public class DefaultIndexOperationsIntegrationTests { public class DefaultIndexOperationsIntegrationTests {
private static final Version THREE_DOT_TWO = new Version(3, 2);
private static Version mongoVersion;
static final DBObject GEO_SPHERE_2D = new BasicDBObject("loaction", "2dsphere"); static final DBObject GEO_SPHERE_2D = new BasicDBObject("loaction", "2dsphere");
@Autowired MongoTemplate template; @Autowired MongoTemplate template;
@ -51,6 +64,7 @@ public class DefaultIndexOperationsIntegrationTests {
@Before @Before
public void setUp() { public void setUp() {
queryMongoVersionIfNecessary();
String collectionName = this.template.getCollectionName(DefaultIndexOperationsIntegrationTestsSample.class); String collectionName = this.template.getCollectionName(DefaultIndexOperationsIntegrationTestsSample.class);
this.collection = this.template.getDb().getCollection(collectionName); this.collection = this.template.getDb().getCollection(collectionName);
@ -59,6 +73,14 @@ public class DefaultIndexOperationsIntegrationTests {
this.indexOps = new DefaultIndexOperations(template, collectionName); this.indexOps = new DefaultIndexOperations(template, collectionName);
} }
private void queryMongoVersionIfNecessary() {
if (mongoVersion == null) {
CommandResult result = template.executeCommand("{ buildInfo: 1 }");
mongoVersion = Version.parse(result.get("version").toString());
}
}
/** /**
* @see DATAMONGO-1008 * @see DATAMONGO-1008
*/ */
@ -71,6 +93,78 @@ public class DefaultIndexOperationsIntegrationTests {
assertThat(info.getIndexFields().get(0).isGeo(), is(true)); assertThat(info.getIndexFields().get(0).isGeo(), is(true));
} }
/**
* @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 BasicDBObject("qty", new BasicDBObject("$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,
this.template.getCollectionName(DefaultIndexOperationsIntegrationTestsSample.class),
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(DBObject keys) { private IndexInfo findAndReturnIndexInfo(DBObject keys) {
return findAndReturnIndexInfo(indexOps.getIndexInfo(), keys); return findAndReturnIndexInfo(indexOps.getIndexInfo(), keys);
} }
@ -89,5 +183,15 @@ public class DefaultIndexOperationsIntegrationTests {
throw new AssertionError(String.format("Index with %s was not found", name)); throw new AssertionError(String.format("Index with %s was not found", name));
} }
static class DefaultIndexOperationsIntegrationTestsSample {} @Document(collection = "default-index-operations-tests")
static class DefaultIndexOperationsIntegrationTestsSample {
@Field("qty") Integer quantity;
}
@Document(collection = "default-index-operations-tests")
static class MappingToSameCollection extends DefaultIndexOperationsIntegrationTestsSample {
@Field("a_g_e") Integer age;
}
} }

Loading…
Cancel
Save