Skip to content

Commit 4b0c027

Browse files
Enable index modification via IndexOperations.
Introduce IndexOptions that can be used to alter an existing index via IndexOperations. See: #4348
1 parent 83217f3 commit 4b0c027

10 files changed

+254
-17
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/UncategorizedMongoDbException.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@
1616
package org.springframework.data.mongodb;
1717

1818
import org.springframework.dao.UncategorizedDataAccessException;
19+
import org.springframework.lang.Nullable;
1920

2021
public class UncategorizedMongoDbException extends UncategorizedDataAccessException {
2122

2223
private static final long serialVersionUID = -2336595514062364929L;
2324

24-
public UncategorizedMongoDbException(String msg, Throwable cause) {
25+
public UncategorizedMongoDbException(String msg, @Nullable Throwable cause) {
2526
super(msg, cause);
2627
}
2728
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java

+16
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@
2222
import org.bson.Document;
2323
import org.springframework.dao.DataAccessException;
2424
import org.springframework.data.mongodb.MongoDatabaseFactory;
25+
import org.springframework.data.mongodb.UncategorizedMongoDbException;
2526
import org.springframework.data.mongodb.core.convert.QueryMapper;
2627
import org.springframework.data.mongodb.core.index.IndexDefinition;
2728
import org.springframework.data.mongodb.core.index.IndexInfo;
2829
import org.springframework.data.mongodb.core.index.IndexOperations;
2930
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
3031
import org.springframework.lang.Nullable;
3132
import org.springframework.util.Assert;
33+
import org.springframework.util.NumberUtils;
3234

3335
import com.mongodb.MongoException;
3436
import com.mongodb.client.MongoCollection;
@@ -155,6 +157,20 @@ public void dropIndex(final String name) {
155157

156158
}
157159

160+
@Override
161+
public void alterIndex(String name, org.springframework.data.mongodb.core.index.IndexOptions options) {
162+
163+
Document indexOptions = new Document("name", name);
164+
indexOptions.putAll(options.toDocument());
165+
166+
Document result = mongoOperations
167+
.execute(db -> db.runCommand(new Document("collMod", collectionName).append("index", indexOptions)));
168+
169+
if(NumberUtils.convertNumberToTargetClass(result.get("ok", (Number) 0), Integer.class) != 1) {
170+
throw new UncategorizedMongoDbException("Index '%s' could not be modified. Response was %s".formatted(name, result.toJson()), null);
171+
}
172+
}
173+
158174
public void dropAllIndexes() {
159175
dropIndex("*");
160176
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperations.java

+18
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@
2222
import java.util.Optional;
2323

2424
import org.bson.Document;
25+
import org.springframework.data.mongodb.UncategorizedMongoDbException;
2526
import org.springframework.data.mongodb.core.convert.QueryMapper;
2627
import org.springframework.data.mongodb.core.index.IndexDefinition;
2728
import org.springframework.data.mongodb.core.index.IndexInfo;
2829
import org.springframework.data.mongodb.core.index.ReactiveIndexOperations;
2930
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
3031
import org.springframework.lang.Nullable;
3132
import org.springframework.util.Assert;
33+
import org.springframework.util.NumberUtils;
3234

3335
import com.mongodb.client.model.IndexOptions;
3436

@@ -104,6 +106,22 @@ public Mono<String> ensureIndex(final IndexDefinition indexDefinition) {
104106
}).next();
105107
}
106108

109+
@Override
110+
public Mono<Void> alterIndex(String name, org.springframework.data.mongodb.core.index.IndexOptions options) {
111+
112+
return mongoOperations.execute(db -> {
113+
Document indexOptions = new Document("name", name);
114+
indexOptions.putAll(options.toDocument());
115+
116+
return Flux.from(db.runCommand(new Document("collMod", collectionName).append("index", indexOptions)))
117+
.doOnNext(result -> {
118+
if(NumberUtils.convertNumberToTargetClass(result.get("ok", (Number) 0), Integer.class) != 1) {
119+
throw new UncategorizedMongoDbException("Index '%s' could not be modified. Response was %s".formatted(name, result.toJson()), null);
120+
}
121+
});
122+
}).then();
123+
}
124+
107125
@Nullable
108126
private MongoPersistentEntity<?> lookupPersistentEntity(String collection) {
109127

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Index.java

+8-15
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
import org.bson.Document;
2626
import org.springframework.data.domain.Sort.Direction;
27+
import org.springframework.data.mongodb.core.index.IndexOptions.Unique;
2728
import org.springframework.data.mongodb.core.query.Collation;
2829
import org.springframework.lang.Nullable;
2930
import org.springframework.util.Assert;
@@ -39,11 +40,9 @@ public class Index implements IndexDefinition {
3940

4041
private final Map<String, Direction> fieldSpec = new LinkedHashMap<String, Direction>();
4142
private @Nullable String name;
42-
private boolean unique = false;
4343
private boolean sparse = false;
4444
private boolean background = false;
45-
private boolean hidden = false;
46-
private long expire = -1;
45+
private final IndexOptions options = IndexOptions.none();
4746
private Optional<IndexFilter> filter = Optional.empty();
4847
private Optional<Collation> collation = Optional.empty();
4948

@@ -71,7 +70,8 @@ public Index named(String name) {
7170
* "https://docs.mongodb.org/manual/core/index-unique/">https://docs.mongodb.org/manual/core/index-unique/</a>
7271
*/
7372
public Index unique() {
74-
this.unique = true;
73+
74+
this.options.setUnique(Unique.YES);
7575
return this;
7676
}
7777

@@ -108,7 +108,8 @@ public Index background() {
108108
* @since 4.1
109109
*/
110110
public Index hidden() {
111-
this.hidden = true;
111+
112+
options.setHidden(true);
112113
return this;
113114
}
114115

@@ -148,7 +149,7 @@ public Index expire(Duration timeout) {
148149
public Index expire(long value, TimeUnit unit) {
149150

150151
Assert.notNull(unit, "TimeUnit for expiration must not be null");
151-
this.expire = unit.toSeconds(value);
152+
options.setExpire(Duration.ofSeconds(unit.toSeconds(value)));
152153
return this;
153154
}
154155

@@ -200,21 +201,13 @@ public Document getIndexOptions() {
200201
if (StringUtils.hasText(name)) {
201202
document.put("name", name);
202203
}
203-
if (unique) {
204-
document.put("unique", true);
205-
}
206204
if (sparse) {
207205
document.put("sparse", true);
208206
}
209207
if (background) {
210208
document.put("background", true);
211209
}
212-
if (hidden) {
213-
document.put("hidden", true);
214-
}
215-
if (expire >= 0) {
216-
document.put("expireAfterSeconds", expire);
217-
}
210+
document.putAll(options.toDocument());
218211

219212
filter.ifPresent(val -> document.put("partialFilterExpression", val.getFilterObject()));
220213
collation.ifPresent(val -> document.append("collation", val.toDocument()));

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexOperations.java

+8
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@ public interface IndexOperations {
3535
*/
3636
String ensureIndex(IndexDefinition indexDefinition);
3737

38+
/**
39+
* Drops an index from this collection.
40+
*
41+
* @param name name of index to hide.
42+
* @since 4.1
43+
*/
44+
void alterIndex(String name, IndexOptions options);
45+
3846
/**
3947
* Drops an index from this collection.
4048
*

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexOperationsAdapter.java

+5
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ public void dropIndex(String name) {
5050
reactiveIndexOperations.dropIndex(name).block();
5151
}
5252

53+
@Override
54+
public void alterIndex(String name, IndexOptions options) {
55+
reactiveIndexOperations.alterIndex(name, options);
56+
}
57+
5358
@Override
5459
public void dropAllIndexes() {
5560
reactiveIndexOperations.dropAllIndexes().block();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/*
2+
* Copyright 2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mongodb.core.index;
17+
18+
import java.time.Duration;
19+
20+
import org.bson.Document;
21+
import org.springframework.lang.Nullable;
22+
23+
/**
24+
* Changeable properties of an index. Can be used for index creation and modification.
25+
*
26+
* @author Christoph Strobl
27+
* @since 4.1
28+
*/
29+
public class IndexOptions {
30+
31+
@Nullable
32+
private Duration expire;
33+
34+
@Nullable
35+
private Boolean hidden;
36+
37+
@Nullable
38+
private Unique unique;
39+
40+
public enum Unique {
41+
42+
NO,
43+
44+
/**
45+
* When unique is true the index rejects duplicate entries.
46+
*/
47+
YES,
48+
49+
/**
50+
* An existing index is not checked for pre-existing, duplicate index entries but inserting new duplicate entries
51+
* fails.
52+
*/
53+
PREPARE
54+
}
55+
56+
/**
57+
* @return new empty instance of {@link IndexOptions}.
58+
*/
59+
public static IndexOptions none() {
60+
return new IndexOptions();
61+
}
62+
63+
/**
64+
* @return new instance of {@link IndexOptions} having the {@link Unique#YES} flag set.
65+
*/
66+
public static IndexOptions unique() {
67+
68+
IndexOptions options = new IndexOptions();
69+
options.unique = Unique.YES;
70+
return options;
71+
}
72+
73+
/**
74+
* @return new instance of {@link IndexOptions} having the hidden flag set.
75+
*/
76+
public static IndexOptions hidden() {
77+
78+
IndexOptions options = new IndexOptions();
79+
options.hidden = true;
80+
return options;
81+
}
82+
83+
/**
84+
* @return new instance of {@link IndexOptions} with given expiration.
85+
*/
86+
public static IndexOptions expireAfter(Duration duration) {
87+
88+
IndexOptions options = new IndexOptions();
89+
options.unique = Unique.YES;
90+
return options;
91+
}
92+
93+
/**
94+
* @return the expiration time. A {@link Duration#isNegative() negative value} represents no expiration, {@literal null} if not set.
95+
*/
96+
public Duration getExpire() {
97+
return expire;
98+
}
99+
100+
/**
101+
* @param expire must not be {@literal null}.
102+
*/
103+
public void setExpire(Duration expire) {
104+
this.expire = expire;
105+
}
106+
107+
/**
108+
* @return {@literal true} if hidden, {@literal null} if not set.
109+
*/
110+
@Nullable
111+
public Boolean isHidden() {
112+
return hidden;
113+
}
114+
115+
/**
116+
* @param hidden
117+
*/
118+
public void setHidden(boolean hidden) {
119+
this.hidden = hidden;
120+
}
121+
122+
/**
123+
* @return the unique property value, {@literal null} if not set.
124+
*/
125+
@Nullable
126+
public Unique getUnique() {
127+
return unique;
128+
}
129+
130+
/**
131+
* @param unique must not be {@literal null}.
132+
*/
133+
public void setUnique(Unique unique) {
134+
this.unique = unique;
135+
}
136+
137+
/**
138+
* @return the store native representation
139+
*/
140+
public Document toDocument() {
141+
142+
Document document = new Document();
143+
if(unique != null) {
144+
switch (unique) {
145+
case NO -> document.put("unique", false);
146+
case YES -> document.put("unique", true);
147+
case PREPARE -> document.put("prepareUnique", true);
148+
}
149+
}
150+
if(hidden != null) {
151+
document.put("hidden", hidden);
152+
}
153+
154+
155+
if (expire != null && !expire.isNegative()) {
156+
document.put("expireAfterSeconds", expire.getSeconds());
157+
}
158+
return document;
159+
}
160+
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/ReactiveIndexOperations.java

+9
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ public interface ReactiveIndexOperations {
3535
*/
3636
Mono<String> ensureIndex(IndexDefinition indexDefinition);
3737

38+
/**
39+
* Alters the index with given {@literal name}.
40+
*
41+
* @param name name of index to hide.
42+
* @param
43+
* @since 4.1
44+
*/
45+
Mono<Void> alterIndex(String name, IndexOptions options);
46+
3847
/**
3948
* Drops an index from this collection.
4049
*

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultIndexOperationsIntegrationTests.java

+10
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,16 @@ void shouldCreateHiddenIndex() {
197197
assertThat(info.isHidden()).isTrue();
198198
}
199199

200+
@Test // GH-4348
201+
void alterIndexShouldAllowHiding() {
202+
203+
collection.createIndex(new Document("a", 1), new IndexOptions().name("my-index"));
204+
205+
indexOps.alterIndex("my-index", org.springframework.data.mongodb.core.index.IndexOptions.hidden());
206+
IndexInfo info = findAndReturnIndexInfo(indexOps.getIndexInfo(), "my-index");
207+
assertThat(info.isHidden()).isTrue();
208+
}
209+
200210
private IndexInfo findAndReturnIndexInfo(org.bson.Document keys) {
201211
return findAndReturnIndexInfo(indexOps.getIndexInfo(), keys);
202212
}

0 commit comments

Comments
 (0)