diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java index a1f2c96725..320d494ebe 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java @@ -173,7 +173,9 @@ private List getIndexData(List dbObjectList) { 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)); + String partialFilter = ix.containsField("partialFilterExpression") + ? ix.get("partialFilterExpression").toString() : ""; + indexInfoList.add(new IndexInfo(indexFields, name, unique, dropDuplicates, sparse, language, partialFilter)); } return indexInfoList; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Index.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Index.java index dbf59f6e2b..5804c2fdd6 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Index.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Index.java @@ -20,6 +20,7 @@ import java.util.Map.Entry; import java.util.concurrent.TimeUnit; +import com.mongodb.util.JSON; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.mongodb.core.query.Order; import org.springframework.util.Assert; @@ -31,6 +32,7 @@ /** * @author Oliver Gierke * @author Christoph Strobl + * @author Christian Schneider */ @SuppressWarnings("deprecation") public class Index implements IndexDefinition { @@ -63,6 +65,8 @@ public enum Duplicates { private long expire = -1; + private DBObject partialFilter; + public Index() {} public Index(String key, Direction direction) { @@ -164,6 +168,19 @@ public Index expire(long value, TimeUnit unit) { return this; } + /** + * @see http://docs.mongodb.com/manual/core/index-partial/ + * @return + */ + public Index partialFilter(String partialFilter) { + if(StringUtils.hasText(partialFilter)) { + this.partialFilter = (DBObject) JSON.parse(partialFilter); + } else { + this.partialFilter = null; + } + return this; + } + /** * @see http://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping * @param duplicates @@ -212,6 +229,9 @@ public DBObject getIndexOptions() { if (expire >= 0) { dbo.put("expireAfterSeconds", expire); } + if (partialFilter != null) { + dbo.put("partialFilterExpression", partialFilter); + } return dbo; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java index 2073fc1c28..f0dd043d69 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java @@ -27,6 +27,7 @@ * @author Mark Pollack * @author Oliver Gierke * @author Christoph Strobl + * @author Christian Schneider */ public class IndexInfo { @@ -37,6 +38,7 @@ public class IndexInfo { private final boolean dropDuplicates; private final boolean sparse; private final String language; + private final String partialFilter; /** * @deprecated Will be removed in 1.7. Please use {@link #IndexInfo(List, String, boolean, boolean, boolean, String)} @@ -51,8 +53,23 @@ public IndexInfo(List indexFields, String name, boolean unique, bool this(indexFields, name, unique, dropDuplicates, sparse, ""); } + /** + * @deprecated Will be removed in 1.7. Please use {@link #IndexInfo(List, String, boolean, boolean, boolean, String)} + * @param indexFields + * @param name + * @param unique + * @param dropDuplicates + * @param sparse + * @param language + */ + @Deprecated public IndexInfo(List indexFields, String name, boolean unique, boolean dropDuplicates, boolean sparse, String language) { + this(indexFields, name, unique, dropDuplicates, sparse, "", ""); + } + + public IndexInfo(List indexFields, String name, boolean unique, boolean dropDuplicates, boolean sparse, + String language, String partialFilter) { this.indexFields = Collections.unmodifiableList(indexFields); this.name = name; @@ -60,6 +77,7 @@ public IndexInfo(List indexFields, String name, boolean unique, bool this.dropDuplicates = dropDuplicates; this.sparse = sparse; this.language = language; + this.partialFilter = partialFilter; } /** @@ -105,6 +123,10 @@ public boolean isSparse() { return sparse; } + public String getPartialFilter() { + return partialFilter; + } + /** * @return * @since 1.6 @@ -116,7 +138,7 @@ public String getLanguage() { @Override public String toString() { return "IndexInfo [indexFields=" + indexFields + ", name=" + name + ", unique=" + unique + ", dropDuplicates=" - + dropDuplicates + ", sparse=" + sparse + ", language=" + language + "]"; + + dropDuplicates + ", sparse=" + sparse + ", language=" + language + ", partialFilter=" + partialFilter + "]"; } @Override @@ -130,6 +152,7 @@ public int hashCode() { result = prime * result + (sparse ? 1231 : 1237); result = prime * result + (unique ? 1231 : 1237); result = prime * result + ObjectUtils.nullSafeHashCode(language); + result = prime * result + ((partialFilter == null) ? 0 : partialFilter.hashCode()); return result; } @@ -168,6 +191,13 @@ public boolean equals(Object obj) { if (unique != other.unique) { return false; } + if(partialFilter == null) { + if(other.partialFilter != null) { + return false; + } + } else if (!partialFilter.equals(other.partialFilter)) { + return false; + } if (!ObjectUtils.nullSafeEquals(language, other.language)) { return false; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Indexed.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Indexed.java index a322622426..49d9bfefe6 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Indexed.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Indexed.java @@ -30,6 +30,7 @@ * @author Thomas Darimont * @author Christoph Strobl * @author Jordi Llach + * @author Christian Schneider */ @Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @@ -134,4 +135,12 @@ * @return */ int expireAfterSeconds() default -1; + + /** + * Takes a MongoDB JSON string as filter. If set the index will be applied to documents matching the filter only. + * + * @see https://docs.mongodb.com/manual/core/index-partial/ + * @return + */ + String partialFilter() default ""; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolver.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolver.java index 074872ba5b..6c74082250 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolver.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolver.java @@ -396,6 +396,10 @@ protected IndexDefinitionHolder createIndexDefinition(String dotPath, String fal indexDefinition.expire(index.expireAfterSeconds(), TimeUnit.SECONDS); } + if (index.partialFilter() != null) { + indexDefinition.partialFilter(index.partialFilter()); + } + return new IndexDefinitionHolder(dotPath, indexDefinition, collection); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java index ebb99b174d..d9a770c1ab 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java @@ -38,6 +38,7 @@ import java.util.Map; import org.bson.types.ObjectId; +import org.hamcrest.core.IsNull; import org.joda.time.DateTime; import org.junit.After; import org.junit.Before; @@ -420,6 +421,23 @@ public void testReadIndexInfoForIndicesCreatedViaMongoShellCommands() throws Exc assertThat(field, is(IndexField.create("age", Direction.DESC))); } + /** + * @see DATAMONGO-1467 + */ + @Test + public void testReadIndexInfoHavingAPartialFilterExpression() throws Exception { + + String command = "db." + template.getCollectionName(Person.class) + + ".createIndex({'age':-1}, {'partialFilterExpression': { 'age' : { '$exists' : true}}})"; + template.indexOps(Person.class).dropAllIndexes(); + + assertThat(template.indexOps(Person.class).getIndexInfo().isEmpty(), is(true)); + factory.getDb().eval(command); + + IndexInfo info = template.indexOps(Person.class).getIndexInfo().get(1); + assertThat(info.getPartialFilter(), is("{ \"age\" : { \"$exists\" : true}}")); + } + @Test public void testProperHandlingOfDifferentIdTypesWithMappingMongoConverter() throws Exception { testProperHandlingOfDifferentIdTypes(this.mappingTemplate); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorUnitTests.java index 093286aab7..31e1001280 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorUnitTests.java @@ -22,6 +22,7 @@ import java.util.Collections; import java.util.Date; +import com.mongodb.util.JSON; import org.hamcrest.core.IsEqual; import org.junit.Before; import org.junit.Test; @@ -56,6 +57,7 @@ * @author Johno Crawford * @author Christoph Strobl * @author Thomas Darimont + * @author Christian Schneider */ @RunWith(MockitoJUnitRunner.class) public class MongoPersistentEntityIndexCreatorUnitTests { @@ -185,6 +187,19 @@ public void autoGeneratedIndexNameShouldGenerateNoName() { assertThat(optionsCaptor.getValue(), is(new BasicDBObjectBuilder().get())); } + /** + * @see DATAMONGO-1467 + */ + @Test + public void indexCreationShouldUsePartialFilterExpression() { + + MongoMappingContext mappingContext = prepareMappingContext(EntityWithPartialFilterIndex.class); + new MongoPersistentEntityIndexCreator(mappingContext, factory); + + assertThat(keysCaptor.getValue().keySet(), hasItem("lastname")); + assertThat(optionsCaptor.getValue().get("partialFilterExpression"), is(JSON.parse("{ \"lastname\" : { \"$exists\" : true } }"))); + } + /** * @see DATAMONGO-367 */ @@ -313,4 +328,10 @@ class EntityWithGeneratedIndexName { @Indexed(useGeneratedName = true, name = "ignored") String lastname; } + + @Document + class EntityWithPartialFilterIndex { + + @Indexed(unique = true, name = "uniqueLastname", partialFilter = "{ \"lastname\" : { \"$exists\" : true } }") String lastname; + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/IndexUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/IndexUnitTests.java index 97384eb541..eaf06593e9 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/IndexUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/IndexUnitTests.java @@ -30,6 +30,7 @@ * * @author Oliver Gierke * @author Laurent Canet + * @author Christian Schneider */ public class IndexUnitTests { @@ -69,6 +70,17 @@ public void testWithSparse() { assertEquals("{ \"unique\" : true , \"sparse\" : true}", i.getIndexOptions().toString()); } + /** + * @see DATAMONGO-1467 + */ + @Test + public void testWithPartialFilter() { + Index i = new Index().on("name", Direction.ASC); + i.partialFilter("{ \"name\" : { \"$exists\" : true } }").unique(); + assertEquals("{ \"name\" : 1}", i.getIndexKeys().toString()); + assertEquals("{ \"unique\" : true , \"partialFilterExpression\" : { \"name\" : { \"$exists\" : true}}}", i.getIndexOptions().toString()); + } + @Test public void testGeospatialIndex() { GeospatialIndex i = new GeospatialIndex("location").withMin(0);