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 @@
@@ -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 @@
@@ -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