Skip to content

DATAMONGO-1467 - Support partial filter expressions in indexing. #380

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,9 @@ private List<IndexInfo> getIndexData(List<DBObject> 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -31,6 +32,7 @@
/**
* @author Oliver Gierke
* @author Christoph Strobl
* @author Christian Schneider
*/
@SuppressWarnings("deprecation")
public class Index implements IndexDefinition {
Expand Down Expand Up @@ -63,6 +65,8 @@ public enum Duplicates {

private long expire = -1;

private DBObject partialFilter;

public Index() {}

public Index(String key, Direction direction) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -212,6 +229,9 @@ public DBObject getIndexOptions() {
if (expire >= 0) {
dbo.put("expireAfterSeconds", expire);
}
if (partialFilter != null) {
dbo.put("partialFilterExpression", partialFilter);
}

return dbo;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
* @author Mark Pollack
* @author Oliver Gierke
* @author Christoph Strobl
* @author Christian Schneider
*/
public class IndexInfo {

Expand All @@ -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)}
Expand All @@ -51,15 +53,31 @@ public IndexInfo(List<IndexField> 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<IndexField> indexFields, String name, boolean unique, boolean dropDuplicates, boolean sparse,
String language) {
this(indexFields, name, unique, dropDuplicates, sparse, "", "");
}

public IndexInfo(List<IndexField> indexFields, String name, boolean unique, boolean dropDuplicates, boolean sparse,
String language, String partialFilter) {

this.indexFields = Collections.unmodifiableList(indexFields);
this.name = name;
this.unique = unique;
this.dropDuplicates = dropDuplicates;
this.sparse = sparse;
this.language = language;
this.partialFilter = partialFilter;
}

/**
Expand Down Expand Up @@ -105,6 +123,10 @@ public boolean isSparse() {
return sparse;
}

public String getPartialFilter() {
return partialFilter;
}

/**
* @return
* @since 1.6
Expand All @@ -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
Expand All @@ -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;
}

Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 "";
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -56,6 +57,7 @@
* @author Johno Crawford
* @author Christoph Strobl
* @author Thomas Darimont
* @author Christian Schneider
*/
@RunWith(MockitoJUnitRunner.class)
public class MongoPersistentEntityIndexCreatorUnitTests {
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
*
* @author Oliver Gierke
* @author Laurent Canet
* @author Christian Schneider
*/
public class IndexUnitTests {

Expand Down Expand Up @@ -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);
Expand Down