diff --git a/examples/src/test/kotlin/AggregatesBuilderTest.kt b/examples/src/test/kotlin/AggregatesBuilderTest.kt index b07258b2..94cabb6f 100644 --- a/examples/src/test/kotlin/AggregatesBuilderTest.kt +++ b/examples/src/test/kotlin/AggregatesBuilderTest.kt @@ -57,7 +57,8 @@ class AggregatesBuilderTest { val rated: String, val plot: String, val runtime: Int, - val imdb: IMDB + val imdb: IMDB, + val fullplot: String? = "No full plot", ){ data class IMDB( val rating: Double @@ -951,6 +952,40 @@ class AggregatesBuilderTest { assertEquals("Back to the Future", results.first().title) } + /* NOTE: Test is not run by default. FTS requires the creation of a text index on the collection before running + (see note at top of file for additional setup requirements for FTS). + */ + @Ignore + fun atlasSearchOperatorTest() = runBlocking { + + // :snippet-start: atlas-search-pipeline + data class Results(val title: String, val year: Int, val genres: List) + + val searchStage = Aggregates.search( + SearchOperator.compound() + .filter( + listOf( + SearchOperator.`in`(SearchPath.fieldPath(Movie::genres.name), listOf("Comedy")), + SearchOperator.phrase(SearchPath.fieldPath(Movie::fullplot.name), "new york"), + SearchOperator.numberRange(SearchPath.fieldPath(Movie::year.name)).gtLt(1950, 2000), + SearchOperator.wildcard(SearchPath.fieldPath(Movie::title.name), "Love *") + ) + ) + ) + + val projectStage = Aggregates.project( + Projections.include(Movie::title.name, Movie::year.name, Movie::genres.name)) + + val pipeline = listOf(searchStage, projectStage) + val resultsFlow = ftsCollection.aggregate(pipeline) + + resultsFlow.collect { println(it) } + // :snippet-end: + + val result = resultsFlow.toList() + assertEquals(2, result.size) + } + /* NOTE: Test is not run by default. FTS requires the creation of a text index on the collection before running (see note at top of file for additional setup requirements for FTS). */ diff --git a/examples/src/test/kotlin/AggregationTest.kt b/examples/src/test/kotlin/AggregationTest.kt index 3b7a54d9..7d366b26 100644 --- a/examples/src/test/kotlin/AggregationTest.kt +++ b/examples/src/test/kotlin/AggregationTest.kt @@ -4,17 +4,21 @@ import com.mongodb.client.model.Accumulators import com.mongodb.client.model.Aggregates import com.mongodb.client.model.Filters import com.mongodb.client.model.Projections +import com.mongodb.client.model.search.SearchOperator +import com.mongodb.client.model.search.SearchPath.fieldPath 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.bson.codecs.pojo.annotations.BsonId +import org.bson.conversions.Bson import org.bson.json.JsonWriterSettings import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import kotlin.test.assertEquals +import kotlin.test.Ignore class AggregationTest { @@ -44,16 +48,102 @@ class AggregationTest { fun beforeAll() { runBlocking { val restaurants = listOf( - Restaurant("Sun Bakery Trattoria", Restaurant.Contact("386-555-0189", "SunBakeryTrattoria@example.org", listOf(-74.0056649, 40.7452371)), 4, listOf("Pizza", "Pasta", "Italian", "Coffee", "Sandwiches")), - Restaurant("Blue Bagels Grill", Restaurant.Contact("786-555-0102", "BlueBagelsGrill@example.com", listOf(-73.92506, 40.8275556)), 3, listOf("Bagels", "Cookies", "Sandwiches")), - Restaurant("XYZ Bagels Restaurant", Restaurant.Contact("435-555-0190", "XYZBagelsRestaurant@example.net", listOf(-74.0707363, 40.59321569999999)), 4, listOf("Bagels", "Sandwiches", "Coffee")), - Restaurant("Hot Bakery Cafe", Restaurant.Contact("264-555-0171", "HotBakeryCafe@example.net", listOf(-73.96485799999999, 40.761899)), 4, listOf("Bakery", "Cafe", "Coffee", "Dessert")), - Restaurant("Green Feast Pizzeria", Restaurant.Contact("840-555-0102", "GreenFeastPizzeria@example.com", listOf(-74.1220973, 40.6129407)), 2, listOf("Pizza", "Italian")), - Restaurant("ZZZ Pasta Buffet", Restaurant.Contact("769-555-0152", "ZZZPastaBuffet@example.com", listOf(-73.9446421, 40.7253944)), 0, listOf("Pasta", "Italian", "Buffet", "Cafeteria")), - Restaurant("XYZ Coffee Bar", Restaurant.Contact("644-555-0193", "XYZCoffeeBar@example.net", listOf(-74.0166091, 40.6284767)), 5, listOf("Coffee", "Cafe", "Bakery", "Chocolates")), - Restaurant("456 Steak Restaurant", Restaurant.Contact("990-555-0165", "456SteakRestaurant@example.com", listOf(-73.9365108, 40.8497077)), 0, listOf("Steak", "Seafood")), - Restaurant("456 Cookies Shop", Restaurant.Contact("604-555-0149", "456CookiesShop@example.org", listOf(-73.8850023, 40.7494272)), 4, listOf("Bakery", "Cookies", "Cake", "Coffee")), - Restaurant("XYZ Steak Buffet", Restaurant.Contact("229-555-0197", "XYZSteakBuffet@example.org", listOf(-73.9799932, 40.7660886)), 3, listOf("Steak", "Salad", "Chinese")) + Restaurant( + "Sun Bakery Trattoria", + Restaurant.Contact( + "386-555-0189", + "SunBakeryTrattoria@example.org", + listOf(-74.0056649, 40.7452371) + ), + 4, + listOf("Pizza", "Pasta", "Italian", "Coffee", "Sandwiches") + ), + Restaurant( + "Blue Bagels Grill", + Restaurant.Contact( + "786-555-0102", + "BlueBagelsGrill@example.com", + listOf(-73.92506, 40.8275556) + ), + 3, + listOf("Bagels", "Cookies", "Sandwiches") + ), + Restaurant( + "XYZ Bagels Restaurant", + Restaurant.Contact( + "435-555-0190", + "XYZBagelsRestaurant@example.net", + listOf(-74.0707363, 40.59321569999999) + ), + 4, + listOf("Bagels", "Sandwiches", "Coffee") + ), + Restaurant( + "Hot Bakery Cafe", + Restaurant.Contact( + "264-555-0171", + "HotBakeryCafe@example.net", + listOf(-73.96485799999999, 40.761899) + ), + 4, + listOf("Bakery", "Cafe", "Coffee", "Dessert") + ), + Restaurant( + "Green Feast Pizzeria", + Restaurant.Contact( + "840-555-0102", + "GreenFeastPizzeria@example.com", + listOf(-74.1220973, 40.6129407) + ), + 2, + listOf("Pizza", "Italian") + ), + Restaurant( + "ZZZ Pasta Buffet", + Restaurant.Contact( + "769-555-0152", + "ZZZPastaBuffet@example.com", + listOf(-73.9446421, 40.7253944) + ), + 0, + listOf("Pasta", "Italian", "Buffet", "Cafeteria") + ), + Restaurant( + "XYZ Coffee Bar", + Restaurant.Contact("644-555-0193", "XYZCoffeeBar@example.net", listOf(-74.0166091, 40.6284767)), + 5, + listOf("Coffee", "Cafe", "Bakery", "Chocolates") + ), + Restaurant( + "456 Steak Restaurant", + Restaurant.Contact( + "990-555-0165", + "456SteakRestaurant@example.com", + listOf(-73.9365108, 40.8497077) + ), + 0, + listOf("Steak", "Seafood") + ), + Restaurant( + "456 Cookies Shop", + Restaurant.Contact( + "604-555-0149", + "456CookiesShop@example.org", + listOf(-73.8850023, 40.7494272) + ), + 4, + listOf("Bakery", "Cookies", "Cake", "Coffee") + ), + Restaurant( + "XYZ Steak Buffet", + Restaurant.Contact( + "229-555-0197", + "XYZSteakBuffet@example.org", + listOf(-73.9799932, 40.7660886) + ), + 3, + listOf("Steak", "Salad", "Chinese") + ) ) collection.insertMany(restaurants) } @@ -71,15 +161,17 @@ class AggregationTest { } @Test - fun basicAggregationTest() = runBlocking { + fun basicAggregationTest() = runBlocking { // :snippet-start: basic-aggregation data class Results(@BsonId val id: Int, val count: Int) val resultsFlow = collection.aggregate( listOf( Aggregates.match(Filters.eq(Restaurant::categories.name, "Bakery")), - Aggregates.group("\$${Restaurant::stars.name}", - Accumulators.sum("count", 1)) + Aggregates.group( + "\$${Restaurant::stars.name}", + Accumulators.sum("count", 1) + ) ) ) @@ -124,7 +216,7 @@ class AggregationTest { @Test fun explainAggregationTest() = runBlocking { // :snippet-start: explain-aggregation - data class Results (val name: String, val count: Int) + data class Results(val name: String, val count: Int) val explanation = collection.aggregate( listOf( @@ -143,11 +235,11 @@ class AggregationTest { @Test fun buildDocumentsTipTest() { val method1 = - // :snippet-start: build-documents-tip - Document("\$arrayElemAt", listOf("\$categories", 0)) + // :snippet-start: build-documents-tip + Document("\$arrayElemAt", listOf("\$categories", 0)) // is equivalent to val method2 = // :remove: - Document.parse("{ \$arrayElemAt: ['\$categories', 0] }") + Document.parse("{ \$arrayElemAt: ['\$categories', 0] }") // :snippet-end: // assert to test equivalency assertEquals(method1, method2) diff --git a/source/examples/generated/AggregatesBuilderTest.snippet.atlas-search-pipeline.kt b/source/examples/generated/AggregatesBuilderTest.snippet.atlas-search-pipeline.kt new file mode 100644 index 00000000..9a9352f2 --- /dev/null +++ b/source/examples/generated/AggregatesBuilderTest.snippet.atlas-search-pipeline.kt @@ -0,0 +1,21 @@ +data class Results(val title: String, val year: Int, val genres: List) + +val searchStage = Aggregates.search( + SearchOperator.compound() + .filter( + listOf( + SearchOperator.`in`(SearchPath.fieldPath(Movie::genres.name), listOf("Comedy")), + SearchOperator.phrase(SearchPath.fieldPath(Movie::fullplot.name), "new york"), + SearchOperator.numberRange(SearchPath.fieldPath(Movie::year.name)).gtLt(1950, 2000), + SearchOperator.wildcard(SearchPath.fieldPath(Movie::title.name), "Love *") + ) + ) +) + +val projectStage = Aggregates.project( + Projections.include(Movie::title.name, Movie::year.name, Movie::genres.name)) + +val pipeline = listOf(searchStage, projectStage) +val resultsFlow = ftsCollection.aggregate(pipeline) + +resultsFlow.collect { println(it) } diff --git a/source/examples/generated/AggregatesBuilderTest.snippet.movie-data-class.kt b/source/examples/generated/AggregatesBuilderTest.snippet.movie-data-class.kt index dc131d0e..7bfc0f44 100644 --- a/source/examples/generated/AggregatesBuilderTest.snippet.movie-data-class.kt +++ b/source/examples/generated/AggregatesBuilderTest.snippet.movie-data-class.kt @@ -5,7 +5,8 @@ data class Movie( val rated: String, val plot: String, val runtime: Int, - val imdb: IMDB + val imdb: IMDB, + val fullplot: String? = "No full plot", ){ data class IMDB( val rating: Double diff --git a/source/examples/generated/AggregationTest.snippet.basic-aggregation.kt b/source/examples/generated/AggregationTest.snippet.basic-aggregation.kt index d3b7a52f..51f726de 100644 --- a/source/examples/generated/AggregationTest.snippet.basic-aggregation.kt +++ b/source/examples/generated/AggregationTest.snippet.basic-aggregation.kt @@ -3,8 +3,10 @@ data class Results(@BsonId val id: Int, val count: Int) val resultsFlow = collection.aggregate( listOf( Aggregates.match(Filters.eq(Restaurant::categories.name, "Bakery")), - Aggregates.group("\$${Restaurant::stars.name}", - Accumulators.sum("count", 1)) + Aggregates.group( + "\$${Restaurant::stars.name}", + Accumulators.sum("count", 1) + ) ) ) diff --git a/source/examples/generated/AggregationTest.snippet.build-documents-tip.kt b/source/examples/generated/AggregationTest.snippet.build-documents-tip.kt index bb27fa8a..21c72e26 100644 --- a/source/examples/generated/AggregationTest.snippet.build-documents-tip.kt +++ b/source/examples/generated/AggregationTest.snippet.build-documents-tip.kt @@ -1,3 +1,3 @@ -Document("\$arrayElemAt", listOf("\$categories", 0)) + Document("\$arrayElemAt", listOf("\$categories", 0)) // is equivalent to -Document.parse("{ \$arrayElemAt: ['\$categories', 0] }") + Document.parse("{ \$arrayElemAt: ['\$categories', 0] }") diff --git a/source/examples/generated/AggregationTest.snippet.explain-aggregation.kt b/source/examples/generated/AggregationTest.snippet.explain-aggregation.kt index 7efe45ee..6aaebceb 100644 --- a/source/examples/generated/AggregationTest.snippet.explain-aggregation.kt +++ b/source/examples/generated/AggregationTest.snippet.explain-aggregation.kt @@ -1,4 +1,4 @@ -data class Results (val name: String, val count: Int) +data class Results(val name: String, val count: Int) val explanation = collection.aggregate( listOf( diff --git a/source/fundamentals/aggregation.txt b/source/fundamentals/aggregation.txt index c3d5b45f..d3913e9a 100644 --- a/source/fundamentals/aggregation.txt +++ b/source/fundamentals/aggregation.txt @@ -215,6 +215,9 @@ first element in the ``categories`` field. Results(name=456 Cookies Shop, firstCategory=Bakery) Results(name=XYZ Steak Buffet, firstCategory=Steak) +API Documentation +----------------- + For more information about the methods and classes mentioned in this section, see the following API Documentation: diff --git a/source/fundamentals/builders/aggregates.txt b/source/fundamentals/builders/aggregates.txt index ca5d8913..1413b0bf 100644 --- a/source/fundamentals/builders/aggregates.txt +++ b/source/fundamentals/builders/aggregates.txt @@ -887,6 +887,31 @@ field in the ``movies`` collection for text that contains the word "Future": Learn more about the builders from the `search package API documentation <{+core-api+}/client/model/search/package-summary.html>`__. +.. _kotlin-cr-atlas-search-stage: + +Search Operator Methods +~~~~~~~~~~~~~~~~~~~~~~~ + +.. sharedinclude:: dbx/jvm/atlas-search-operator-helpers.rst + + .. replacement:: as-idx-link + + the :ref:`kotlin-search-indexes` section of the Indexes guide + + .. replacement:: atlas-query-operators-example + + .. io-code-block:: + + .. input:: /examples/generated/AggregatesBuilderTest.snippet.atlas-search-pipeline.kt + :language: kotlin + + .. output:: + :language: console + :visible: false + + Results(title=Love at First Bite, year=1979, genres=[Comedy, Romance]) + Results(title=Love Affair, year=1994, genres=[Comedy, Drama]) + Atlas Search Metadata --------------------- diff --git a/source/whats-new.txt b/source/whats-new.txt index b4b3237f..12874633 100644 --- a/source/whats-new.txt +++ b/source/whats-new.txt @@ -53,6 +53,11 @@ and features: the `SearchOperator <{+core-api+}/client/model/search/SearchOperator.html>`__ interface API documentation + .. replacement:: atlas-query-operators + + the :ref:`kotlin-cr-atlas-search-stage` section of the Aggregates + Builders guide + .. _kotlin-coroutine-version-5.3: What's New in 5.3