Skip to content

Commit

Permalink
Add support for Wildcard Index.
Browse files Browse the repository at this point in the history
Add WildcardIndexed annotation and the programatic WildcardIndex.

Closes #3225
Original pull request: #3671.
  • Loading branch information
christophstrobl authored and mp911de committed Jul 14, 2021
1 parent 986ea39 commit d57c5a9
Show file tree
Hide file tree
Showing 9 changed files with 654 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ private static Converter<IndexDefinition, IndexOptions> getIndexDefinitionIndexO
ops = ops.collation(fromDocument(indexOptions.get("collation", Document.class)));
}

if(indexOptions.containsKey("wildcardProjection")) {
ops.wildcardProjection(indexOptions.get("wildcardProjection", Document.class));
}

return ops;
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
public final class IndexField {

enum Type {
GEO, TEXT, DEFAULT, HASH;
GEO, TEXT, DEFAULT, HASH, WILDCARD;
}

private final String key;
Expand All @@ -48,7 +48,7 @@ private IndexField(String key, @Nullable Direction direction, @Nullable Type typ
if (Type.GEO.equals(type) || Type.TEXT.equals(type)) {
Assert.isNull(direction, "Geo/Text indexes must not have a direction!");
} else {
if (!Type.HASH.equals(type)) {
if (!(Type.HASH.equals(type) || Type.WILDCARD.equals(type))) {
Assert.notNull(direction, "Default indexes require a direction");
}
}
Expand Down Expand Up @@ -77,6 +77,17 @@ static IndexField hashed(String key) {
return new IndexField(key, null, Type.HASH);
}

/**
* Creates a {@literal wildcard} {@link IndexField} for the given key.
*
* @param key must not be {@literal null} or empty.
* @return new instance of {@link IndexField}.
* @since 3.3
*/
static IndexField wildcard(String key) {
return new IndexField(key, null, Type.WILDCARD);
}

/**
* Creates a geo {@link IndexField} for the given key.
*
Expand Down Expand Up @@ -142,6 +153,16 @@ public boolean isHashed() {
return Type.HASH.equals(type);
}

/**
* Returns whether the {@link IndexField} is contains a {@literal wildcard} expression.
*
* @return {@literal true} if {@link IndexField} contains a wildcard {@literal $**}.
* @since 3.3
*/
public boolean isWildcard() {
return Type.WILDCARD.equals(type);
}

/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public class IndexInfo {
private @Nullable Duration expireAfter;
private @Nullable String partialFilterExpression;
private @Nullable Document collation;
private @Nullable Document wildcardProjection;

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

Expand Down Expand Up @@ -99,6 +100,8 @@ public static IndexInfo indexInfoOf(Document sourceDocument) {

if (ObjectUtils.nullSafeEquals("hashed", value)) {
indexFields.add(IndexField.hashed(key));
} else if (key.contains("$**")) {
indexFields.add(IndexField.wildcard(key));
} else {

Double keyValue = new Double(value.toString());
Expand Down Expand Up @@ -131,6 +134,10 @@ public static IndexInfo indexInfoOf(Document sourceDocument) {
info.expireAfter = Duration.ofSeconds(NumberUtils.convertNumberToTargetClass(expireAfterSeconds, Long.class));
}

if (sourceDocument.containsKey("wildcardProjection")) {
info.wildcardProjection = sourceDocument.get("wildcardProjection", Document.class);
}

return info;
}

Expand Down Expand Up @@ -216,6 +223,16 @@ public Optional<Document> getCollation() {
return Optional.ofNullable(collation);
}

/**
* Get {@literal wildcardProjection} information.
*
* @return {@link Optional#empty() empty} if not set.
* @since 3.3
*/
public Optional<Document> getWildcardProjection() {
return Optional.ofNullable(wildcardProjection);
}

/**
* Get the duration after which documents within the index expire.
*
Expand All @@ -234,6 +251,14 @@ public boolean isHashed() {
return getIndexFields().stream().anyMatch(IndexField::isHashed);
}

/**
* @return {@literal true} if a wildcard index field is present.
* @since 3.3
*/
public boolean isWildcard() {
return getIndexFields().stream().anyMatch(IndexField::isWildcard);
}

@Override
public String toString() {

Expand Down Expand Up @@ -303,4 +328,5 @@ public boolean equals(Object obj) {
}
return true;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.query.Collation;
import org.springframework.data.mongodb.util.BsonUtils;
import org.springframework.data.mongodb.util.DotPath;
import org.springframework.data.spel.EvaluationContextProvider;
Expand Down Expand Up @@ -121,6 +122,7 @@ public List<IndexDefinitionHolder> resolveIndexForEntity(MongoPersistentEntity<?
List<IndexDefinitionHolder> indexInformation = new ArrayList<>();
String collection = root.getCollection();
indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions("", collection, root));
indexInformation.addAll(potentiallyCreateWildcardIndexDefinitions("", collection, root));
indexInformation.addAll(potentiallyCreateTextIndexDefinition(root, collection));

root.doWithProperties((PropertyHandler<MongoPersistentProperty>) property -> this
Expand Down Expand Up @@ -162,17 +164,18 @@ private void potentiallyAddIndexForProperty(MongoPersistentEntity<?> root, Mongo
* @return List of {@link IndexDefinitionHolder} representing indexes for given type and its referenced property
* types. Will never be {@code null}.
*/
private List<IndexDefinitionHolder> resolveIndexForClass( TypeInformation<?> type, String dotPath,
Path path, String collection, CycleGuard guard) {
private List<IndexDefinitionHolder> resolveIndexForClass(TypeInformation<?> type, String dotPath, Path path,
String collection, CycleGuard guard) {

return resolveIndexForEntity(mappingContext.getRequiredPersistentEntity(type), dotPath, path, collection, guard);
}

private List<IndexDefinitionHolder> resolveIndexForEntity(MongoPersistentEntity<?> entity, String dotPath,
Path path, String collection, CycleGuard guard) {
private List<IndexDefinitionHolder> resolveIndexForEntity(MongoPersistentEntity<?> entity, String dotPath, Path path,
String collection, CycleGuard guard) {

List<IndexDefinitionHolder> indexInformation = new ArrayList<>();
indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions(dotPath, collection, entity));
indexInformation.addAll(potentiallyCreateWildcardIndexDefinitions(dotPath, collection, entity));

entity.doWithProperties((PropertyHandler<MongoPersistentProperty>) property -> this
.guardAndPotentiallyAddIndexForProperty(property, dotPath, path, collection, indexInformation, guard));
Expand All @@ -196,15 +199,15 @@ private void guardAndPotentiallyAddIndexForProperty(MongoPersistentProperty pers

if (persistentProperty.isEntity()) {
try {
indexes.addAll(resolveIndexForEntity(mappingContext.getPersistentEntity(persistentProperty), propertyDotPath.toString(),
propertyPath, collection, guard));
indexes.addAll(resolveIndexForEntity(mappingContext.getPersistentEntity(persistentProperty),
propertyDotPath.toString(), propertyPath, collection, guard));
} catch (CyclicPropertyReferenceException e) {
LOGGER.info(e.getMessage());
}
}

List<IndexDefinitionHolder> indexDefinitions = createIndexDefinitionHolderForProperty(propertyDotPath.toString(), collection,
persistentProperty);
List<IndexDefinitionHolder> indexDefinitions = createIndexDefinitionHolderForProperty(propertyDotPath.toString(),
collection, persistentProperty);

if (!indexDefinitions.isEmpty()) {
indexes.addAll(indexDefinitions);
Expand Down Expand Up @@ -232,6 +235,11 @@ private List<IndexDefinitionHolder> createIndexDefinitionHolderForProperty(Strin
if (persistentProperty.isAnnotationPresent(HashIndexed.class)) {
indices.add(createHashedIndexDefinition(dotPath, collection, persistentProperty));
}
if (persistentProperty.isAnnotationPresent(WildcardIndexed.class)) {
indices.add(createWildcardIndexDefinition(dotPath, collection,
persistentProperty.getRequiredAnnotation(WildcardIndexed.class),
mappingContext.getPersistentEntity(persistentProperty)));
}

return indices;
}
Expand All @@ -246,6 +254,18 @@ private List<IndexDefinitionHolder> potentiallyCreateCompoundIndexDefinitions(St
return createCompoundIndexDefinitions(dotPath, collection, entity);
}

private List<IndexDefinitionHolder> potentiallyCreateWildcardIndexDefinitions(String dotPath, String collection,
MongoPersistentEntity<?> entity) {

if (entity.findAnnotation(WildcardIndexed.class) == null) {
return Collections.emptyList();
}

return Collections.singletonList(new IndexDefinitionHolder(dotPath,
createWildcardIndexDefinition(dotPath, collection, entity.getRequiredAnnotation(WildcardIndexed.class), entity),
collection));
}

private Collection<? extends IndexDefinitionHolder> potentiallyCreateTextIndexDefinition(
MongoPersistentEntity<?> root, String collection) {

Expand Down Expand Up @@ -292,9 +312,8 @@ private Collection<? extends IndexDefinitionHolder> potentiallyCreateTextIndexDe

}

private void appendTextIndexInformation(DotPath dotPath, Path path,
TextIndexDefinitionBuilder indexDefinitionBuilder, MongoPersistentEntity<?> entity,
TextIndexIncludeOptions includeOptions, CycleGuard guard) {
private void appendTextIndexInformation(DotPath dotPath, Path path, TextIndexDefinitionBuilder indexDefinitionBuilder,
MongoPersistentEntity<?> entity, TextIndexIncludeOptions includeOptions, CycleGuard guard) {

entity.doWithProperties(new PropertyHandler<MongoPersistentProperty>() {

Expand All @@ -311,8 +330,7 @@ public void doWithPersistentProperty(MongoPersistentProperty persistentProperty)

if (includeOptions.isForce() || indexed != null || persistentProperty.isEntity()) {

DotPath propertyDotPath = dotPath
.append(persistentProperty.getFieldName());
DotPath propertyDotPath = dotPath.append(persistentProperty.getFieldName());

Path propertyPath = path.append(persistentProperty);

Expand Down Expand Up @@ -406,6 +424,32 @@ protected IndexDefinitionHolder createCompoundIndexDefinition(String dotPath, St
return new IndexDefinitionHolder(dotPath, indexDefinition, collection);
}

protected IndexDefinitionHolder createWildcardIndexDefinition(String dotPath, String collection,
WildcardIndexed index, @Nullable MongoPersistentEntity<?> entity) {

WildcardIndex indexDefinition = new WildcardIndex(dotPath);

if (StringUtils.hasText(index.wildcardProjection())) {
indexDefinition.wildcardProjection(evaluateWildcardProjection(index.wildcardProjection(), entity));
}

if (!index.useGeneratedName()) {
indexDefinition.named(pathAwareIndexName(index.name(), dotPath, entity, null));
}

if (StringUtils.hasText(index.partialFilter())) {
indexDefinition.partial(evaluatePartialFilter(index.partialFilter(), entity));
}

if (StringUtils.hasText(index.collation())) {
indexDefinition.collation(evaluateCollation(index.collation(), entity));
} else if (entity != null && entity.hasCollation()) {
indexDefinition.collation(entity.getCollation());
}

return new IndexDefinitionHolder(dotPath, indexDefinition, collection);
}

private org.bson.Document resolveCompoundIndexKeyFromStringDefinition(String dotPath, String keyDefinitionString,
PersistentEntity<?, ?> entity) {

Expand Down Expand Up @@ -510,6 +554,33 @@ private PartialIndexFilter evaluatePartialFilter(String filterExpression, Persis
return PartialIndexFilter.of(BsonUtils.parse(filterExpression, null));
}

private org.bson.Document evaluateWildcardProjection(String projectionExpression, PersistentEntity<?, ?> entity) {

Object result = evaluate(projectionExpression, getEvaluationContextForProperty(entity));

if (result instanceof org.bson.Document) {
return (org.bson.Document) result;
}

return BsonUtils.parse(projectionExpression, null);
}

private Collation evaluateCollation(String collationExpression, PersistentEntity<?, ?> entity) {

Object result = evaluate(collationExpression, getEvaluationContextForProperty(entity));
if (result instanceof org.bson.Document) {
return Collation.from((org.bson.Document) result);
}
if (result instanceof Collation) {
return (Collation) result;
}
if (result instanceof String) {
return Collation.parse(result.toString());
}
throw new IllegalStateException("Cannot parse collation " + result);

}

/**
* Creates {@link HashedIndex} wrapped in {@link IndexDefinitionHolder} out of {@link HashIndexed} for a given
* {@link MongoPersistentProperty}.
Expand Down Expand Up @@ -657,8 +728,8 @@ private void resolveAndAddIndexesForAssociation(Association<MongoPersistentPrope
propertyDotPath));
}

List<IndexDefinitionHolder> indexDefinitions = createIndexDefinitionHolderForProperty(propertyDotPath.toString(), collection,
property);
List<IndexDefinitionHolder> indexDefinitions = createIndexDefinitionHolderForProperty(propertyDotPath.toString(),
collection, property);

if (!indexDefinitions.isEmpty()) {
indexes.addAll(indexDefinitions);
Expand Down Expand Up @@ -998,6 +1069,11 @@ public org.bson.Document getIndexKeys() {
public org.bson.Document getIndexOptions() {
return indexDefinition.getIndexOptions();
}

@Override
public String toString() {
return "IndexDefinitionHolder{" + "indexKeys=" + getIndexKeys() + '}';
}
}

/**
Expand Down
Loading

0 comments on commit d57c5a9

Please # to comment.