Skip to content

Commit 46903fb

Browse files
authored
Add support for managing Atlas search indexes. (#1158)
- Add new collection helpers that allow users to define their Atlas Search Indexes directly within their code. - Implement unified tests to ensure the proper command structure and format. JAVA-4983
1 parent df1264b commit 46903fb

File tree

48 files changed

+3478
-4
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+3478
-4
lines changed

driver-core/src/main/com/mongodb/assertions/Assertions.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,34 @@ public static <T> T notNull(final String name, final T value) {
5050
return value;
5151
}
5252

53+
/**
54+
* Throw IllegalArgumentException if the values is null or contains null.
55+
*
56+
* <p><b>Note:</b> If performance is a concern, consider deferring the integrity validation
57+
* to the point of actual data iteration to avoid incurring additional reference chasing for collections of complex objects.
58+
* However, if performance considerations are low and it is acceptable to iterate over the data twice,
59+
* this method can still be used for validation purposes.
60+
*
61+
* @param name the parameter name.
62+
* @param values the values that should not contain null elements.
63+
* @param <T> the type of elements in the collection.
64+
* @return the input collection if it passes the null element validation.
65+
* @throws java.lang.IllegalArgumentException if the input collection is null or contains null elements.
66+
*/
67+
public static <T> Iterable<T> notNullElements(final String name, final Iterable<T> values) {
68+
if (values == null) {
69+
throw new IllegalArgumentException(name + " can not be null");
70+
}
71+
72+
for (T value : values) {
73+
if (value == null){
74+
throw new IllegalArgumentException(name + " can not contain null");
75+
}
76+
}
77+
78+
return values;
79+
}
80+
5381
/**
5482
* Throw IllegalArgumentException if the value is null.
5583
*
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
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+
* http://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+
17+
package com.mongodb.client.model;
18+
19+
import com.mongodb.lang.Nullable;
20+
import org.bson.conversions.Bson;
21+
22+
import static com.mongodb.assertions.Assertions.notNull;
23+
24+
/**
25+
* A model describing the creation of a single Atlas Search index.
26+
*
27+
* @since 4.11
28+
* @mongodb.server.release 7.0
29+
*/
30+
public final class SearchIndexModel {
31+
@Nullable
32+
private final String name;
33+
private final Bson definition;
34+
35+
/**
36+
* Construct an instance with the given Atlas Search index mapping definition.
37+
*
38+
* <p>After calling this constructor, the {@code name} field will be {@code null}. In that case, when passing this
39+
* {@code SearchIndexModel} to the {@code createSearchIndexes} method, the default search index name 'default'
40+
* will be used to create the search index.</p>
41+
*
42+
* @param definition the search index mapping definition.
43+
*/
44+
public SearchIndexModel(final Bson definition) {
45+
this.definition = notNull("definition", definition);
46+
this.name = null;
47+
}
48+
49+
/**
50+
* Construct an instance with the given Atlas Search name and index definition.
51+
*
52+
* @param name the search index name.
53+
* @param definition the search index mapping definition.
54+
*/
55+
public SearchIndexModel(final String name, final Bson definition) {
56+
this.definition = notNull("definition", definition);
57+
this.name = notNull("name", name);
58+
}
59+
60+
/**
61+
* Get the Atlas Search index mapping definition.
62+
*
63+
* @return the index definition.
64+
*/
65+
public Bson getDefinition() {
66+
return definition;
67+
}
68+
69+
/**
70+
* Get the Atlas Search index name.
71+
*
72+
* @return the search index name.
73+
*/
74+
@Nullable
75+
public String getName() {
76+
return name;
77+
}
78+
79+
@Override
80+
public String toString() {
81+
return "SearchIndexModel{"
82+
+ "name=" + name
83+
+ ", definition=" + definition
84+
+ '}';
85+
}
86+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
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+
* http://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+
17+
package com.mongodb.internal.operation;
18+
19+
20+
import com.mongodb.MongoCommandException;
21+
import com.mongodb.MongoNamespace;
22+
import com.mongodb.WriteConcern;
23+
import com.mongodb.internal.async.SingleResultCallback;
24+
import com.mongodb.internal.binding.AsyncWriteBinding;
25+
import com.mongodb.internal.binding.WriteBinding;
26+
import com.mongodb.lang.Nullable;
27+
import org.bson.BsonDocument;
28+
29+
import static com.mongodb.internal.operation.CommandOperationHelper.executeCommand;
30+
import static com.mongodb.internal.operation.CommandOperationHelper.executeCommandAsync;
31+
import static com.mongodb.internal.operation.CommandOperationHelper.writeConcernErrorTransformer;
32+
import static com.mongodb.internal.operation.CommandOperationHelper.writeConcernErrorWriteTransformer;
33+
import static com.mongodb.internal.operation.OperationHelper.withAsyncSourceAndConnection;
34+
import static com.mongodb.internal.operation.OperationHelper.withConnection;
35+
36+
/**
37+
* An abstract class for defining operations for managing Atlas Search indexes.
38+
*
39+
* <p>This class is not part of the public API and may be removed or changed at any time</p>
40+
*/
41+
abstract class AbstractWriteSearchIndexOperation implements AsyncWriteOperation<Void>, WriteOperation<Void> {
42+
private final MongoNamespace namespace;
43+
private final WriteConcern writeConcern;
44+
45+
AbstractWriteSearchIndexOperation(final MongoNamespace mongoNamespace,
46+
final WriteConcern writeConcern) {
47+
this.namespace = mongoNamespace;
48+
this.writeConcern = writeConcern;
49+
}
50+
51+
@Override
52+
public Void execute(final WriteBinding binding) {
53+
return withConnection(binding, connection -> {
54+
try {
55+
executeCommand(binding, namespace.getDatabaseName(), buildCommand(), connection, writeConcernErrorTransformer());
56+
} catch (MongoCommandException mongoCommandException) {
57+
swallowOrThrow(mongoCommandException);
58+
}
59+
return null;
60+
});
61+
}
62+
63+
@Override
64+
public void executeAsync(final AsyncWriteBinding binding, final SingleResultCallback<Void> callback) {
65+
withAsyncSourceAndConnection(binding::getWriteConnectionSource, false, callback,
66+
(connectionSource, connection, cb) ->
67+
executeCommandAsync(binding, namespace.getDatabaseName(), buildCommand(), connection,
68+
writeConcernErrorWriteTransformer(), (result, commandExecutionError) -> {
69+
try {
70+
swallowOrThrow(commandExecutionError);
71+
callback.onResult(result, null);
72+
} catch (Throwable mongoCommandException) {
73+
callback.onResult(null, mongoCommandException);
74+
}
75+
}
76+
)
77+
);
78+
}
79+
80+
/**
81+
* Handles the provided execution exception by either throwing it or ignoring it. This method is meant to be overridden
82+
* by subclasses that need to handle exceptions differently based on their specific requirements.
83+
*
84+
* <p>
85+
* <strong>Note:</strong> While the method declaration allows throwing a checked exception to enhance readability, the implementation
86+
* of this method must not throw a checked exception.
87+
* </p>
88+
*
89+
* @param <E> The type of the execution exception.
90+
* @param mongoExecutionException The execution exception to handle. If not null, it may be thrown or ignored.
91+
* @throws E The execution exception, if it is not null (implementation-specific).
92+
*/
93+
<E extends Throwable> void swallowOrThrow(@Nullable final E mongoExecutionException) throws E {
94+
if (mongoExecutionException != null) {
95+
throw mongoExecutionException;
96+
}
97+
}
98+
99+
abstract BsonDocument buildCommand();
100+
101+
MongoNamespace getNamespace() {
102+
return namespace;
103+
}
104+
105+
WriteConcern getWriteConcern() {
106+
return writeConcern;
107+
}
108+
}

driver-core/src/main/com/mongodb/internal/operation/AggregateOperation.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,12 @@ public AggregateOperation<T> collation(@Nullable final Collation collation) {
102102
return this;
103103
}
104104

105+
@Nullable
105106
public BsonValue getComment() {
106107
return wrapped.getComment();
107108
}
108109

109-
public AggregateOperation<T> comment(final BsonValue comment) {
110+
public AggregateOperation<T> comment(@Nullable final BsonValue comment) {
110111
wrapped.comment(comment);
111112
return this;
112113
}

driver-core/src/main/com/mongodb/internal/operation/AggregateOperationImpl.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,11 +152,12 @@ AggregateOperationImpl<T> collation(@Nullable final Collation collation) {
152152
return this;
153153
}
154154

155+
@Nullable
155156
BsonValue getComment() {
156157
return comment;
157158
}
158159

159-
AggregateOperationImpl<T> comment(final BsonValue comment) {
160+
AggregateOperationImpl<T> comment(@Nullable final BsonValue comment) {
160161
this.comment = comment;
161162
return this;
162163
}

driver-core/src/main/com/mongodb/internal/operation/AsyncOperations.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import com.mongodb.client.model.InsertOneOptions;
4141
import com.mongodb.client.model.RenameCollectionOptions;
4242
import com.mongodb.client.model.ReplaceOptions;
43+
import com.mongodb.client.model.SearchIndexModel;
4344
import com.mongodb.client.model.UpdateOptions;
4445
import com.mongodb.client.model.WriteModel;
4546
import com.mongodb.client.model.changestream.FullDocument;
@@ -48,6 +49,7 @@
4849
import com.mongodb.internal.client.model.AggregationLevel;
4950
import com.mongodb.internal.client.model.FindOptions;
5051
import com.mongodb.internal.client.model.changestream.ChangeStreamLevel;
52+
import com.mongodb.lang.Nullable;
5153
import org.bson.BsonDocument;
5254
import org.bson.BsonTimestamp;
5355
import org.bson.BsonValue;
@@ -274,6 +276,29 @@ public AsyncWriteOperation<Void> createIndexes(final List<IndexModel> indexes, f
274276
return operations.createIndexes(indexes, options);
275277
}
276278

279+
public AsyncWriteOperation<Void> createSearchIndexes(final List<SearchIndexModel> indexes) {
280+
return operations.createSearchIndexes(indexes);
281+
}
282+
283+
public AsyncWriteOperation<Void> updateSearchIndex(final String indexName, final Bson definition) {
284+
return operations.updateSearchIndex(indexName, definition);
285+
}
286+
287+
public AsyncWriteOperation<Void> dropSearchIndex(final String indexName) {
288+
return operations.dropSearchIndex(indexName);
289+
}
290+
291+
public <TResult> AsyncExplainableReadOperation<AsyncBatchCursor<TResult>> listSearchIndexes(final Class<TResult> resultClass,
292+
final long maxTimeMS,
293+
@Nullable final String indexName,
294+
@Nullable final Integer batchSize,
295+
@Nullable final Collation collation,
296+
@Nullable final BsonValue comment,
297+
@Nullable final Boolean allowDiskUse) {
298+
return operations.listSearchIndexes(resultClass, maxTimeMS, indexName, batchSize, collation,
299+
comment, allowDiskUse);
300+
}
301+
277302
public AsyncWriteOperation<Void> dropIndex(final String indexName, final DropIndexOptions options) {
278303
return operations.dropIndex(indexName, options);
279304
}

driver-core/src/main/com/mongodb/internal/operation/ChangeStreamOperation.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ public boolean getRetryReads() {
161161
return wrapped.getRetryReads();
162162
}
163163

164+
@Nullable
164165
public BsonValue getComment() {
165166
return wrapped.getComment();
166167
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
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+
* http://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+
17+
package com.mongodb.internal.operation;
18+
19+
import com.mongodb.MongoNamespace;
20+
import com.mongodb.WriteConcern;
21+
import org.bson.BsonArray;
22+
import org.bson.BsonDocument;
23+
import org.bson.BsonString;
24+
25+
import java.util.List;
26+
import java.util.stream.Collectors;
27+
28+
import static com.mongodb.assertions.Assertions.assertNotNull;
29+
import static com.mongodb.internal.operation.WriteConcernHelper.appendWriteConcernToCommand;
30+
31+
/**
32+
* An operation that creates one or more Atlas Search indexes.
33+
*
34+
* <p>This class is not part of the public API and may be removed or changed at any time</p>
35+
*/
36+
final class CreateSearchIndexesOperation extends AbstractWriteSearchIndexOperation {
37+
private static final String COMMAND_NAME = "createSearchIndexes";
38+
private final List<SearchIndexRequest> indexRequests;
39+
40+
CreateSearchIndexesOperation(final MongoNamespace namespace, final List<SearchIndexRequest> indexRequests,
41+
final WriteConcern writeConcern) {
42+
super(namespace, writeConcern);
43+
this.indexRequests = assertNotNull(indexRequests);
44+
}
45+
46+
private static BsonArray convert(final List<SearchIndexRequest> requests) {
47+
return requests.stream()
48+
.map(CreateSearchIndexesOperation::convert)
49+
.collect(Collectors.toCollection(BsonArray::new));
50+
}
51+
52+
private static BsonDocument convert(final SearchIndexRequest request) {
53+
BsonDocument bsonIndexRequest = new BsonDocument();
54+
String searchIndexName = request.getIndexName();
55+
if (searchIndexName != null) {
56+
bsonIndexRequest.append("name", new BsonString(searchIndexName));
57+
}
58+
bsonIndexRequest.append("definition", request.getDefinition());
59+
return bsonIndexRequest;
60+
}
61+
62+
@Override
63+
BsonDocument buildCommand() {
64+
BsonDocument command = new BsonDocument(COMMAND_NAME, new BsonString(getNamespace().getCollectionName()))
65+
.append("indexes", convert(indexRequests));
66+
appendWriteConcernToCommand(getWriteConcern(), command);
67+
return command;
68+
}
69+
}

0 commit comments

Comments
 (0)