diff --git a/driver-core/src/main/com/mongodb/client/model/SearchIndexModel.java b/driver-core/src/main/com/mongodb/client/model/SearchIndexModel.java index 124be4885c0..2a229e1a579 100644 --- a/driver-core/src/main/com/mongodb/client/model/SearchIndexModel.java +++ b/driver-core/src/main/com/mongodb/client/model/SearchIndexModel.java @@ -25,12 +25,14 @@ * A model describing the creation of a single Atlas Search index. * * @since 4.11 - * @mongodb.server.release 7.0 + * @mongodb.server.release 6.0 */ public final class SearchIndexModel { @Nullable private final String name; private final Bson definition; + @Nullable + private final SearchIndexType type; /** * Construct an instance with the given Atlas Search index mapping definition. @@ -42,8 +44,7 @@ public final class SearchIndexModel { * @param definition the search index mapping definition. */ public SearchIndexModel(final Bson definition) { - this.definition = notNull("definition", definition); - this.name = null; + this(null, definition, null); } /** @@ -53,8 +54,21 @@ public SearchIndexModel(final Bson definition) { * @param definition the search index mapping definition. */ public SearchIndexModel(final String name, final Bson definition) { + this(name, definition, null); + } + + /** + * Construct an instance with the given Atlas Search name, index definition, and type. + * + * @param name the search index name. + * @param definition the search index mapping definition. + * @param type the search index type. + * @since 5.2 + */ + public SearchIndexModel(@Nullable final String name, final Bson definition, @Nullable final SearchIndexType type) { this.definition = notNull("definition", definition); - this.name = notNull("name", name); + this.name = name; + this.type = type; } /** @@ -76,11 +90,23 @@ public String getName() { return name; } + /** + * Get the Atlas Search index type. + * + * @return the search index type. + * @since 5.2 + */ + @Nullable + public SearchIndexType getType() { + return type; + } + @Override public String toString() { return "SearchIndexModel{" + "name=" + name + ", definition=" + definition + + ", type=" + (type == null ? "null" : type.toBsonValue()) + '}'; } } diff --git a/driver-core/src/main/com/mongodb/client/model/SearchIndexType.java b/driver-core/src/main/com/mongodb/client/model/SearchIndexType.java new file mode 100644 index 00000000000..5ed73461a05 --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/SearchIndexType.java @@ -0,0 +1,83 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * 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 + * + * http://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 com.mongodb.client.model; + +import com.mongodb.annotations.Sealed; +import org.bson.BsonString; +import org.bson.BsonValue; + +import static com.mongodb.assertions.Assertions.notNull; + +/** + * This interface represents an Atlas Search Index type, which is utilized for creating specific types of indexes. + *

+ * It provides methods for creating and converting Atlas Search Index types to {@link BsonValue}. + *

