From f4740931b54b389b2dc44c65a5380ae69a83b153 Mon Sep 17 00:00:00 2001 From: rustagir Date: Thu, 11 Jan 2024 08:55:49 -0500 Subject: [PATCH 1/5] 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 From fba81379c03cddd27dd3cbfdcbc190b546f605b6 Mon Sep 17 00:00:00 2001 From: rustagir Date: Fri, 12 Jan 2024 13:38:45 -0500 Subject: [PATCH 2/5] DOCSP-33429: atlas search idx mgmt --- examples/src/test/kotlin/SearchIndexesTest.kt | 462 ++---------------- ...chIndexesTest.snippet.drop-search-index.kt | 1 + ...IndexesTest.snippet.list-search-indexes.kt | 1 + ...sTest.snippet.multi-search-index-create.kt | 15 + ...Test.snippet.single-search-index-create.kt | 6 + ...dexesTest.snippet.update-search-indexes.kt | 14 + source/fundamentals/indexes.txt | 25 +- 7 files changed, 102 insertions(+), 422 deletions(-) create mode 100644 source/examples/generated/SearchIndexesTest.snippet.drop-search-index.kt create mode 100644 source/examples/generated/SearchIndexesTest.snippet.list-search-indexes.kt create mode 100644 source/examples/generated/SearchIndexesTest.snippet.multi-search-index-create.kt create mode 100644 source/examples/generated/SearchIndexesTest.snippet.single-search-index-create.kt create mode 100644 source/examples/generated/SearchIndexesTest.snippet.update-search-indexes.kt diff --git a/examples/src/test/kotlin/SearchIndexesTest.kt b/examples/src/test/kotlin/SearchIndexesTest.kt index 5d4e6a26..9a5a11e0 100644 --- a/examples/src/test/kotlin/SearchIndexesTest.kt +++ b/examples/src/test/kotlin/SearchIndexesTest.kt @@ -1,29 +1,16 @@ -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.client.model.SearchIndexModel 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.Ignore import kotlin.test.assertFalse -import kotlin.test.assertNotNull -import kotlin.test.assertTrue // :replace-start: { // "terms": { @@ -33,453 +20,100 @@ import kotlin.test.assertTrue @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 - ) - ) - ) - ) - ) - ) - } - } + val moviesCollection = database.getCollection("movies") @AfterAll @JvmStatic fun afterAll() { runBlocking { moviesCollection.drop() - theatersCollection.drop() } mongoClient.close() } } - @AfterEach - fun afterEach() { - runBlocking { - moviesCollection.dropIndexes() - theatersCollection.dropIndexes() - } - } - - + @Ignore @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() + fun singleSearchIndexTest() = runBlocking { + // :snippet-start: single-search-index-create + val index = Document( + "mappings", + Document("dynamic", true) ) - - 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)) - + val resultCreateIndex = moviesCollection.createSearchIndex("myIndex", index) 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" }) + assertEquals("myIndex", resultCreateIndex) } + @Ignore @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() + fun multipleSearchIndexTest() = runBlocking { + // :snippet-start: multi-search-index-create + val indexOne = SearchIndexModel( + "myIndex1", + Document("analyzer", "lucene.standard").append( + "mappings", Document("dynamic", true) + ) ) - 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: + val indexTwo = SearchIndexModel( + "myIndex2", + Document("analyzer", "lucene.simple").append( + "mappings", Document("dynamic", true) ) - - } - // 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) } + val resultCreateIndexes = moviesCollection.createSearchIndexes(listOf(indexOne, indexTwo)) // :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()) + assertEquals(listOf("myIndex1", "myIndex2"), resultCreateIndexes.toList()) } + @Ignore @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") + fun listSearchIndexTest() = runBlocking { + // :snippet-start: list-search-indexes + val searchIndexesList = moviesCollection.listSearchIndexes().toList() // :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()) + assertFalse(searchIndexesList.isEmpty()) } + @Ignore @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 + fun updateSearchIndexTest() = runBlocking { + // :snippet-start: update-search-indexes + moviesCollection.updateSearchIndex( + "myIndex", + Document("analyzer", "lucene.simple").append( + "mappings", + Document("dynamic", false) + .append( + "fields", + Document( + "title", + Document("type", "string") + ) ) - 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"]) } + @Ignore @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)); + fun dropSearchIndexTest() = runBlocking { + // :snippet-start: drop-search-index + moviesCollection.dropSearchIndex("myIndex"); // :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/examples/generated/SearchIndexesTest.snippet.drop-search-index.kt b/source/examples/generated/SearchIndexesTest.snippet.drop-search-index.kt new file mode 100644 index 00000000..adf0f4e2 --- /dev/null +++ b/source/examples/generated/SearchIndexesTest.snippet.drop-search-index.kt @@ -0,0 +1 @@ +moviesCollection.dropSearchIndex("myIndex"); diff --git a/source/examples/generated/SearchIndexesTest.snippet.list-search-indexes.kt b/source/examples/generated/SearchIndexesTest.snippet.list-search-indexes.kt new file mode 100644 index 00000000..49e3f526 --- /dev/null +++ b/source/examples/generated/SearchIndexesTest.snippet.list-search-indexes.kt @@ -0,0 +1 @@ +val searchIndexesList = moviesCollection.listSearchIndexes().toList() diff --git a/source/examples/generated/SearchIndexesTest.snippet.multi-search-index-create.kt b/source/examples/generated/SearchIndexesTest.snippet.multi-search-index-create.kt new file mode 100644 index 00000000..2042ba13 --- /dev/null +++ b/source/examples/generated/SearchIndexesTest.snippet.multi-search-index-create.kt @@ -0,0 +1,15 @@ +val indexOne = SearchIndexModel( + "myIndex1", + Document("analyzer", "lucene.standard").append( + "mappings", Document("dynamic", true) + ) +) + +val indexTwo = SearchIndexModel( + "myIndex2", + Document("analyzer", "lucene.simple").append( + "mappings", Document("dynamic", true) + ) +) + +val resultCreateIndexes = moviesCollection.createSearchIndexes(listOf(indexOne, indexTwo)) diff --git a/source/examples/generated/SearchIndexesTest.snippet.single-search-index-create.kt b/source/examples/generated/SearchIndexesTest.snippet.single-search-index-create.kt new file mode 100644 index 00000000..08cefa14 --- /dev/null +++ b/source/examples/generated/SearchIndexesTest.snippet.single-search-index-create.kt @@ -0,0 +1,6 @@ +val index = Document( + "mappings", + Document("dynamic", true) +) +val resultCreateIndex = moviesCollection.createSearchIndex("myIndex", index) +println("Index created: $resultCreateIndex") diff --git a/source/examples/generated/SearchIndexesTest.snippet.update-search-indexes.kt b/source/examples/generated/SearchIndexesTest.snippet.update-search-indexes.kt new file mode 100644 index 00000000..a5ee0464 --- /dev/null +++ b/source/examples/generated/SearchIndexesTest.snippet.update-search-indexes.kt @@ -0,0 +1,14 @@ +moviesCollection.updateSearchIndex( + "myIndex", + Document("analyzer", "lucene.simple").append( + "mappings", + Document("dynamic", false) + .append( + "fields", + Document( + "title", + Document("type", "string") + ) + ) + ) +) diff --git a/source/fundamentals/indexes.txt b/source/fundamentals/indexes.txt index 30b5b770..19c53f75 100644 --- a/source/fundamentals/indexes.txt +++ b/source/fundamentals/indexes.txt @@ -254,45 +254,50 @@ methods to create Atlas Search indexes. The following code example shows how to create a single index: - +.. literalinclude:: /examples/generated/SearchIndexesTest.snippet.single-search-index-create.kt + :language: kotlin The following code example shows how to create multiple indexes: - +.. literalinclude:: /examples/generated/SearchIndexesTest.snippet.multi-search-index-create.kt + :language: kotlin List Search Indexes +++++++++++++++++++ You can use the -`listSearchIndexes() <{+api+}/apidocs/mongodb-driver-sync/com/mongodb/client/MongoCollection.html#listSearchIndexes()>`__ +`listSearchIndexes() <{+api+}/apidocs/mongodb-driver-kotlin-coroutine/mongodb-driver-kotlin-coroutine/com.mongodb.kotlin.client.coroutine/-mongo-collection/list-search-indexes.html>`__ 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: - +.. literalinclude:: /examples/generated/SearchIndexesTest.snippet.list-search-indexes.kt + :language: kotlin 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)>`__ +`updateSearchIndex() <{+api+}/apidocs/mongodb-driver-kotlin-coroutine/mongodb-driver-kotlin-coroutine/com.mongodb.kotlin.client.coroutine/-mongo-collection/update-search-index.html>`__ method to update an Atlas Search index. The following code shows how to update a search index: - +.. literalinclude:: /examples/generated/SearchIndexesTest.snippet.update-search-indexes.kt + :language: kotlin Drop a Search Index +++++++++++++++++++ You can use the -`dropSearchIndex() <{+api+}/apidocs/mongodb-driver-sync/com/mongodb/client/MongoCollection.html#dropSearchIndex(java.lang.String)>`__ +`dropSearchIndex() <{+api+}/apidocs/mongodb-driver-kotlin-coroutine/mongodb-driver-kotlin-coroutine/com.mongodb.kotlin.client.coroutine/-mongo-collection/drop-search-index.html>`__ method to remove an Atlas Search index. The following code shows how to delete a search index from a collection: - +.. literalinclude:: /examples/generated/SearchIndexesTest.snippet.drop-search-index.kt + :language: kotlin .. _text-indexes: @@ -408,6 +413,10 @@ The following example creates a ``2dsphere`` index on the ``location.geo`` field Index created: location.geo_2dsphere +.. important:: + + Attempting to create a geospatial index on a field that is covered by a geospatial index results in an error. + The following is an example of a geospatial query that is covered by the index created in the preceding code snippet: From 4ab88428adb3a25062f135f4eefdd546ffb995c1 Mon Sep 17 00:00:00 2001 From: rustagir Date: Fri, 12 Jan 2024 13:43:03 -0500 Subject: [PATCH 3/5] fixes --- examples/src/test/kotlin/SearchIndexesTest.kt | 6 +++--- .../SearchIndexesTest.snippet.multi-search-index-create.kt | 3 ++- .../SearchIndexesTest.snippet.single-search-index-create.kt | 1 - source/fundamentals/indexes.txt | 2 -- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/examples/src/test/kotlin/SearchIndexesTest.kt b/examples/src/test/kotlin/SearchIndexesTest.kt index 9a5a11e0..c7d4fcf3 100644 --- a/examples/src/test/kotlin/SearchIndexesTest.kt +++ b/examples/src/test/kotlin/SearchIndexesTest.kt @@ -47,9 +47,8 @@ class SearchIndexesTest { Document("dynamic", true) ) val resultCreateIndex = moviesCollection.createSearchIndex("myIndex", index) - println("Index created: $resultCreateIndex") // :snippet-end: - + println("Index created: $resultCreateIndex") assertEquals("myIndex", resultCreateIndex) } @@ -71,7 +70,8 @@ class SearchIndexesTest { ) ) - val resultCreateIndexes = moviesCollection.createSearchIndexes(listOf(indexOne, indexTwo)) + val resultCreateIndexes = moviesCollection + .createSearchIndexes(listOf(indexOne, indexTwo)) // :snippet-end: assertEquals(listOf("myIndex1", "myIndex2"), resultCreateIndexes.toList()) } diff --git a/source/examples/generated/SearchIndexesTest.snippet.multi-search-index-create.kt b/source/examples/generated/SearchIndexesTest.snippet.multi-search-index-create.kt index 2042ba13..238dcc8e 100644 --- a/source/examples/generated/SearchIndexesTest.snippet.multi-search-index-create.kt +++ b/source/examples/generated/SearchIndexesTest.snippet.multi-search-index-create.kt @@ -12,4 +12,5 @@ val indexTwo = SearchIndexModel( ) ) -val resultCreateIndexes = moviesCollection.createSearchIndexes(listOf(indexOne, indexTwo)) +val resultCreateIndexes = moviesCollection + .createSearchIndexes(listOf(indexOne, indexTwo)) diff --git a/source/examples/generated/SearchIndexesTest.snippet.single-search-index-create.kt b/source/examples/generated/SearchIndexesTest.snippet.single-search-index-create.kt index 08cefa14..1266fd74 100644 --- a/source/examples/generated/SearchIndexesTest.snippet.single-search-index-create.kt +++ b/source/examples/generated/SearchIndexesTest.snippet.single-search-index-create.kt @@ -3,4 +3,3 @@ val index = Document( Document("dynamic", true) ) val resultCreateIndex = moviesCollection.createSearchIndex("myIndex", index) -println("Index created: $resultCreateIndex") diff --git a/source/fundamentals/indexes.txt b/source/fundamentals/indexes.txt index 19c53f75..0a4f566a 100644 --- a/source/fundamentals/indexes.txt +++ b/source/fundamentals/indexes.txt @@ -245,8 +245,6 @@ 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>`__ From 4e448c0c0cf132a7ef0c2bd871c06bd2f78d2510 Mon Sep 17 00:00:00 2001 From: rustagir Date: Tue, 16 Jan 2024 10:38:49 -0500 Subject: [PATCH 4/5] MW PR fixes 1 --- source/fundamentals/indexes.txt | 80 +++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 33 deletions(-) diff --git a/source/fundamentals/indexes.txt b/source/fundamentals/indexes.txt index 0a4f566a..7197b699 100644 --- a/source/fundamentals/indexes.txt +++ b/source/fundamentals/indexes.txt @@ -23,16 +23,18 @@ Overview 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 documents it must inspect. +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 documents that the query must inspect. 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 ` +- Indexes allow efficient sorting. +- Indexes enable special capabilities such as :ref:`geospatial queries `. +- Indexes allow the creation of constraints to ensure a field value is :ref:`unique `. To learn more, see :manual:`Indexes ` in the Server manual. @@ -49,7 +51,7 @@ 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, such as read concern +- Options that affect the query's execution, such as the read concern - Projection criteria to specify the fields MongoDB returns (optional) - Sort criteria to specify the order of documents returned from MongoDB (optional) @@ -67,7 +69,8 @@ from the index, also called a **covered query**. name_1_age_-1 - MongoDB uses this index when you sort your data by either: + MongoDB uses this index when you sort your data in either of the + following ways: - ``name`` ascending, ``age`` descending - ``name`` descending, ``age`` ascending @@ -81,12 +84,17 @@ 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 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. +The following guidelines describe how you can optimize the way +application uses indexes: + +- To improve query performance, build indexes on fields that appear often in + your application's queries and operations that return sorted results. + +- Track index memory and disk usage for capacity planning, because each + index that you add consumes disk space and memory when active. + +- Avoid adding indexes that you infrequently use. Note that 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. @@ -105,8 +113,11 @@ most common index types and provide sample code for creating each index type. Fo .. tip:: - 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 {+driver-short+} provides the `Indexes + <{+api+}/apidocs/mongodb-driver-core/com/mongodb/client/model/Indexes.html>`__ + class to create and manage indexes. This class includes static + factory methods to create index specification documents for different + MongoDB Index key types. The following examples use the `createIndex() <{+api+}/apidocs/mongodb-driver-kotlin-coroutine/mongodb-driver-kotlin-coroutine/com.mongodb.kotlin.client.coroutine/-mongo-collection/create-index.html>`__ @@ -206,9 +217,9 @@ created in the preceding code snippet: .. literalinclude:: /examples/generated/IndexesTest.snippet.multikey-index-query.kt :language: kotlin -Multikey indexes behave differently from other indexes in terms of query coverage, index bound computation, and +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. +see :manual:`Multikey Indexes ` in the Server manual. .. _kotlin-search-indexes: @@ -216,8 +227,8 @@ 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. +collections hosted on MongoDB Atlas. The indexes specify how you can +perform full-text searches on specific fields. To learn more about MongoDB Atlas Search, see the :atlas:`Atlas Search Indexes ` @@ -234,7 +245,7 @@ indexes: .. note:: - The Atlas Search Index management methods run asynchronously. The + 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. @@ -246,9 +257,8 @@ Create a Search Index +++++++++++++++++++++ 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. +and `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 on a collection. The following code example shows how to create a single index: @@ -265,7 +275,7 @@ List Search Indexes You can use the `listSearchIndexes() <{+api+}/apidocs/mongodb-driver-kotlin-coroutine/mongodb-driver-kotlin-coroutine/com.mongodb.kotlin.client.coroutine/-mongo-collection/list-search-indexes.html>`__ -method to return the Atlas Search indexes of a collection. +method to return a list of the Atlas Search indexes on a collection. The following code example shows how to print a list of the search indexes of a collection: @@ -368,10 +378,12 @@ Geospatial Indexes MongoDB supports queries of geospatial coordinate data using **2dsphere indexes**. With a ``2dsphere`` index, you can query the geospatial data for inclusion, intersection, and proximity. For more information on querying geospatial data, see -:manual:`Geospatial Queries `. +:manual:`Geospatial Queries ` in the Server manual. -To create a ``2dsphere`` index, you must specify a field that contains only **GeoJSON objects**. For more details on this -type, see the MongoDB server manual page on :manual:`GeoJSON objects `. +To create a ``2dsphere`` index, you must specify a field that contains +only **GeoJSON objects**. To learn more about this +type, see the :manual:`GeoJSON objects reference ` +in the Server manual. The ``location.geo`` field in the following sample document from the ``theaters`` collection in the ``sample_mflix`` database is a GeoJSON Point object that describes the coordinates of the theater: @@ -413,7 +425,8 @@ The following example creates a ``2dsphere`` index on the ``location.geo`` field .. important:: - Attempting to create a geospatial index on a field that is covered by a geospatial index results in an error. + Attempting to create a geospatial index on a field that is already + covered by a geospatial index results in an error. The following is an example of a geospatial query that is covered by the index created in the preceding code snippet: @@ -421,9 +434,10 @@ created in the preceding code snippet: .. literalinclude:: /examples/generated/IndexesTest.snippet.geospatial-index-query.kt :language: kotlin -MongoDB also supports ``2d`` indexes for calculating distances on a Euclidean plane and for working with the "legacy -coordinate pairs" syntax used in MongoDB 2.2 and earlier. See the :manual:`Geospatial Queries page ` -in the MongoDB server manual for more information. +MongoDB also supports ``2d`` indexes for calculating distances on a +Euclidean plane and for working with the "legacy coordinate pairs" +syntax used in MongoDB 2.2 and earlier. To learn more, see +:manual:`Geospatial Queries ` in the Server manual. Unique Indexes ~~~~~~~~~~~~~~ From e2f402ce975a528c5dcffc75ae045598a45a50df Mon Sep 17 00:00:00 2001 From: rustagir Date: Tue, 16 Jan 2024 13:31:14 -0500 Subject: [PATCH 5/5] MW PR Small fixes --- source/fundamentals/indexes.txt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/source/fundamentals/indexes.txt b/source/fundamentals/indexes.txt index 7197b699..305384c4 100644 --- a/source/fundamentals/indexes.txt +++ b/source/fundamentals/indexes.txt @@ -85,7 +85,7 @@ Operational Considerations ~~~~~~~~~~~~~~~~~~~~~~~~~~ The following guidelines describe how you can optimize the way -application uses indexes: +your application uses indexes: - To improve query performance, build indexes on fields that appear often in your application's queries and operations that return sorted results. @@ -117,7 +117,7 @@ most common index types and provide sample code for creating each index type. Fo <{+api+}/apidocs/mongodb-driver-core/com/mongodb/client/model/Indexes.html>`__ class to create and manage indexes. This class includes static factory methods to create index specification documents for different - MongoDB Index key types. + MongoDB index key types. The following examples use the `createIndex() <{+api+}/apidocs/mongodb-driver-kotlin-coroutine/mongodb-driver-kotlin-coroutine/com.mongodb.kotlin.client.coroutine/-mongo-collection/create-index.html>`__ @@ -277,7 +277,7 @@ You can use the `listSearchIndexes() <{+api+}/apidocs/mongodb-driver-kotlin-coroutine/mongodb-driver-kotlin-coroutine/com.mongodb.kotlin.client.coroutine/-mongo-collection/list-search-indexes.html>`__ method to return a list of the Atlas Search indexes on a collection. -The following code example shows how to print a list of the search indexes of +The following code example shows how to print a list of the search indexes on a collection: .. literalinclude:: /examples/generated/SearchIndexesTest.snippet.list-search-indexes.kt @@ -381,9 +381,8 @@ the geospatial data for inclusion, intersection, and proximity. For more informa :manual:`Geospatial Queries ` in the Server manual. To create a ``2dsphere`` index, you must specify a field that contains -only **GeoJSON objects**. To learn more about this -type, see the :manual:`GeoJSON objects reference ` -in the Server manual. +only **GeoJSON objects**. To learn more about this type, see +:manual:`GeoJSON objects ` in the Server manual. The ``location.geo`` field in the following sample document from the ``theaters`` collection in the ``sample_mflix`` database is a GeoJSON Point object that describes the coordinates of the theater: