Enable index modification via IndexOperations.
Introduce IndexOptions that can be used to alter an existing index via IndexOperations. See: #4348
This commit is contained in:
@@ -16,12 +16,13 @@
|
||||
package org.springframework.data.mongodb;
|
||||
|
||||
import org.springframework.dao.UncategorizedDataAccessException;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
public class UncategorizedMongoDbException extends UncategorizedDataAccessException {
|
||||
|
||||
private static final long serialVersionUID = -2336595514062364929L;
|
||||
|
||||
public UncategorizedMongoDbException(String msg, Throwable cause) {
|
||||
public UncategorizedMongoDbException(String msg, @Nullable Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.List;
|
||||
import org.bson.Document;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.data.mongodb.MongoDatabaseFactory;
|
||||
import org.springframework.data.mongodb.UncategorizedMongoDbException;
|
||||
import org.springframework.data.mongodb.core.convert.QueryMapper;
|
||||
import org.springframework.data.mongodb.core.index.IndexDefinition;
|
||||
import org.springframework.data.mongodb.core.index.IndexInfo;
|
||||
@@ -29,6 +30,7 @@ import org.springframework.data.mongodb.core.index.IndexOperations;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.NumberUtils;
|
||||
|
||||
import com.mongodb.MongoException;
|
||||
import com.mongodb.client.MongoCollection;
|
||||
@@ -155,6 +157,20 @@ public class DefaultIndexOperations implements IndexOperations {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void alterIndex(String name, org.springframework.data.mongodb.core.index.IndexOptions options) {
|
||||
|
||||
Document indexOptions = new Document("name", name);
|
||||
indexOptions.putAll(options.toDocument());
|
||||
|
||||
Document result = mongoOperations
|
||||
.execute(db -> db.runCommand(new Document("collMod", collectionName).append("index", indexOptions)));
|
||||
|
||||
if(NumberUtils.convertNumberToTargetClass(result.get("ok", (Number) 0), Integer.class) != 1) {
|
||||
throw new UncategorizedMongoDbException("Index '%s' could not be modified. Response was %s".formatted(name, result.toJson()), null);
|
||||
}
|
||||
}
|
||||
|
||||
public void dropAllIndexes() {
|
||||
dropIndex("*");
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mongodb.UncategorizedMongoDbException;
|
||||
import org.springframework.data.mongodb.core.convert.QueryMapper;
|
||||
import org.springframework.data.mongodb.core.index.IndexDefinition;
|
||||
import org.springframework.data.mongodb.core.index.IndexInfo;
|
||||
@@ -29,6 +30,7 @@ import org.springframework.data.mongodb.core.index.ReactiveIndexOperations;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.NumberUtils;
|
||||
|
||||
import com.mongodb.client.model.IndexOptions;
|
||||
|
||||
@@ -104,6 +106,22 @@ public class DefaultReactiveIndexOperations implements ReactiveIndexOperations {
|
||||
}).next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> alterIndex(String name, org.springframework.data.mongodb.core.index.IndexOptions options) {
|
||||
|
||||
return mongoOperations.execute(db -> {
|
||||
Document indexOptions = new Document("name", name);
|
||||
indexOptions.putAll(options.toDocument());
|
||||
|
||||
return Flux.from(db.runCommand(new Document("collMod", collectionName).append("index", indexOptions)))
|
||||
.doOnNext(result -> {
|
||||
if(NumberUtils.convertNumberToTargetClass(result.get("ok", (Number) 0), Integer.class) != 1) {
|
||||
throw new UncategorizedMongoDbException("Index '%s' could not be modified. Response was %s".formatted(name, result.toJson()), null);
|
||||
}
|
||||
});
|
||||
}).then();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private MongoPersistentEntity<?> lookupPersistentEntity(String collection) {
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.mongodb.core.index.IndexOptions.Unique;
|
||||
import org.springframework.data.mongodb.core.query.Collation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -39,11 +40,9 @@ public class Index implements IndexDefinition {
|
||||
|
||||
private final Map<String, Direction> fieldSpec = new LinkedHashMap<String, Direction>();
|
||||
private @Nullable String name;
|
||||
private boolean unique = false;
|
||||
private boolean sparse = false;
|
||||
private boolean background = false;
|
||||
private boolean hidden = false;
|
||||
private long expire = -1;
|
||||
private final IndexOptions options = IndexOptions.none();
|
||||
private Optional<IndexFilter> filter = Optional.empty();
|
||||
private Optional<Collation> collation = Optional.empty();
|
||||
|
||||
@@ -71,7 +70,8 @@ public class Index implements IndexDefinition {
|
||||
* "https://docs.mongodb.org/manual/core/index-unique/">https://docs.mongodb.org/manual/core/index-unique/</a>
|
||||
*/
|
||||
public Index unique() {
|
||||
this.unique = true;
|
||||
|
||||
this.options.setUnique(Unique.YES);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -108,7 +108,8 @@ public class Index implements IndexDefinition {
|
||||
* @since 4.1
|
||||
*/
|
||||
public Index hidden() {
|
||||
this.hidden = true;
|
||||
|
||||
options.setHidden(true);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -148,7 +149,7 @@ public class Index implements IndexDefinition {
|
||||
public Index expire(long value, TimeUnit unit) {
|
||||
|
||||
Assert.notNull(unit, "TimeUnit for expiration must not be null");
|
||||
this.expire = unit.toSeconds(value);
|
||||
options.setExpire(Duration.ofSeconds(unit.toSeconds(value)));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -200,21 +201,13 @@ public class Index implements IndexDefinition {
|
||||
if (StringUtils.hasText(name)) {
|
||||
document.put("name", name);
|
||||
}
|
||||
if (unique) {
|
||||
document.put("unique", true);
|
||||
}
|
||||
if (sparse) {
|
||||
document.put("sparse", true);
|
||||
}
|
||||
if (background) {
|
||||
document.put("background", true);
|
||||
}
|
||||
if (hidden) {
|
||||
document.put("hidden", true);
|
||||
}
|
||||
if (expire >= 0) {
|
||||
document.put("expireAfterSeconds", expire);
|
||||
}
|
||||
document.putAll(options.toDocument());
|
||||
|
||||
filter.ifPresent(val -> document.put("partialFilterExpression", val.getFilterObject()));
|
||||
collation.ifPresent(val -> document.append("collation", val.toDocument()));
|
||||
|
||||
@@ -35,6 +35,14 @@ public interface IndexOperations {
|
||||
*/
|
||||
String ensureIndex(IndexDefinition indexDefinition);
|
||||
|
||||
/**
|
||||
* Drops an index from this collection.
|
||||
*
|
||||
* @param name name of index to hide.
|
||||
* @since 4.1
|
||||
*/
|
||||
void alterIndex(String name, IndexOptions options);
|
||||
|
||||
/**
|
||||
* Drops an index from this collection.
|
||||
*
|
||||
|
||||
@@ -50,6 +50,11 @@ public interface IndexOperationsAdapter extends IndexOperations {
|
||||
reactiveIndexOperations.dropIndex(name).block();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void alterIndex(String name, IndexOptions options) {
|
||||
reactiveIndexOperations.alterIndex(name, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dropAllIndexes() {
|
||||
reactiveIndexOperations.dropAllIndexes().block();
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright 2023 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 org.bson.Document;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Changeable properties of an index. Can be used for index creation and modification.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 4.1
|
||||
*/
|
||||
public class IndexOptions {
|
||||
|
||||
@Nullable
|
||||
private Duration expire;
|
||||
|
||||
@Nullable
|
||||
private Boolean hidden;
|
||||
|
||||
@Nullable
|
||||
private Unique unique;
|
||||
|
||||
public enum Unique {
|
||||
|
||||
NO,
|
||||
|
||||
/**
|
||||
* When unique is true the index rejects duplicate entries.
|
||||
*/
|
||||
YES,
|
||||
|
||||
/**
|
||||
* An existing index is not checked for pre-existing, duplicate index entries but inserting new duplicate entries
|
||||
* fails.
|
||||
*/
|
||||
PREPARE
|
||||
}
|
||||
|
||||
/**
|
||||
* @return new empty instance of {@link IndexOptions}.
|
||||
*/
|
||||
public static IndexOptions none() {
|
||||
return new IndexOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return new instance of {@link IndexOptions} having the {@link Unique#YES} flag set.
|
||||
*/
|
||||
public static IndexOptions unique() {
|
||||
|
||||
IndexOptions options = new IndexOptions();
|
||||
options.unique = Unique.YES;
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return new instance of {@link IndexOptions} having the hidden flag set.
|
||||
*/
|
||||
public static IndexOptions hidden() {
|
||||
|
||||
IndexOptions options = new IndexOptions();
|
||||
options.hidden = true;
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return new instance of {@link IndexOptions} with given expiration.
|
||||
*/
|
||||
public static IndexOptions expireAfter(Duration duration) {
|
||||
|
||||
IndexOptions options = new IndexOptions();
|
||||
options.unique = Unique.YES;
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the expiration time. A {@link Duration#isNegative() negative value} represents no expiration, {@literal null} if not set.
|
||||
*/
|
||||
public Duration getExpire() {
|
||||
return expire;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param expire must not be {@literal null}.
|
||||
*/
|
||||
public void setExpire(Duration expire) {
|
||||
this.expire = expire;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} if hidden, {@literal null} if not set.
|
||||
*/
|
||||
@Nullable
|
||||
public Boolean isHidden() {
|
||||
return hidden;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param hidden
|
||||
*/
|
||||
public void setHidden(boolean hidden) {
|
||||
this.hidden = hidden;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the unique property value, {@literal null} if not set.
|
||||
*/
|
||||
@Nullable
|
||||
public Unique getUnique() {
|
||||
return unique;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param unique must not be {@literal null}.
|
||||
*/
|
||||
public void setUnique(Unique unique) {
|
||||
this.unique = unique;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the store native representation
|
||||
*/
|
||||
public Document toDocument() {
|
||||
|
||||
Document document = new Document();
|
||||
if(unique != null) {
|
||||
switch (unique) {
|
||||
case NO -> document.put("unique", false);
|
||||
case YES -> document.put("unique", true);
|
||||
case PREPARE -> document.put("prepareUnique", true);
|
||||
}
|
||||
}
|
||||
if(hidden != null) {
|
||||
document.put("hidden", hidden);
|
||||
}
|
||||
|
||||
|
||||
if (expire != null && !expire.isNegative()) {
|
||||
document.put("expireAfterSeconds", expire.getSeconds());
|
||||
}
|
||||
return document;
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,15 @@ public interface ReactiveIndexOperations {
|
||||
*/
|
||||
Mono<String> ensureIndex(IndexDefinition indexDefinition);
|
||||
|
||||
/**
|
||||
* Alters the index with given {@literal name}.
|
||||
*
|
||||
* @param name name of index to hide.
|
||||
* @param
|
||||
* @since 4.1
|
||||
*/
|
||||
Mono<Void> alterIndex(String name, IndexOptions options);
|
||||
|
||||
/**
|
||||
* Drops an index from this collection.
|
||||
*
|
||||
|
||||
@@ -197,6 +197,16 @@ public class DefaultIndexOperationsIntegrationTests {
|
||||
assertThat(info.isHidden()).isTrue();
|
||||
}
|
||||
|
||||
@Test // GH-4348
|
||||
void alterIndexShouldAllowHiding() {
|
||||
|
||||
collection.createIndex(new Document("a", 1), new IndexOptions().name("my-index"));
|
||||
|
||||
indexOps.alterIndex("my-index", org.springframework.data.mongodb.core.index.IndexOptions.hidden());
|
||||
IndexInfo info = findAndReturnIndexInfo(indexOps.getIndexInfo(), "my-index");
|
||||
assertThat(info.isHidden()).isTrue();
|
||||
}
|
||||
|
||||
private IndexInfo findAndReturnIndexInfo(org.bson.Document keys) {
|
||||
return findAndReturnIndexInfo(indexOps.getIndexInfo(), keys);
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import org.springframework.data.mongodb.test.util.MongoTemplateExtension;
|
||||
import org.springframework.data.mongodb.test.util.ReactiveMongoTestTemplate;
|
||||
import org.springframework.data.mongodb.test.util.Template;
|
||||
|
||||
import com.mongodb.client.model.IndexOptions;
|
||||
import com.mongodb.reactivestreams.client.MongoCollection;
|
||||
|
||||
/**
|
||||
@@ -49,7 +50,7 @@ import com.mongodb.reactivestreams.client.MongoCollection;
|
||||
@ExtendWith(MongoTemplateExtension.class)
|
||||
public class DefaultReactiveIndexOperationsTests {
|
||||
|
||||
@Template(initialEntitySet = DefaultIndexOperationsIntegrationTestsSample.class)
|
||||
@Template(initialEntitySet = DefaultIndexOperationsIntegrationTestsSample.class) //
|
||||
static ReactiveMongoTestTemplate template;
|
||||
|
||||
String collectionName = template.getCollectionName(DefaultIndexOperationsIntegrationTestsSample.class);
|
||||
@@ -192,6 +193,22 @@ public class DefaultReactiveIndexOperationsTests {
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // GH-4348
|
||||
void alterIndexShouldAllowHiding() {
|
||||
|
||||
template.execute(collectionName, collection -> {
|
||||
return collection.createIndex(new Document("a", 1), new IndexOptions().name("my-index"));
|
||||
}).then().as(StepVerifier::create).verifyComplete();
|
||||
|
||||
indexOps.alterIndex("my-index", org.springframework.data.mongodb.core.index.IndexOptions.hidden())
|
||||
.as(StepVerifier::create).verifyComplete();
|
||||
indexOps.getIndexInfo().filter(this.indexByName("my-index")).as(StepVerifier::create) //
|
||||
.consumeNextWith(indexInfo -> {
|
||||
assertThat(indexInfo.isHidden()).isTrue();
|
||||
}) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
Predicate<IndexInfo> indexByName(String name) {
|
||||
return indexInfo -> indexInfo.getName().equals(name);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user