From f4740931b54b389b2dc44c65a5380ae69a83b153 Mon Sep 17 00:00:00 2001 From: rustagir Date: Thu, 11 Jan 2024 08:55:49 -0500 Subject: [PATCH] DOCSP-33429: (wip) atlas search idx mgmt --- examples/src/test/kotlin/SearchIndexesTest.kt | 485 ++++++++++++++++++ source/fundamentals/indexes.txt | 174 +++++-- 2 files changed, 623 insertions(+), 36 deletions(-) create mode 100644 examples/src/test/kotlin/SearchIndexesTest.kt diff --git a/examples/src/test/kotlin/SearchIndexesTest.kt b/examples/src/test/kotlin/SearchIndexesTest.kt new file mode 100644 index 00000000..5d4e6a26 --- /dev/null +++ b/examples/src/test/kotlin/SearchIndexesTest.kt @@ -0,0 +1,485 @@ + +import com.mongodb.DuplicateKeyException +import com.mongodb.MongoCommandException +import com.mongodb.client.model.ClusteredIndexOptions +import com.mongodb.client.model.CreateCollectionOptions +import com.mongodb.client.model.Filters +import com.mongodb.client.model.IndexOptions +import com.mongodb.client.model.Indexes +import com.mongodb.client.model.Projections +import com.mongodb.client.model.Sorts +import com.mongodb.client.model.geojson.Point +import com.mongodb.client.model.geojson.Position +import com.mongodb.kotlin.client.coroutine.MongoClient +import config.getConfig +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.runBlocking +import org.bson.Document +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +// :replace-start: { +// "terms": { +// "CONNECTION_URI_PLACEHOLDER": "\"\"" +// } +// } +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class SearchIndexesTest { + + // :snippet-start: data-classes + // Data class for the movies collection + data class Movie( + val title: String, + val year: Int, + val cast: List, + val genres: List, + val type: String, + val rated: String, + val plot: String, + val fullplot: String, + ) + + // Data class for the theaters collection + data class Theater( + val theaterId: Int, + val location: Location + ) { + data class Location( + val address: Address, + val geo: Point + ) { + data class Address( + val street1: String, + val city: String, + val state: String, + val zipcode: String + ) + } + } + // :snippet-end: + + companion object { + private val config = getConfig() + private val CONNECTION_URI_PLACEHOLDER = config.connectionUri + + val mongoClient = MongoClient.create(CONNECTION_URI_PLACEHOLDER) + val database = mongoClient.getDatabase("sample_mflix") + val moviesCollection = database.getCollection("movies") + val theatersCollection = database.getCollection("theaters") + + @BeforeAll + @JvmStatic + fun beforeAll() { + runBlocking { + moviesCollection.insertMany( + listOf( + Movie( + title = "The Shawshank Redemption", + year = 1994, + cast = listOf("Tim Robbins", "Morgan Freeman", "Bob Gunton"), + genres = listOf("Drama"), + type = "movie", + rated = "R", + plot = "Two imprisoned men bond over a number of years, finding solace and eventual redemption through acts of common decency.", + fullplot = "Andy Dufresne is sent to Shawshank Prison for the murder of his wife and her secret lover. He is very isolated and lonely at first, but realizes there is something deep inside your body that people can't touch or get to....'HOPE'. Andy becomes friends with prison 'fixer' Red, and Andy epitomizes why it is crucial to have dreams. His spirit and determination lead us into a world full of imagination, one filled with courage and desire. Will Andy ever realize his dreams?" + ), + Movie( + title = "The Godfather", + year = 1972, + cast = listOf("Marlon Brando", "Al Pacino", "James Caan"), + genres = listOf("Crime", "Drama"), + type = "movie", + rated = "R", + plot = "The aging patriarch of an organized crime dynasty transfers control of his clandestine empire to his reluctant son.", + fullplot = "When the aging head of a famous crime family decides to transfer his position to one of his subalterns, a series of unfortunate events start happening to the family, and a war begins between all the well-known families leading to insolence, deportation, murder and revenge, and ends with the favorable successor being finally chosen." + ), + Movie( + title = "Pulp Fiction", + year = 1994, + cast = listOf("John Travolta", "Samuel L. Jackson", "Uma Thurman"), + genres = listOf("Crime", "Drama"), + type = "movie", + rated = "R", + plot = "The lives of two mob hitmen, a boxer, a gangster's wife, and a pair of diner bandits intertwine in four tales of violence and redemption.", + fullplot = "Jules Winnfield and Vincent Vega are two hitmen who are out to retrieve a suitcase stolen from their employer, mob boss Marsellus Wallace. Wallace has also asked Vincent to take his wife Mia out a few days later when Wallace himself will be out of town. Butch Coolidge is an aging boxer who is paid by Wallace to lose his fight. The lives of these seemingly unrelated people are woven together comprising of a series of funny, bizarre and uncalled-for incidents." + ), + + Movie( + title = "The Dark Knight", + year = 2008, + cast = listOf("Christian Bale", "Heath Ledger", "Aaron Eckhart"), + genres = listOf("Action", "Crime", "Drama"), + type = "movie", + rated = "PG-13", + plot = "When the menace known as the Joker wreaks havoc and chaos on the people of Gotham, Batman must accept one of the greatest psychological and physical tests of his ability to fight injustice.", + fullplot = "Set within a year after the events of Batman Begins, Batman, Lieutenant James Gordon, and new district attorney Harvey Dent successfully begin to round up the criminals that plague Gotham City until a mysterious and sadistic criminal mastermind known only as the Joker appears in Gotham, creating a new wave of chaos. Batman's struggle against the Joker becomes deeply personal, forcing him to 'confront everything he believes' and improve his technology to stop him. A love triangle develops between Bruce Wayne, Dent, and Rachel Dawes." + ), + Movie( + title = "Forrest Gump", + year = 1994, + cast = listOf("Tom Hanks", "Robin Wright", "Gary Sinise"), + genres = listOf("Drama", "Romance"), + type = "movie", + rated = "PG-13", + plot = "The presidencies of Kennedy and Johnson, the Vietnam War, the Watergate scandal and other historical events unfold from the perspective of an Alabama man with an IQ of 75, whose only desire is to be reunited with his childhood sweetheart.", + fullplot = "Forrest Gump is a simple man with a low IQ but good intentions. He is running through childhood with his best and only friend Jenny. His 'mama' teaches him the ways of life and leaves him to choose his destiny. Forrest joins the army for service in Vietnam, finding new friends called Dan and Bubba, he wins medals, creates a famous shrimp fishing fleet, inspires people to jog, starts a ping-pong craze, creates the smiley, writes bumper stickers and songs, donates to people and meets the president several times. However, this is all irrelevant to Forrest who can only think of his childhood sweetheart Jenny. Who has messed up her life. Although in the end, all he wants to prove is that anyone can love anyone." + ) + ) + ) + theatersCollection.insertMany( + listOf( + Theater( + theaterId = 101, + location = Theater.Location( + address = Theater.Location.Address( + street1 = "123 Broadway", + city = "New York", + state = "NY", + zipcode = "10001" + ), + geo = Point( + Position( + -73.98500, + 40.7610 + ) + ) + ) + ), + Theater( + theaterId = 102, + location = Theater.Location( + address = Theater.Location.Address( + street1 = "456 Main Street", + city = "Los Angeles", + state = "CA", + zipcode = "90001" + ), + geo = Point( + Position( + -118.24368, + 34.05223 + ) + ) + ) + ) + ) + ) + } + } + + @AfterAll + @JvmStatic + fun afterAll() { + runBlocking { + moviesCollection.drop() + theatersCollection.drop() + } + mongoClient.close() + } + } + + @AfterEach + fun afterEach() { + runBlocking { + moviesCollection.dropIndexes() + theatersCollection.dropIndexes() + } + } + + + @Test + fun singleIndexTest() = runBlocking { + // :snippet-start: single-index-setup + val resultCreateIndex = moviesCollection.createIndex(Indexes.ascending(Movie::title.name)) + println("Index created: $resultCreateIndex") + // :snippet-end: + assertEquals("title_1", resultCreateIndex) + // :snippet-start: single-index-query + val filter = Filters.eq(Movie::title.name, "The Dark Knight") + val sort = Sorts.ascending(Movie::title.name) + val projection = Projections.fields( + Projections.include(Movie::title.name), + Projections.excludeId() + ) + + data class Results(val title: String) + + val resultsFlow = moviesCollection.find(filter).sort(sort).projection(projection) + + resultsFlow.collect { println(it) } + // :snippet-end: + val results = resultsFlow.toList() + assertTrue(results.any { it.title == "The Dark Knight" }) + } + + @Test + fun compoundIndexTest() = runBlocking { + // :snippet-start: compound-index-setup + val resultCreateIndex = moviesCollection.createIndex(Indexes.ascending(Movie::type.name, Movie::rated.name)) + + println("Index created: $resultCreateIndex") + // :snippet-end: + assertEquals("type_1_rated_1", resultCreateIndex) + // :snippet-start: compound-index-query + val filter = Filters.and( + Filters.eq(Movie::type.name, "movie"), + Filters.eq(Movie::rated.name, "G") + ) + val sort = Sorts.ascending(Movie::type.name, Movie::rated.name) + val projection = Projections.fields( + Projections.include(Movie::type.name, Movie::rated.name), + Projections.excludeId() + ) + val resultsFlow = moviesCollection.find(filter).sort(sort).projection(projection) + + resultsFlow.collect { println(it) } + // :snippet-end: + val results = resultsFlow.toList() + assertTrue(results.all { it.title == "Saving Private Ryan" }) + } + + @Test + fun multiKeyIndexTest() = runBlocking { + // :snippet-start: multikey-index-setup + val resultCreateIndex = + moviesCollection.createIndex(Indexes.ascending(Movie::rated.name, Movie::genres.name, Movie::title.name)) + + println("Index created: $resultCreateIndex") + // :snippet-end: + assertEquals("rated_1_genres_1_title_1", resultCreateIndex) + // :snippet-start: multikey-index-query + val filter = Filters.and( + Filters.eq(Movie::genres.name, "Animation"), + Filters.eq(Movie::rated.name, "G") + ) + val sort = Sorts.ascending(Movie::title.name) + val projection = Projections.fields( + Projections.include(Movie::title.name, Movie::rated.name), + Projections.excludeId() + ) + val resultsFlow = moviesCollection.find(filter).sort(sort).projection(projection) + + resultsFlow.collect { println(it) } + // :snippet-end: + val results = resultsFlow.toList() + assertTrue(results.all { it.rated == "G" && it.genres.contains("Animation") }) + + } + + @Test + fun textIndexTest() = runBlocking { + suspend fun generateTextIndex(): String { + return ( + // :snippet-start: text-index-setup + try { + val resultCreateIndex = moviesCollection.createIndex(Indexes.text(Movie::plot.name)) + println("Index created: $resultCreateIndex") + resultCreateIndex // :remove: + } catch (e: MongoCommandException) { + if (e.errorCodeName == "IndexOptionsConflict") { + println("there's an existing text index with different options") + return "EXPECTED_EXCEPTION" // :remove: + } + "UNEXPECTED_EXCEPTION" // :remove: + } + // :snippet-end: + ) + + } + // Test successful index generation + assertEquals("plot_text", generateTextIndex()) + // :snippet-start: text-index-query + val filter = Filters.text("Batman") + val projection = Projections.fields( + Projections.include(Movie::fullplot.name), + Projections.excludeId() + ) + + data class Results(val fullplot: String) + + val resultsFlow = moviesCollection.find(filter).projection(projection) + + resultsFlow.collect { println(it) } + // :snippet-end: + val results = resultsFlow.toList() + assertTrue(results.any { it.fullplot.contains("district attorney Harvey Dent") }) + // Test duplicate index generation error + moviesCollection.dropIndex("plot_text") + moviesCollection.createIndex(Indexes.text(Movie::plot.name), IndexOptions().name("plot_text_1")) + assertEquals("EXPECTED_EXCEPTION", generateTextIndex()) + } + + @Test + fun textMultipleIndex() = runBlocking { + suspend fun generateTextMultipleIndex(): String { + return ( + // :snippet-start: text-multiple-index + try { + val resultCreateIndex = moviesCollection.createIndex( + Indexes.compoundIndex( + Indexes.text(Movie::title.name), Indexes.text(Movie::genres.name) + ) + ) + println("Index created: $resultCreateIndex") + resultCreateIndex // :remove: + } catch (e: MongoCommandException) { + if (e.errorCodeName == "IndexOptionsConflict") { + println("there's an existing text index with different options") + return "EXPECTED_EXCEPTION" // :remove: + } + "UNEXPECTED_EXCEPTION" // :remove: + } + // :snippet-end: + ) + } + // Test successful index generation + assertEquals("title_text_genres_text", generateTextMultipleIndex()) + // Test duplicate index generation error + moviesCollection.dropIndex("title_text_genres_text") + moviesCollection.createIndex(Indexes.text(Movie::genres.name)) + assertEquals("EXPECTED_EXCEPTION", generateTextMultipleIndex()) + } + + @Test + fun geoSpatialIndexTest() = runBlocking { + // :snippet-start: geospatial-index-setup + val resultCreateIndex = theatersCollection.createIndex( + Indexes.geo2dsphere("${Theater::location.name}.${Theater.Location::geo.name}") + ) + + println("Index created: $resultCreateIndex") + // :snippet-end: + // Test that the index was created + assertEquals("location.geo_2dsphere", resultCreateIndex) + // :snippet-start: geospatial-index-query + // MongoDB Headquarters in New York, NY. + val refPoint = Point(Position(-73.98456, 40.7612)) + val filter = Filters.near( + "${Theater::location.name}.${Theater.Location::geo.name}", + refPoint, 1000.0, 0.0 + ) + val resultsFlow = theatersCollection.find(filter) + + resultsFlow.collect { println(it) } + // :snippet-end: + val results = resultsFlow.toList() + assertTrue(results.isNotEmpty()) + } + + @Test + fun uniqueIndex() = runBlocking { + suspend fun executeCreateUniqueIndex(): String { + return ( + // :snippet-start: unique-index + try { + val indexOptions = IndexOptions().unique(true) + val resultCreateIndex = theatersCollection.createIndex( + Indexes.descending(Theater::theaterId.name), indexOptions + ) + println("Index created: $resultCreateIndex") + resultCreateIndex // :remove: + } catch (e: DuplicateKeyException) { + println("duplicate field values encountered, couldn't create index: \t${e.message}") + "EXCEPTION" // :remove: + } + // :snippet-end: + ) + } + + // Test that the index was created successfully + assertEquals("theaterId_-1", executeCreateUniqueIndex()) + val results = theatersCollection.find().toList() + val theaterIds = results.map { it.theaterId } + assertEquals(theaterIds.size, theaterIds.distinct().size) + + // Clear previous test's index + theatersCollection.dropIndex("theaterId_-1") + + // Test that index creation fails when there are duplicate values + val duplicateTheater = Theater( + theaterId = 101, + location = Theater.Location( + address = Theater.Location.Address( + street1 = "123 Broadway", + city = "New York", + state = "NY", + zipcode = "10001" + ), + geo = Point( + Position( + -73.98500, + 40.7610 + ) + ) + ) + ) + theatersCollection.insertOne(duplicateTheater) + assertEquals("EXCEPTION", executeCreateUniqueIndex()) + } + + @Test + fun clusteredIndexesTest() = runBlocking { + var vendorsCollection = database.getCollection("vendors") + vendorsCollection.drop() + // :snippet-start: clustered-indexes + val clusteredIndexOptions = ClusteredIndexOptions(Document("_id", 1), true) + val createCollectionOptions = CreateCollectionOptions().clusteredIndexOptions(clusteredIndexOptions) + + database.createCollection("vendors", createCollectionOptions) + // :snippet-end: + vendorsCollection = database.getCollection("vendors") + val indexes = vendorsCollection.listIndexes().toList() + println("INDEXES:: $indexes") + val clusteredIndex = indexes.find { it.getString("name") == "_id_" } + assertNotNull(clusteredIndex) + assertEquals(Document("_id", 1), clusteredIndex["key"]) + } + + @Test + fun dropIndexWithSpecificationDocumentTest() = runBlocking { + moviesCollection.createIndex(Indexes.ascending(Movie::title.name)) + // :snippet-start: drop-index-with-specification-document + moviesCollection.dropIndex(Indexes.ascending(Movie::title.name)); + // :snippet-end: + val indexes = moviesCollection.listIndexes().toList() + assertFalse(indexes.any { it.getString("name") == "title_1" }) + } + + @Test + fun dropIndexWithNameTest() = runBlocking { + moviesCollection.createIndex(Indexes.text("title"), IndexOptions().name("title_text")) + // :snippet-start: list-indexes + val indexes = moviesCollection.listIndexes() + + indexes.collect { println(it.toJson()) } + // :snippet-end: + // :snippet-start: drop-index-with-name + moviesCollection.dropIndex("title_text") + // :snippet-end: + val indexesAfterDrop = moviesCollection.listIndexes().toList() + assertFalse(indexesAfterDrop.any { it.getString("name") == "title_text" }) + } + + @Test + fun dropAllIndexesTest() = runBlocking { + moviesCollection.createIndex(Indexes.ascending("title")) + moviesCollection.createIndex(Indexes.ascending("year")) + // :snippet-start: drop-all-indexes + moviesCollection.dropIndexes() + // :snippet-end: + // :snippet-start: drop-all-indexes-wildcard + moviesCollection.dropIndex("*") + // :snippet-end: + val indexes = moviesCollection.listIndexes().toList() + assertEquals(1, indexes.size) + assertEquals("_id_", indexes.first().getString("name")) + } +} +// :replace-end: diff --git a/source/fundamentals/indexes.txt b/source/fundamentals/indexes.txt index 6558f799..30b5b770 100644 --- a/source/fundamentals/indexes.txt +++ b/source/fundamentals/indexes.txt @@ -4,6 +4,13 @@ Indexes ======= +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: code example, optimization, atlas search + .. contents:: On this page :local: :backlinks: none @@ -13,25 +20,28 @@ Indexes Overview -------- -In this guide, you can learn how to use **indexes** with the MongoDB Kotlin driver. +In this guide, you can learn how to create and manage **indexes** by +using the {+driver-long+}. Indexes support the efficient execution of queries in MongoDB. Without indexes, MongoDB must scan *every* document in a collection (a **collection scan**) to find the documents that match each query. These collection scans are slow and can negatively affect the performance of your application. If an appropriate index exists for a query, MongoDB can use the -index to limit the number of documents it must inspect. +index to limit the documents it must inspect. -Indexes also: +Indexes also have the following benefits: - Allow efficient sorting - Enable special capabilities like :ref:`geospatial ` search - Allow adding constraints to ensure a field value is :ref:`unique ` -- And :manual:`more ` + +To learn more, see :manual:`Indexes ` in the Server manual. .. tip:: - Indexes are also used by update operations when finding the documents to update, delete operations when finding the - documents to delete, and by :manual:`certain stages ` in - the aggregation pipeline. + Update operations use indexes when finding documents to update, and + delete operations use indexes when finding documents to delete. + :manual:`Certain stages ` in + the aggregation pipeline also use indexes to improve performance. Query Coverage and Performance ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -39,9 +49,9 @@ Query Coverage and Performance When you execute a query against MongoDB, your command can include various elements: - Query criteria that specify fields and values you are looking for -- Options that affect the query's execution (e.g. read concern) -- Projection criteria to specify the fields MongoDB should return (optional) -- Sort criteria to specify the order documents will be returned from MongoDB (optional) +- Options that affect the query's execution, such as read concern +- Projection criteria to specify the fields MongoDB returns (optional) +- Sort criteria to specify the order of documents returned from MongoDB (optional) When all the fields specified in the query, projection, and sort are in the same index, MongoDB returns results directly from the index, also called a **covered query**. @@ -57,24 +67,26 @@ from the index, also called a **covered query**. name_1_age_-1 - MongoDB would use this index when you sort your data by either: + MongoDB uses this index when you sort your data by either: - ``name`` ascending, ``age`` descending - ``name`` descending, ``age`` ascending Specifying a sort order of ``name`` and :guilabel:`age` ascending or :guilabel:`name` and ``age`` - descending would require an in-memory sort. + descending requires an in-memory sort. -For additional information on how to ensure your index covers your query criteria and projection, see the MongoDB manual +For more information on how to ensure your index covers your query criteria and projection, see the Server manual articles on :manual:`query coverage `. Operational Considerations ~~~~~~~~~~~~~~~~~~~~~~~~~~ -To improve query performance, build indexes on fields that appear often in your application's queries and operations -that return sorted results. Each index that you add consumes disk space and memory when active so you should track index -memory and disk usage for capacity planning. In addition, when a write operation updates an indexed field, MongoDB also -has to update the related index. +To improve query performance, build indexes on fields that appear often in +your application's queries and operations that return sorted results. Each +index that you add consumes disk space and memory when active, so we recommend +that you track index memory and disk usage for capacity planning. In addition, +when a write operation updates an indexed field, MongoDB updates the related +index. Since MongoDB supports dynamic schemas, applications can query against fields whose names cannot be known in advance or are arbitrary. MongoDB 4.2 introduced :manual:`wildcard indexes ` to help support these queries. @@ -87,13 +99,13 @@ server documentation on :manual:`Indexing Strategies ` an Index Types ----------- -MongoDB supports a number of different index types to support querying your data. The following sections describe the +MongoDB supports several different index types to support querying your data. The following sections describe the most common index types and provide sample code for creating each index type. For a full list of index types, see -:manual:`Indexes `. +:manual:`Indexes ` in the Server manual. .. tip:: - The MongoDB Kotlin driver provides the `Indexes <{+api+}/apidocs/mongodb-driver-core/com/mongodb/client/model/Indexes.html>`__ class that + The {+driver-short+} provides the `Indexes <{+api+}/apidocs/mongodb-driver-core/com/mongodb/client/model/Indexes.html>`__ class that includes static factory methods to create index specification documents for different MongoDB Index key types. The following examples use the @@ -131,7 +143,8 @@ The following example creates an index in ascending order on the ``title`` field Index created: title_1 -The following is an example of a query that would be covered by the index created in the preceding code snippet: +The following is an example of a query that is covered by the index +created in the preceding code snippet: .. literalinclude:: /examples/generated/IndexesTest.snippet.single-index-query.kt :language: kotlin @@ -160,7 +173,8 @@ The following example creates a compound index on the ``type`` and ``rated`` fie Index created: type_1_rated_1 -The following is an example of a query that would be covered by the index created in the preceding code snippet: +The following is an example of a query that is covered by the index +created in the preceding code snippet: .. literalinclude:: /examples/generated/IndexesTest.snippet.compound-index-query.kt :language: kotlin @@ -186,14 +200,99 @@ Strings), and ``title`` fields: Index created: rated_1_genres_1_title_1 -The following is an example of a query that would be covered by the index created in the preceding code snippet: +The following is an example of a query that is covered by the index +created in the preceding code snippet: .. literalinclude:: /examples/generated/IndexesTest.snippet.multikey-index-query.kt :language: kotlin -Multikey indexes behave differently from non-multikey indexes in terms of query coverage, index bound computation, and -sort behavior. For a full explanation of multikey indexes, including a discussion of their behavior and limitations, -refer to the :manual:`Multikey Indexes page ` in the MongoDB manual. +Multikey indexes behave differently from other indexes in terms of query coverage, index bound computation, and +sort behavior. To learn more about multikey indexes, including a discussion of their behavior and limitations, +see the :manual:`Multikey Indexes page ` in the MongoDB manual. + +.. _kotlin-search-indexes: + +Atlas Search Indexes +~~~~~~~~~~~~~~~~~~~~ + +The Atlas Search feature enables you to perform full-text searches on +collections hosted on MongoDB Atlas. The indexes specify the behavior of +the search and which fields to index. + +To learn more about MongoDB Atlas Search, see the +:atlas:`Atlas Search Indexes ` +documentation. + +You can call the following methods on a collection to manage your Atlas Search +indexes: + +- ``createSearchIndex()`` +- ``createSearchIndexes()`` +- ``listSearchIndexes()`` +- ``updateSearchIndex()`` +- ``dropSearchIndex()`` + +.. note:: + + The Atlas Search Index management methods run asynchronously. The + driver methods can return before confirming that they ran + successfully. To determine the current status of the indexes, call the + ``listSearchIndexes()`` method. + +The following sections provide code examples that demonstrate how to use +each of the preceding methods. + +Create a Search Index ++++++++++++++++++++++ + +https://mongodb.github.io/mongo-java-driver/4.11 + +You can use the `createSearchIndex() <{+api+}/apidocs/mongodb-driver-kotlin-coroutine/mongodb-driver-kotlin-coroutine/com.mongodb.kotlin.client.coroutine/-mongo-collection/create-search-index.html>`__ +and the +`createSearchIndexes() <{+api+}/apidocs/mongodb-driver-kotlin-coroutine/mongodb-driver-kotlin-coroutine/com.mongodb.kotlin.client.coroutine/-mongo-collection/create-search-indexes.html>`__ +methods to create Atlas Search indexes. + +The following code example shows how to create a single index: + + + +The following code example shows how to create multiple indexes: + + + +List Search Indexes ++++++++++++++++++++ + +You can use the +`listSearchIndexes() <{+api+}/apidocs/mongodb-driver-sync/com/mongodb/client/MongoCollection.html#listSearchIndexes()>`__ +method to return the Atlas Search indexes of a collection. + +The following code example shows how to print a list of the search indexes of +a collection: + + + +Update a Search Index ++++++++++++++++++++++ + +You can use the +`updateSearchIndex() <{+api+}/apidocs/mongodb-driver-sync/com/mongodb/client/MongoCollection.html#updateSearchIndex(java.lang.String,org.bson.conversions.Bson)>`__ +method to update an Atlas Search index. + +The following code shows how to update a search index: + + + +Drop a Search Index ++++++++++++++++++++ + +You can use the +`dropSearchIndex() <{+api+}/apidocs/mongodb-driver-sync/com/mongodb/client/MongoCollection.html#dropSearchIndex(java.lang.String)>`__ +method to remove an Atlas Search index. + +The following code shows how to delete a search index from a collection: + + .. _text-indexes: @@ -206,9 +305,10 @@ language as an option when creating the index. .. tip:: - Text indexes differ from the more powerful - :atlas:`Atlas full text search indexes `. - Atlas users should use Atlas Search. + MongoDB offers an improved full-text search solution, + :atlas:`Atlas Search `. To learn more about Atlas Search + indexes and how to use them, see the :ref:`kotlin-search-indexes` section of this + guide. Single Field ++++++++++++ @@ -225,7 +325,8 @@ The following example creates a text index on the ``plot`` field: Index created: plot_text -The following is an example of a query that would use the index created in the preceding code snippet. Note that the ``sort`` is +The following is an example of a query that is covered by the index +created in the preceding code snippet. Note that the ``sort`` is omitted because text indexes do not contain sort order. .. literalinclude:: /examples/generated/IndexesTest.snippet.text-index-query.kt @@ -235,7 +336,7 @@ Multiple Fields +++++++++++++++ A collection can only contain one text index. If you want to create a -text index for multiple text fields, you need to create a compound +text index for multiple text fields, you must create a compound index. A text search runs on all the text fields within the compound index. @@ -307,8 +408,8 @@ The following example creates a ``2dsphere`` index on the ``location.geo`` field Index created: location.geo_2dsphere -The following is an example of a geospatial query using the index created -in the preceding code snippet: +The following is an example of a geospatial query that is covered by the index +created in the preceding code snippet: .. literalinclude:: /examples/generated/IndexesTest.snippet.geospatial-index-query.kt :language: kotlin @@ -340,8 +441,9 @@ The following example creates a unique, descending index on the ``theaterId`` fi .. important:: - If you perform a write operation that stores a duplicate value that violates the unique index, the MongoDB - Kotlin driver will raise a ``DuplicateKeyException``, and MongoDB will throw an error resembling the following: + If you perform a write operation that stores a duplicate value that + violates the unique index, the driver raises a ``DuplicateKeyException``, + and MongoDB throws an error resembling the following: .. code-block:: none :copyable: false @@ -411,7 +513,7 @@ Remove an Index Using a Name Field Pass the ``name`` field of the index to the ``dropIndex()`` method to remove an index from a collection. -If you need to find the name of your index, use the ``listIndexes()`` +If you must find the name of your index, use the ``listIndexes()`` method to see the value of the ``name`` fields in your indexes. The following snippet retrieves and prints all the indexes in a