+ * + * @mongodb.server.release 6.0 + * @see SearchIndexModel The model class that utilizes this index type. + * @since 5.2 + */ +@Sealed +public interface SearchIndexType { + + /** + * Returns a {@link SearchIndexType} instance representing the "search" index type. + * + * @return The requested {@link SearchIndexType}. + */ + static SearchIndexType search() { + return new SearchIndexTypeBson(new BsonString("search")); + } + + /** + * Returns a {@link SearchIndexType} instance representing the "vectorSearch" index type. + * + * @return The requested {@link SearchIndexType}. + */ + static SearchIndexType vectorSearch() { + return new SearchIndexTypeBson(new BsonString("vectorSearch")); + } + + /** + * Creates a {@link SearchIndexType} from a {@link BsonValue} in situations when there is no builder method + * that better satisfies your needs. + * This method cannot be used to validate the syntax. + *

+ * Example
+ * The following code creates two functionally equivalent {@link SearchIndexType}s, + * though they may not be {@linkplain Object#equals(Object) equal}. + *

{@code
+     *  SearchIndexType type1 = SearchIndexType.vectorSearch();
+     *  SearchIndexType type2 = SearchIndexType.of(new BsonString("vectorSearch"));
+     * }
+ * + * @param indexType A {@link BsonValue} representing the required {@link SearchIndexType}. + * @return The requested {@link SearchIndexType}. + */ + static SearchIndexType of(final BsonValue indexType) { + notNull("indexType", indexType); + return new SearchIndexTypeBson(indexType); + } + + /** + * Converts this object to {@link BsonValue}. + * + * @return A {@link BsonValue} representing this {@link SearchIndexType}. + */ + BsonValue toBsonValue(); +} diff --git a/driver-core/src/main/com/mongodb/client/model/SearchIndexTypeBson.java b/driver-core/src/main/com/mongodb/client/model/SearchIndexTypeBson.java new file mode 100644 index 00000000000..75e8788e681 --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/SearchIndexTypeBson.java @@ -0,0 +1,52 @@ +package com.mongodb.client.model; + +/* + * Copyright 2008-present MongoDB, Inc. + * + * 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 + * + * http://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. + */ + +import org.bson.BsonValue; + +import java.util.Objects; + +final class SearchIndexTypeBson implements SearchIndexType { + private final BsonValue bsonValue; + + SearchIndexTypeBson(final BsonValue bsonValue) { + this.bsonValue = bsonValue; + } + + @Override + public BsonValue toBsonValue() { + return bsonValue; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SearchIndexTypeBson that = (SearchIndexTypeBson) o; + return Objects.equals(bsonValue, that.bsonValue); + } + + @Override + public int hashCode() { + return Objects.hash(bsonValue); + } +} + diff --git a/driver-core/src/main/com/mongodb/internal/operation/CreateSearchIndexesOperation.java b/driver-core/src/main/com/mongodb/internal/operation/CreateSearchIndexesOperation.java index 1a44d887586..2e52e3fa0ae 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/CreateSearchIndexesOperation.java +++ b/driver-core/src/main/com/mongodb/internal/operation/CreateSearchIndexesOperation.java @@ -17,6 +17,7 @@ package com.mongodb.internal.operation; import com.mongodb.MongoNamespace; +import com.mongodb.client.model.SearchIndexType; import org.bson.BsonArray; import org.bson.BsonDocument; import org.bson.BsonString; @@ -52,6 +53,10 @@ private static BsonDocument convert(final SearchIndexRequest request) { if (searchIndexName != null) { bsonIndexRequest.append("name", new BsonString(searchIndexName)); } + SearchIndexType searchIndexType = request.getSearchIndexType(); + if (searchIndexType != null) { + bsonIndexRequest.append("type", searchIndexType.toBsonValue()); + } bsonIndexRequest.append("definition", request.getDefinition()); return bsonIndexRequest; } diff --git a/driver-core/src/main/com/mongodb/internal/operation/Operations.java b/driver-core/src/main/com/mongodb/internal/operation/Operations.java index e271f23d522..5ec696b61ce 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/Operations.java +++ b/driver-core/src/main/com/mongodb/internal/operation/Operations.java @@ -48,6 +48,7 @@ import com.mongodb.client.model.ReplaceOptions; import com.mongodb.client.model.ReturnDocument; import com.mongodb.client.model.SearchIndexModel; +import com.mongodb.client.model.SearchIndexType; import com.mongodb.client.model.UpdateManyModel; import com.mongodb.client.model.UpdateOneModel; import com.mongodb.client.model.UpdateOptions; @@ -752,7 +753,8 @@ private List toBsonDocumentList(@Nullable final List The type of the result. * @since 4.11 - * @mongodb.server.release 7.0 + * @mongodb.server.release 6.0 */ @Evolving public interface ListSearchIndexesPublisher extends Publisher { diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoCollection.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoCollection.java index 4e17208b342..821c7723a74 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoCollection.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoCollection.java @@ -1465,7 +1465,7 @@ Publisher findOneAndUpdate(ClientSession clientSession, Bson filter, * @param indexName the name of the search index to create. * @param definition Atlas Search index mapping definition. * @return a {@link Publisher} with search index name. - * @mongodb.server.release 7.0 + * @mongodb.server.release 6.0 * @mongodb.driver.manual reference/command/createSearchIndexes/ Create Search indexes * @since 4.11 */ @@ -1476,7 +1476,7 @@ Publisher findOneAndUpdate(ClientSession clientSession, Bson filter, * * @param definition Atlas Search index mapping definition. * @return a {@link Publisher} with search index name. - * @mongodb.server.release 7.0 + * @mongodb.server.release 6.0 * @mongodb.driver.manual reference/command/createSearchIndexes/ Create Search indexes * @since 4.11 */ @@ -1490,7 +1490,7 @@ Publisher findOneAndUpdate(ClientSession clientSession, Bson filter, * * @param searchIndexModels the search index models. * @return a {@link Publisher} with the search index names in the order specified by the given list {@link SearchIndexModel}s. - * @mongodb.server.release 7.0 + * @mongodb.server.release 6.0 * @mongodb.driver.manual reference/command/createSearchIndexes/ Create Search indexes * @since 4.11 */ @@ -1501,7 +1501,7 @@ Publisher findOneAndUpdate(ClientSession clientSession, Bson filter, * @param indexName the name of the search index to update. * @param definition Atlas Search index mapping definition. * @return an empty publisher that indicates when the operation has completed. - * @mongodb.server.release 7.0 + * @mongodb.server.release 6.0 * @mongodb.driver.manual reference/command/updateSearchIndex/ Update Search index * @since 4.11 */ @@ -1511,7 +1511,7 @@ Publisher findOneAndUpdate(ClientSession clientSession, Bson filter, * * @param indexName the name of the search index to drop. * @return an empty publisher that indicates when the operation has completed. - * @mongodb.server.release 7.0 + * @mongodb.server.release 6.0 * @mongodb.driver.manual reference/command/dropSearchIndex/ Drop Search index * @since 4.11 */ @@ -1522,7 +1522,7 @@ Publisher findOneAndUpdate(ClientSession clientSession, Bson filter, * * @return the fluent list search indexes interface. * @since 4.11 - * @mongodb.server.release 7.0 + * @mongodb.server.release 6.0 */ ListSearchIndexesPublisher listSearchIndexes(); @@ -1533,7 +1533,7 @@ Publisher findOneAndUpdate(ClientSession clientSession, Bson filter, * @param the target document type of the iterable. * @return the fluent list search indexes interface. * @since 4.11 - * @mongodb.server.release 7.0 + * @mongodb.server.release 6.0 */ ListSearchIndexesPublisher listSearchIndexes(Class resultClass); diff --git a/driver-scala/src/main/scala/org/mongodb/scala/ListSearchIndexesObservable.scala b/driver-scala/src/main/scala/org/mongodb/scala/ListSearchIndexesObservable.scala index 3987e830732..126d5976776 100644 --- a/driver-scala/src/main/scala/org/mongodb/scala/ListSearchIndexesObservable.scala +++ b/driver-scala/src/main/scala/org/mongodb/scala/ListSearchIndexesObservable.scala @@ -41,7 +41,7 @@ case class ListSearchIndexesObservable[TResult](wrapped: ListSearchIndexesPublis * Sets an Atlas Search index name for this operation. * * @param indexName Atlas Search index name. - * @note Requires MongoDB 7.0 or greater + * @note Requires MongoDB 6.0 or greater */ def name(indexName: String): ListSearchIndexesObservable[TResult] = { wrapped.name(indexName) diff --git a/driver-scala/src/main/scala/org/mongodb/scala/MongoCollection.scala b/driver-scala/src/main/scala/org/mongodb/scala/MongoCollection.scala index bdd63f9245a..48e09aa7921 100644 --- a/driver-scala/src/main/scala/org/mongodb/scala/MongoCollection.scala +++ b/driver-scala/src/main/scala/org/mongodb/scala/MongoCollection.scala @@ -1414,7 +1414,7 @@ case class MongoCollection[TResult](private val wrapped: JMongoCollection[TResul * @param definition the search index mapping definition. * @return an Observable with the search index name. * @since 4.11 - * @note Requires MongoDB 7.0 or greater + * @note Requires MongoDB 6.0 or greater * @see [[https://www.mongodb.com/docs/manual/reference/command/createSearchIndexes/ Create Search Indexes]] */ def createSearchIndex(indexName: String, definition: Bson): SingleObservable[String] = @@ -1426,7 +1426,7 @@ case class MongoCollection[TResult](private val wrapped: JMongoCollection[TResul * @param definition the search index mapping definition. * @return an Observable with search index name. * @since 4.11 - * @note Requires MongoDB 7.0 or greater + * @note Requires MongoDB 6.0 or greater * @see [[https://www.mongodb.com/docs/manual/reference/command/createSearchIndexes/ Create Search Indexes]] */ def createSearchIndex(definition: Bson): SingleObservable[String] = wrapped.createSearchIndex(definition) @@ -1441,7 +1441,7 @@ case class MongoCollection[TResult](private val wrapped: JMongoCollection[TResul * @return an Observable with the names of the search indexes * in the order specified by the given list of [[org.mongodb.scala.model.SearchIndexModel]]s. * @since 4.11 - * @note Requires MongoDB 7.0 or greater + * @note Requires MongoDB 6.0 or greater * @see [[https://www.mongodb.com/docs/manual/reference/command/createSearchIndexes/ Create Search Indexes]] */ def createSearchIndexes(searchIndexModels: List[SearchIndexModel]): Observable[String] = @@ -1454,7 +1454,7 @@ case class MongoCollection[TResult](private val wrapped: JMongoCollection[TResul * @param definition the search index mapping definition. * @return an Observable that indicates when the operation has completed. * @since 4.11 - * @note Requires MongoDB 7.0 or greater + * @note Requires MongoDB 6.0 or greater * @see [[https://www.mongodb.com/docs/manual/reference/command/updateSearchIndex/ Update Search Index]] */ def updateSearchIndex(indexName: String, definition: Bson): SingleObservable[Unit] = @@ -1466,7 +1466,7 @@ case class MongoCollection[TResult](private val wrapped: JMongoCollection[TResul * @param indexName the name of the search index to drop. * @return an Observable that indicates when the operation has completed. * @since 4.11 - * @note Requires MongoDB 7.0 or greater + * @note Requires MongoDB 6.0 or greater * @see [[https://www.mongodb.com/docs/manual/reference/command/dropSearchIndex/ Drop Search Index]] */ def dropSearchIndex(indexName: String): SingleObservable[Unit] = wrapped.dropSearchIndex(indexName) @@ -1477,7 +1477,7 @@ case class MongoCollection[TResult](private val wrapped: JMongoCollection[TResul * @tparam C the target document type of the observable. * @return the fluent list search indexes interface * @since 4.11 - * @note Requires MongoDB 7.0 or greater + * @note Requires MongoDB 6.0 or greater * @see [[https://www.mongodb.com/docs/manual/reference/operator/aggregation/listSearchIndexes List Search Indexes]] */ def listSearchIndexes[C]()(implicit e: C DefaultsTo Document, ct: ClassTag[C]): ListSearchIndexesObservable[C] = diff --git a/driver-scala/src/main/scala/org/mongodb/scala/model/package.scala b/driver-scala/src/main/scala/org/mongodb/scala/model/package.scala index 111af0e6568..0d23a38c2e8 100644 --- a/driver-scala/src/main/scala/org/mongodb/scala/model/package.scala +++ b/driver-scala/src/main/scala/org/mongodb/scala/model/package.scala @@ -19,7 +19,7 @@ package org.mongodb.scala import com.mongodb.annotations.{ Beta, Reason, Sealed } import scala.collection.JavaConverters._ -import com.mongodb.client.model.{ GeoNearOptions, MongoTimeUnit => JMongoTimeUnit, WindowOutputField } +import com.mongodb.client.model.{ MongoTimeUnit => JMongoTimeUnit } import org.mongodb.scala.bson.conversions.Bson // scalastyle:off number.of.methods number.of.types @@ -481,6 +481,11 @@ package object model { */ type SearchIndexModel = com.mongodb.client.model.SearchIndexModel + /** + * Represents an Atlas Search Index type, which is utilized for creating specific types of indexes. + */ + type SearchIndexType = com.mongodb.client.model.SearchIndexType + /** * A model describing the creation of a single Atlas Search index. */ @@ -507,6 +512,17 @@ package object model { */ def apply(indexName: String, definition: Bson): SearchIndexModel = new com.mongodb.client.model.SearchIndexModel(indexName, definition) + + /** + * Construct an instance with the given search index name and definition. + * + * @param indexName the name of the search index to create. + * @param definition the search index mapping definition. + * @param indexType the search index type. + * @return the SearchIndexModel + */ + def apply(indexName: Option[String], definition: Bson, indexType: Option[SearchIndexType]): SearchIndexModel = + new com.mongodb.client.model.SearchIndexModel(indexName.orNull, definition, indexType.orNull) } /** diff --git a/driver-sync/src/main/com/mongodb/client/ListSearchIndexesIterable.java b/driver-sync/src/main/com/mongodb/client/ListSearchIndexesIterable.java index 2384fcef29d..a5579bacfd5 100644 --- a/driver-sync/src/main/com/mongodb/client/ListSearchIndexesIterable.java +++ b/driver-sync/src/main/com/mongodb/client/ListSearchIndexesIterable.java @@ -34,7 +34,7 @@ * @param The type of the result. * @mongodb.driver.manual reference/operator/aggregation/listSearchIndexes ListSearchIndexes * @since 4.11 - * @mongodb.server.release 7.0 + * @mongodb.server.release 6.0 */ @Evolving public interface ListSearchIndexesIterable extends MongoIterable { diff --git a/driver-sync/src/main/com/mongodb/client/MongoCollection.java b/driver-sync/src/main/com/mongodb/client/MongoCollection.java index 7db38040bed..0d3248b613f 100644 --- a/driver-sync/src/main/com/mongodb/client/MongoCollection.java +++ b/driver-sync/src/main/com/mongodb/client/MongoCollection.java @@ -1751,7 +1751,7 @@ BulkWriteResult bulkWrite(ClientSession clientSession, List listSearchIndexes(); @@ -1819,7 +1819,7 @@ BulkWriteResult bulkWrite(ClientSession clientSession, List the target document type of the iterable. * @return the list search indexes iterable interface. * @since 4.11 - * @mongodb.server.release 7.0 + * @mongodb.server.release 6.0 */ ListSearchIndexesIterable listSearchIndexes(Class resultClass); diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractAtlasSearchIndexManagementProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractAtlasSearchIndexManagementProseTest.java index fd7bc428576..17c007e14ba 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractAtlasSearchIndexManagementProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractAtlasSearchIndexManagementProseTest.java @@ -17,9 +17,11 @@ package com.mongodb.client; import com.mongodb.MongoClientSettings; +import com.mongodb.MongoCommandException; import com.mongodb.ReadConcern; import com.mongodb.WriteConcern; import com.mongodb.client.model.SearchIndexModel; +import com.mongodb.client.model.SearchIndexType; import com.mongodb.event.CommandListener; import com.mongodb.event.CommandStartedEvent; import org.bson.BsonDocument; @@ -32,7 +34,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.UUID; @@ -46,11 +47,15 @@ import static com.mongodb.assertions.Assertions.assertFalse; import static com.mongodb.client.Fixture.getMongoClientSettings; import static com.mongodb.client.Fixture.getMongoClientSettingsBuilder; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; /** - * See Search Index Management Tests + * See Search Index Management Tests */ public abstract class AbstractAtlasSearchIndexManagementProseTest { /** @@ -74,6 +79,18 @@ public abstract class AbstractAtlasSearchIndexManagementProseTest { "{" + " mappings: { dynamic: true }" + "}"); + private static final Document VECTOR_SEARCH_DEFINITION = Document.parse( + "{" + + " fields: [" + + " {" + + " type: 'vector'," + + " path: 'plot_embedding'," + + " numDimensions: 1536," + + " similarity: 'euclidean'," + + " }," + + " ]" + + "}"); + private MongoClient client = createMongoClient(getMongoClientSettings()); private MongoDatabase db; private MongoCollection collection; @@ -153,7 +170,7 @@ public void shouldCreateMultipleIndexesInBatch() throws InterruptedException { SearchIndexModel searchIndexModel2 = new SearchIndexModel(TEST_SEARCH_INDEX_NAME_2, NOT_DYNAMIC_MAPPING_DEFINITION); //when - List searchIndexes = collection.createSearchIndexes(Arrays.asList(searchIndexModel1, searchIndexModel2)); + List searchIndexes = collection.createSearchIndexes(asList(searchIndexModel1, searchIndexModel2)); //then assertThat(searchIndexes, contains(TEST_SEARCH_INDEX_NAME_1, TEST_SEARCH_INDEX_NAME_2)); @@ -200,6 +217,69 @@ public void shouldSuppressNamespaceErrorWhenDroppingIndexWithoutCollection() { collection.dropSearchIndex("not existent index"); } + @Test + @DisplayName("Case 7 implicit: Driver can successfully handle search index types when creating indexes") + public void shouldHandleImplicitSearchIndexTypes() throws InterruptedException { + //given + String indexName = "test-search-index-case7-implicit"; + + //when + String result = collection.createSearchIndex( + indexName, + NOT_DYNAMIC_MAPPING_DEFINITION); + + //then + assertEquals(indexName, result); + awaitIndexChanges(isQueryable().and(hasSearchIndexType()), new SearchIndexModel(indexName, NOT_DYNAMIC_MAPPING_DEFINITION)); + } + + @Test + @DisplayName("Case 7 explicit 'search' type: Driver can successfully handle search index types when creating indexes") + public void shouldHandleExplicitSearchIndexTypes() throws InterruptedException { + //given + String indexName = "test-search-index-case7-explicit"; + + //when + List searchIndexes = collection.createSearchIndexes(singletonList(new SearchIndexModel( + indexName, + NOT_DYNAMIC_MAPPING_DEFINITION, + SearchIndexType.search()))); + + //then + assertEquals(1, searchIndexes.size()); + assertEquals(indexName, searchIndexes.get(0)); + awaitIndexChanges(isQueryable().and(hasSearchIndexType()), new SearchIndexModel(indexName, NOT_DYNAMIC_MAPPING_DEFINITION)); + } + + @Test + @DisplayName("Case 7 explicit 'vectorSearch' type: Driver can successfully handle search index types when creating indexes") + public void shouldHandleExplicitVectorSearchIndexTypes() throws InterruptedException { + //given + String indexName = "test-search-index-case7-vector"; + + //when + List searchIndexes = collection.createSearchIndexes(singletonList(new SearchIndexModel( + indexName, + VECTOR_SEARCH_DEFINITION, + SearchIndexType.vectorSearch()))); + + //then + assertEquals(1, searchIndexes.size()); + assertEquals(indexName, searchIndexes.get(0)); + awaitIndexChanges(isQueryable().and(hasVectorSearchIndexType()), new SearchIndexModel(indexName, NOT_DYNAMIC_MAPPING_DEFINITION)); + } + + @Test + @DisplayName("Case 8: Driver requires explicit type to create a vector search index") + public void shouldRequireExplicitTypeToCreateVectorSearchIndex() { + //given + String indexName = "test-search-index-case8-error"; + + //when & then + assertThrows(MongoCommandException.class, () -> collection.createSearchIndex( + indexName, + VECTOR_SEARCH_DEFINITION)); + } private void assertIndexDeleted() throws InterruptedException { int attempts = MAX_WAIT_ATTEMPTS; @@ -250,6 +330,16 @@ private Predicate isReady() { } + private Predicate hasSearchIndexType() { + return document -> "search".equals(document.getString("type")); + } + + private Predicate hasVectorSearchIndexType() { + return document -> "vectorSearch".equals(document.getString("type")); + } + + + private boolean checkAttempt(final int attempt) { Assertions.assertFalse(attempt <= 0, "Exceeded maximum attempts waiting for Search Index changes in Atlas cluster"); return true; diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudHelper.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudHelper.java index 67f95903997..041f016510f 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudHelper.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudHelper.java @@ -68,6 +68,7 @@ import com.mongodb.client.model.ReplaceOptions; import com.mongodb.client.model.ReturnDocument; import com.mongodb.client.model.SearchIndexModel; +import com.mongodb.client.model.SearchIndexType; import com.mongodb.client.model.TimeSeriesGranularity; import com.mongodb.client.model.TimeSeriesOptions; import com.mongodb.client.model.UpdateManyModel; @@ -99,6 +100,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; @@ -1508,19 +1510,24 @@ OperationResult executeCreateSearchIndex(final BsonDocument operation) { MongoCollection collection = getMongoCollection(operation); BsonDocument arguments = operation.getDocument("arguments", new BsonDocument()); BsonDocument model = arguments.getDocument("model"); - BsonDocument definition = model.getDocument("definition"); return resultOf(() -> { - if (model.containsKey("name")) { - String name = model.getString("name").getValue(); - collection.createSearchIndex(name, definition); - } else { - collection.createSearchIndex(definition); - } + collection.createSearchIndexes(Collections.singletonList(toIndexSearchModel(model))); return null; }); } + private static SearchIndexType getSearchIndexType(final BsonString type) { + switch (type.getValue()) { + case "search": + return SearchIndexType.search(); + case "vectorSearch": + return SearchIndexType.vectorSearch(); + default: + throw new UnsupportedOperationException("Unsupported search index type: " + type.getValue()); + } + } + OperationResult executeCreateSearchIndexes(final BsonDocument operation) { MongoCollection collection = getMongoCollection(operation); BsonDocument arguments = operation.getDocument("arguments", new BsonDocument()); @@ -1561,14 +1568,12 @@ OperationResult executeDropSearchIndex(final BsonDocument operation) { private static SearchIndexModel toIndexSearchModel(final BsonValue bsonValue) { BsonDocument model = bsonValue.asDocument(); - String name; BsonDocument definition = model.getDocument("definition"); - if (model.containsKey("name")) { - name = model.getString("name").getValue(); - return new SearchIndexModel(name, definition); - } else { - return new SearchIndexModel(definition); - } + SearchIndexType type = model.containsKey("type") ? getSearchIndexType(model.getString("type")) : null; + String name = Optional.ofNullable(model.getString("name", null)) + .map(BsonString::getValue). + orElse(null); + return new SearchIndexModel(name, definition, type); }