Browse Source
Add WildcardIndexed annotation and the programatic WildcardIndex. Closes #3225 Original pull request: #3671.pull/3732/head
9 changed files with 654 additions and 19 deletions
@ -0,0 +1,198 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 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 |
||||||
|
* |
||||||
|
* https://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 java.time.Duration; |
||||||
|
import java.util.LinkedHashMap; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.concurrent.TimeUnit; |
||||||
|
|
||||||
|
import org.bson.Document; |
||||||
|
import org.springframework.lang.Nullable; |
||||||
|
import org.springframework.util.CollectionUtils; |
||||||
|
import org.springframework.util.StringUtils; |
||||||
|
|
||||||
|
/** |
||||||
|
* {@link WildcardIndex} is a specific {@link Index} that can be used to include all fields into an index based on the |
||||||
|
* {@code $**" : 1} pattern on a root object (the one typically carrying the |
||||||
|
* {@link org.springframework.data.mongodb.core.mapping.Document} annotation). On those it is possible to use |
||||||
|
* {@link #wildcardProjectionInclude(String...)} and {@link #wildcardProjectionExclude(String...)} to define specific |
||||||
|
* paths for in-/exclusion. |
||||||
|
* <p /> |
||||||
|
* It can also be used to define an index on a specific field path and its subfields, e.g. |
||||||
|
* {@code "path.to.field.$**" : 1}. <br /> |
||||||
|
* Note that {@literal wildcardProjections} are not allowed in this case. |
||||||
|
* <p /> |
||||||
|
* <strong>LIMITATIONS</strong><br /> |
||||||
|
* <ul> |
||||||
|
* <li>{@link #unique() Unique} and {@link #expire(long) ttl} options are not supported.</li> |
||||||
|
* <li>Keys used for sharding must not be included</li> |
||||||
|
* <li>Cannot be used to generate any type of geo index.</li> |
||||||
|
* </ul> |
||||||
|
* |
||||||
|
* @author Christoph Strobl |
||||||
|
* @see <a href= "https://docs.mongodb.com/manual/core/index-wildcard/">MongoDB Reference Documentation: Wildcard |
||||||
|
* Indexes/</a> |
||||||
|
* @since 3.3 |
||||||
|
*/ |
||||||
|
public class WildcardIndex extends Index { |
||||||
|
|
||||||
|
private @Nullable String fieldName; |
||||||
|
private Map<String, Object> wildcardProjection = new LinkedHashMap<>(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a new instance of {@link WildcardIndex} using {@code $**}. |
||||||
|
*/ |
||||||
|
public WildcardIndex() {} |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a new instance of {@link WildcardIndex} for the given {@literal path}. If no {@literal path} is provided the |
||||||
|
* index will be considered a root one using {@code $**}. <br /> |
||||||
|
* <strong>NOTE</strong> {@link #wildcardProjectionInclude(String...)}, {@link #wildcardProjectionExclude(String...)} |
||||||
|
* can only be used for top level index definitions having an {@literal empty} or {@literal null} path. |
||||||
|
* |
||||||
|
* @param path can be {@literal null}. If {@literal null} all fields will be indexed. |
||||||
|
*/ |
||||||
|
public WildcardIndex(@Nullable String path) { |
||||||
|
this.fieldName = path; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Include the {@code _id} field in {@literal wildcardProjection}. |
||||||
|
* |
||||||
|
* @return this. |
||||||
|
*/ |
||||||
|
public WildcardIndex includeId() { |
||||||
|
|
||||||
|
wildcardProjection.put("_id", 1); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Set the index name to use. |
||||||
|
* |
||||||
|
* @param name |
||||||
|
* @return this. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public WildcardIndex named(String name) { |
||||||
|
|
||||||
|
super.named(name); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Unique option is not supported. |
||||||
|
* |
||||||
|
* @throws UnsupportedOperationException |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public Index unique() { |
||||||
|
throw new UnsupportedOperationException("Wildcard Index does not support 'unique'."); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* ttl option is not supported. |
||||||
|
* |
||||||
|
* @throws UnsupportedOperationException |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public Index expire(long seconds) { |
||||||
|
throw new UnsupportedOperationException("Wildcard Index does not support 'ttl'."); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* ttl option is not supported. |
||||||
|
* |
||||||
|
* @throws UnsupportedOperationException |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public Index expire(long value, TimeUnit timeUnit) { |
||||||
|
throw new UnsupportedOperationException("Wildcard Index does not support 'ttl'."); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* ttl option is not supported. |
||||||
|
* |
||||||
|
* @throws UnsupportedOperationException |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public Index expire(Duration duration) { |
||||||
|
throw new UnsupportedOperationException("Wildcard Index does not support 'ttl'."); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add fields to be included from indexing via {@code wildcardProjection}. <br /> |
||||||
|
* This option is only allowed on {@link WildcardIndex#WildcardIndex() top level} wildcard indexes. |
||||||
|
* |
||||||
|
* @param paths must not be {@literal null}. |
||||||
|
* @return this. |
||||||
|
*/ |
||||||
|
public WildcardIndex wildcardProjectionInclude(String... paths) { |
||||||
|
|
||||||
|
for (String path : paths) { |
||||||
|
wildcardProjection.put(path, 1); |
||||||
|
} |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add fields to be excluded from indexing via {@code wildcardProjection}. <br /> |
||||||
|
* This option is only allowed on {@link WildcardIndex#WildcardIndex() top level} wildcard indexes. |
||||||
|
* |
||||||
|
* @param paths must not be {@literal null}. |
||||||
|
* @return this. |
||||||
|
*/ |
||||||
|
public WildcardIndex wildcardProjectionExclude(String... paths) { |
||||||
|
|
||||||
|
for (String path : paths) { |
||||||
|
wildcardProjection.put(path, 0); |
||||||
|
} |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Set the fields to be in-/excluded from indexing via {@code wildcardProjection}. <br /> |
||||||
|
* This option is only allowed on {@link WildcardIndex#WildcardIndex() top level} wildcard indexes. |
||||||
|
* |
||||||
|
* @param includeExclude must not be {@literal null}. |
||||||
|
* @return this. |
||||||
|
*/ |
||||||
|
public WildcardIndex wildcardProjection(Map<String, Object> includeExclude) { |
||||||
|
|
||||||
|
wildcardProjection.putAll(includeExclude); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
private String getTargetFieldName() { |
||||||
|
return StringUtils.hasText(fieldName) ? (fieldName + ".$**") : "$**"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Document getIndexKeys() { |
||||||
|
return new Document(getTargetFieldName(), 1); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Document getIndexOptions() { |
||||||
|
|
||||||
|
Document options = new Document(super.getIndexOptions()); |
||||||
|
if (!CollectionUtils.isEmpty(wildcardProjection)) { |
||||||
|
options.put("wildcardProjection", new Document(wildcardProjection)); |
||||||
|
} |
||||||
|
return options; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,130 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 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 |
||||||
|
* |
||||||
|
* https://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 java.lang.annotation.Documented; |
||||||
|
import java.lang.annotation.ElementType; |
||||||
|
import java.lang.annotation.Retention; |
||||||
|
import java.lang.annotation.RetentionPolicy; |
||||||
|
import java.lang.annotation.Target; |
||||||
|
|
||||||
|
/** |
||||||
|
* Annotation for an entity or property that should be used as key for a |
||||||
|
* <a href="https://docs.mongodb.com/manual/core/index-wildcard/">Wildcard Index</a>. <br /> |
||||||
|
* If placed on a {@link ElementType#TYPE type} that is a root level domain entity (one having an |
||||||
|
* {@link org.springframework.data.mongodb.core.mapping.Document} annotation) will advise the index creator to create a |
||||||
|
* wildcard index for it. |
||||||
|
* |
||||||
|
* <pre class="code"> |
||||||
|
* |
||||||
|
* @Document |
||||||
|
* @WildcardIndexed |
||||||
|
* public class Product { |
||||||
|
* ... |
||||||
|
* } |
||||||
|
* |
||||||
|
* db.product.createIndex({ "$**" : 1 } , {}) |
||||||
|
* </pre> |
||||||
|
* |
||||||
|
* {@literal wildcardProjection} can be used to specify keys to in-/exclude in the index. |
||||||
|
* |
||||||
|
* <pre class="code"> |
||||||
|
* |
||||||
|
* @Document |
||||||
|
* @WildcardIndexed(wildcardProjection = "{ 'userMetadata.age' : 0 }") |
||||||
|
* public class User { |
||||||
|
* private @Id String id; |
||||||
|
* private UserMetadata userMetadata; |
||||||
|
* } |
||||||
|
* |
||||||
|
* |
||||||
|
* db.user.createIndex( |
||||||
|
* { "$**" : 1 }, |
||||||
|
* { "wildcardProjection" : |
||||||
|
* { "userMetadata.age" : 0 } |
||||||
|
* } |
||||||
|
* ) |
||||||
|
* </pre> |
||||||
|
* |
||||||
|
* Wildcard indexes can also be expressed by adding the annotation directly to the field. Please note that |
||||||
|
* {@literal wildcardProjection} is not allowed on nested paths. |
||||||
|
* |
||||||
|
* <pre class="code"> |
||||||
|
* @Document |
||||||
|
* public class User { |
||||||
|
* |
||||||
|
* private @Id String id; |
||||||
|
* |
||||||
|
* @WildcardIndexed |
||||||
|
* private UserMetadata userMetadata; |
||||||
|
* } |
||||||
|
* |
||||||
|
* |
||||||
|
* db.user.createIndex({ "userMetadata.$**" : 1 }, {}) |
||||||
|
* </pre> |
||||||
|
* |
||||||
|
* @author Christoph Strobl |
||||||
|
* @since 3.3 |
||||||
|
*/ |
||||||
|
@Documented |
||||||
|
@Target({ ElementType.TYPE, ElementType.FIELD }) |
||||||
|
@Retention(RetentionPolicy.RUNTIME) |
||||||
|
public @interface WildcardIndexed { |
||||||
|
|
||||||
|
/** |
||||||
|
* Index name either as plain value or as {@link org.springframework.expression.spel.standard.SpelExpression template |
||||||
|
* expression}. <br /> |
||||||
|
* <br /> |
||||||
|
* The name will only be applied as is when defined on root level. For usage on nested or embedded structures the |
||||||
|
* provided name will be prefixed with the path leading to the entity. <br /> |
||||||
|
* |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
String name() default ""; |
||||||
|
|
||||||
|
/** |
||||||
|
* If set to {@literal true} then MongoDB will ignore the given index name and instead generate a new name. Defaults |
||||||
|
* to {@literal false}. |
||||||
|
* |
||||||
|
* @return {@literal false} by default. |
||||||
|
*/ |
||||||
|
boolean useGeneratedName() default false; |
||||||
|
|
||||||
|
/** |
||||||
|
* Only index the documents in a collection that meet a specified {@link IndexFilter filter expression}. <br /> |
||||||
|
* |
||||||
|
* @return empty by default. |
||||||
|
* @see <a href= |
||||||
|
* "https://docs.mongodb.com/manual/core/index-partial/">https://docs.mongodb.com/manual/core/index-partial/</a>
|
||||||
|
*/ |
||||||
|
String partialFilter() default ""; |
||||||
|
|
||||||
|
/** |
||||||
|
* Explicitly specify sub fields to be in-/excluded as a {@link org.bson.Document#parse(String) prasable} String. |
||||||
|
* <br /> |
||||||
|
* <strong>NOTE: </strong>Can only be done on root level documents. |
||||||
|
* |
||||||
|
* @return empty by default. |
||||||
|
*/ |
||||||
|
String wildcardProjection() default ""; |
||||||
|
|
||||||
|
/** |
||||||
|
* Defines the collation to apply. |
||||||
|
* |
||||||
|
* @return an empty {@link String} by default. |
||||||
|
*/ |
||||||
|
String collation() default ""; |
||||||
|
} |
||||||
Loading…
Reference in new issue