diff --git a/examples/src/test/kotlin/FlowTest.kt b/examples/src/test/kotlin/FlowTest.kt new file mode 100644 index 00000000..96ab7649 --- /dev/null +++ b/examples/src/test/kotlin/FlowTest.kt @@ -0,0 +1,120 @@ + + +import com.mongodb.ExplainVerbosity +import com.mongodb.kotlin.client.coroutine.MongoClient +import config.getConfig +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.runBlocking +import org.bson.Document +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.TestInstance +import java.util.* +import kotlin.test.* + + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +internal class FlowTest { + + data class PaintOrder( + val id: Int, + val color: String, + val qty: Int + ) + companion object { + val config = getConfig() + val client = MongoClient.create(config.connectionUri) + val database = client.getDatabase("paint_store") + val collection = database.getCollection("paint_order") + + @BeforeAll + @JvmStatic + fun beforeAll() { + runBlocking { + collection.insertMany(listOf( + PaintOrder(1, "red", 5), + PaintOrder(2, "purple", 8), + PaintOrder(3, "yellow", 0), + PaintOrder(4, "green", 6), + PaintOrder(5, "pink", 0) + )) + } + } + + @AfterAll + @JvmStatic + fun afterAll() { + runBlocking { + collection.drop() + client.close() + } + } + } + + @Test + fun firstOrNullTest() = runBlocking { + // :snippet-start: firstOrNull + val resultsFlow = collection.find() + val firstResultOrNull = resultsFlow.firstOrNull() + // :snippet-end: + assertNotNull(firstResultOrNull) + } + @Test + fun firstTest() = runBlocking { + var isReached = false + // :snippet-start: first + try { + val resultsFlow = collection.find() + val firstResult = resultsFlow.first() + isReached = true // :remove: + } catch (e: NoSuchElementException) { + println("No results found") + } + // :snippet-end: + assert(isReached) + } + + @Test + fun countTest() = runBlocking { + // :snippet-start: count + val resultsFlow = collection.find() + val count = resultsFlow.count() + // :snippet-end: + assertEquals(5, count) + } + + @Test + fun toListTest() = runBlocking { + // :snippet-start: toList + val resultsFlow = collection.find() + val results = resultsFlow.toList() + // :snippet-end: + assertEquals(5, results.size) + } + + @Test + fun iterateTest() = runBlocking { + // :snippet-start: iterate + val resultsFlow = collection.find() + resultsFlow.collect { println(it) } + // :snippet-end: + assertEquals(5, resultsFlow.count()) + } + + @Test + fun explainTest() = runBlocking { + // :snippet-start: explain + val explanation = collection.find().explain(ExplainVerbosity.EXECUTION_STATS) + val jsonSummary = explanation.getEmbedded( + listOf("queryPlanner", "winningPlan"), + Document::class.java + ).toJson() + println(jsonSummary) + // :snippet-end: + val expected = """{"stage": "COLLSCAN", "direction": "forward"} + """.trimIndent() + assertEquals(expected, jsonSummary) + } +} + diff --git a/examples/src/test/kotlin/TimeSeriesTest.kt b/examples/src/test/kotlin/TimeSeriesTest.kt index ce4c0ec5..23e49cf4 100644 --- a/examples/src/test/kotlin/TimeSeriesTest.kt +++ b/examples/src/test/kotlin/TimeSeriesTest.kt @@ -5,7 +5,7 @@ import com.mongodb.client.model.Filters.* import com.mongodb.client.model.Projections.* import com.mongodb.client.model.TimeSeriesOptions import com.mongodb.kotlin.client.coroutine.MongoClient -import io.github.cdimascio.dotenv.dotenv +import config.getConfig import kotlinx.coroutines.flow.toList import kotlinx.coroutines.runBlocking import org.bson.json.JsonWriterSettings @@ -20,8 +20,8 @@ import kotlin.test.* internal class TimeSeriesTest { companion object { - val dotenv = dotenv() - val mongoClient = MongoClient.create(dotenv["MONGODB_CONNECTION_URI"]) + val config = getConfig() + val mongoClient = MongoClient.create(config.connectionUri) @AfterAll @JvmStatic diff --git a/source/examples/generated/FlowTest.snippet.count.kt b/source/examples/generated/FlowTest.snippet.count.kt new file mode 100644 index 00000000..072671d8 --- /dev/null +++ b/source/examples/generated/FlowTest.snippet.count.kt @@ -0,0 +1,2 @@ +val resultsFlow = collection.find() +val count = resultsFlow.count() diff --git a/source/examples/generated/FlowTest.snippet.explain.kt b/source/examples/generated/FlowTest.snippet.explain.kt new file mode 100644 index 00000000..66e10ccf --- /dev/null +++ b/source/examples/generated/FlowTest.snippet.explain.kt @@ -0,0 +1,6 @@ +val explanation = collection.find().explain(ExplainVerbosity.EXECUTION_STATS) +val jsonSummary = explanation.getEmbedded( + listOf("queryPlanner", "winningPlan"), + Document::class.java +).toJson() +println(jsonSummary) diff --git a/source/examples/generated/FlowTest.snippet.first.kt b/source/examples/generated/FlowTest.snippet.first.kt new file mode 100644 index 00000000..97bd7820 --- /dev/null +++ b/source/examples/generated/FlowTest.snippet.first.kt @@ -0,0 +1,6 @@ +try { + val resultsFlow = collection.find() + val firstResult = resultsFlow.first() +} catch (e: NoSuchElementException) { + println("No results found") +} diff --git a/source/examples/generated/FlowTest.snippet.firstOrNull.kt b/source/examples/generated/FlowTest.snippet.firstOrNull.kt new file mode 100644 index 00000000..80630557 --- /dev/null +++ b/source/examples/generated/FlowTest.snippet.firstOrNull.kt @@ -0,0 +1,2 @@ +val resultsFlow = collection.find() +val firstResultOrNull = resultsFlow.firstOrNull() diff --git a/source/examples/generated/FlowTest.snippet.iterate.kt b/source/examples/generated/FlowTest.snippet.iterate.kt new file mode 100644 index 00000000..13148972 --- /dev/null +++ b/source/examples/generated/FlowTest.snippet.iterate.kt @@ -0,0 +1,2 @@ +val resultsFlow = collection.find() +resultsFlow.collect { println(it) } diff --git a/source/examples/generated/FlowTest.snippet.toList.kt b/source/examples/generated/FlowTest.snippet.toList.kt new file mode 100644 index 00000000..4547241b --- /dev/null +++ b/source/examples/generated/FlowTest.snippet.toList.kt @@ -0,0 +1,2 @@ +val resultsFlow = collection.find() +val results = resultsFlow.toList() diff --git a/source/fundamentals/crud/read-operations.txt b/source/fundamentals/crud/read-operations.txt index 3c13af0b..d2752f8c 100644 --- a/source/fundamentals/crud/read-operations.txt +++ b/source/fundamentals/crud/read-operations.txt @@ -5,6 +5,7 @@ Read Operations .. default-domain:: mongodb - :doc:`/fundamentals/crud/read-operations/retrieve` +- :doc:`/fundamentals/crud/read-operations/flow` - :doc:`/fundamentals/crud/read-operations/change-streams` - :doc:`/fundamentals/crud/read-operations/sort` - :doc:`/fundamentals/crud/read-operations/skip` @@ -13,13 +14,11 @@ Read Operations - :doc:`/fundamentals/crud/read-operations/geo` - :doc:`/fundamentals/crud/read-operations/text` -.. TODO:(DOCSP-29167) add back when refactor cursor page to flow page -.. - :doc:`/fundamentals/crud/read-operations/cursor` - .. toctree:: :caption: Read Operations /fundamentals/crud/read-operations/retrieve + /fundamentals/crud/read-operations/flow /fundamentals/crud/read-operations/change-streams /fundamentals/crud/read-operations/sort /fundamentals/crud/read-operations/skip @@ -27,6 +26,3 @@ Read Operations /fundamentals/crud/read-operations/project /fundamentals/crud/read-operations/geo /fundamentals/crud/read-operations/text - -.. TODO:(DOCSP-29167) add back when refactor cursor page to flow page -.. /fundamentals/crud/read-operations/cursor diff --git a/source/fundamentals/crud/read-operations/cursor.draft.rst b/source/fundamentals/crud/read-operations/cursor.draft.rst deleted file mode 100644 index b0c5323c..00000000 --- a/source/fundamentals/crud/read-operations/cursor.draft.rst +++ /dev/null @@ -1,242 +0,0 @@ -.. _kotlin-fundamentals-cursor: - -========================= -Access Data From a Cursor -========================= - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -Overview --------- - -In this guide, you can learn how to access data using a **cursor** with the -MongoDB Java driver. - -A cursor is a mechanism that allows an application to iterate over database -results while only holding a subset of them in memory at a given time. The -driver uses cursors in read operations that match multiple documents to return -matched documents in batches as opposed to returning them all at once. - -This page uses an initiating method, ``find()`` to show how to access -data from a `FindIterable -<{+api+}/apidocs/mongodb-driver-sync/com/mongodb/client/FindIterable.html>`__. - -.. note:: - - The following ways to access and store data apply to - other iterables such as an `AggregateIterable - <{+api+}/apidocs/mongodb-driver-sync/com/mongodb/client/AggregateIterable.html>`__. - -The ``find()`` method creates and returns an instance of a -``FindIterable``. A ``FindIterable`` allows you to browse the documents -matched by your search criteria and to further specify which documents -to see by setting parameters through methods. - -Terminal Methods ----------------- - -Terminal methods execute an operation on the MongoDB server after -configuring all parameters of an ``Iterable`` instance controlling the -operation. - -First -~~~~~ - -Use the ``first()`` method to retrieve the first document in your query -results: - -.. literalinclude:: /includes/fundamentals/code-snippets/Cursor.java - :language: java - :dedent: - :start-after: begin firstExample - :end-before: end firstExample - -This method is often used when your query filter will match one -document, such as when filtering by a unique index. - -Number of Results -~~~~~~~~~~~~~~~~~ - -Use the ``available()`` method to retrieve the number of results -locally present without blocking: - -.. literalinclude:: /includes/fundamentals/code-snippets/Cursor.java - :language: java - :dedent: - :start-after: begin availableExample - :end-before: end availableExample - -The method returns ``0`` if the application has already iterated though -all the documents in the cursor or if the cursor is closed. - -Into -~~~~ - -Use the ``into()`` method to store your query results in a ``List``: - -.. literalinclude:: /includes/fundamentals/code-snippets/Cursor.java - :language: java - :dedent: - :start-after: begin intoExample - :end-before: end intoExample - -This method is often used when your query filter returns a small number -of documents that can fit into available memory. - -Cursor -~~~~~~ - -Use the ``cursor()`` method to iterate through fetched documents and -ensure that the cursor closes if there is an early termination: - -.. code-block:: java - :copyable: true - - MongoCursor cursor = collection.find().cursor(); - -For more information on how to ensure a cursor closes, see the :ref:`cursor cleanup section `. - -Explain -~~~~~~~ - -Use the ``explain()`` method to view information about how MongoDB -executes your operation. - -The ``explain()`` method returns **execution plans** and performance -statistics. An execution plan is a potential way MongoDB -can complete an operation. The ``explain()`` method provides both the -winning plan (the plan MongoDB executed) and rejected plans. - -.. include:: /includes/fundamentals/explain-verbosity.rst - -The following example prints the JSON representation of the -winning plan for aggregation stages that produce execution plans: - -.. literalinclude:: /includes/fundamentals/code-snippets/Cursor.java - :language: java - :dedent: - :start-after: begin explainExample - :end-before: end explainExample - -The preceding code snippet should produce the following output: - -.. code-block:: none - :copyable: false - - { "stage": "COLLSCAN", "direction": "forward" } - -For more information on the explain operation, see the following -Server Manual Entries: - -- :manual:`Explain Output ` -- :manual:`Query Plans ` - -For more information about the methods and classes mentioned in this section, -see the following API Documentation: - -- `first() <{+api+}/apidocs/mongodb-driver-sync/com/mongodb/client/MongoIterable.html#first()>`__ -- `available() <{+api+}/apidocs/mongodb-driver-sync/com/mongodb/client/MongoCursor.html#available()>`__ -- `into() <{+api+}/apidocs/mongodb-driver-sync/com/mongodb/client/MongoIterable.html#into(A)>`__ -- `cursor() <{+api+}/apidocs/mongodb-driver-sync/com/mongodb/client/MongoIterable.html#cursor()>`__ -- `explain() <{+api+}/apidocs/mongodb-driver-sync/com/mongodb/client/FindIterable.html#explain()>`__ -- `ExplainVerbosity <{+api+}/apidocs/mongodb-driver-core/com/mongodb/ExplainVerbosity>`__ - -Usage Patterns --------------- - -A ``MongoCursor`` and ``FindIterable`` allow you to access query results -one document at a time, abstracting away network and caching logic. - -Functional Iteration -~~~~~~~~~~~~~~~~~~~~~ - -Pass a function to the ``forEach()`` method of a ``FindIterable`` to -iterate through results in a functional style: - -.. literalinclude:: /includes/fundamentals/code-snippets/Cursor.java - :language: java - :dedent: - :start-after: begin forEachIteration - :end-before: end forEachIteration - -.. important:: - - Initiating methods return objects that implement the ``Iterable`` interface which allows you - to iterate through them using iterator methods. Sometimes, we use an enhanced - for-each loop to iterate through results: - - .. code-block:: java - - for (Document cur : collection.find()) { - ... - } - - Iterating this way is not preferable because if an exception is - thrown before the loop completes, the cursor will not close. Using a - ``MongoCursor`` allows us to ensure the cursor closes as shown in the - :ref:`cursor cleanup section `. - -.. _kotlin-fundamentals-cursor-conditional-iteration: - -Conditional Iteration -~~~~~~~~~~~~~~~~~~~~~ - -Use the ``hasNext()`` method to check if there are any documents -available in the cursor, and then use the ``next()`` method to retrieve -the next available document from the cursor: - -.. literalinclude:: /includes/fundamentals/code-snippets/Cursor.java - :language: java - :dedent: - :start-after: begin manualIteration - :end-before: end manualIteration - -For more information about the methods and classes mentioned in this section, -see the following API Documentation: - -- `forEach() `__ -- `hasNext() <{+api+}/apidocs/mongodb-driver-sync/com/mongodb/client/MongoCursor.html#hasNext()>`__ -- `next() <{+api+}/apidocs/mongodb-driver-sync/com/mongodb/client/MongoCursor.html#next()>`__ - -.. _cursor_cleanup: - -Cursor Cleanup --------------- - -Close -~~~~~ - -Use the ``close()`` method in a finally block to free up a cursor's -consumption of resources in both the client application and the MongoDB -server: - -.. literalinclude:: /includes/fundamentals/code-snippets/Cursor.java - :language: java - :dedent: - :start-after: begin closeExample - :end-before: end closeExample - -Try with Resources Statement -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Use a try-with-resources statement -to automatically free up a cursor's consumption of resources in both the -client application and the MongoDB server: - -.. literalinclude:: /includes/fundamentals/code-snippets/Cursor.java - :language: java - :dedent: - :start-after: begin tryWithResourcesExample - :end-before: end tryWithResourcesExample - -For more information about the methods and classes mentioned in this section, -see the following API Documentation: - -- `close() <{+api+}/apidocs/mongodb-driver-sync/com/mongodb/client/MongoCursor.html#close()>`__ -- `try-with-resources statement `__ diff --git a/source/fundamentals/crud/read-operations/flow.txt b/source/fundamentals/crud/read-operations/flow.txt new file mode 100644 index 00000000..6287a0b5 --- /dev/null +++ b/source/fundamentals/crud/read-operations/flow.txt @@ -0,0 +1,128 @@ +.. _kotlin-fundamentals-flow: + +======================= +Access Data From a Flow +======================= + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this guide, you can learn how to access data using a ``Flow`` with the +MongoDB Kotlin driver. + +A ``Flow`` is a data type built into Kotlin coroutines that represent a stream +of values that are being computed asynchronously. The Kotlin coroutine driver +uses flows to represent the results of database read operations. + +This page uses an initiating method, ``find()`` to show how to access +data from a `FindFlow +<{+api-kotlin+}/apidocs/mongodb-driver-kotlin-coroutine/mongodb-driver-kotlin-coroutine/com.mongodb.kotlin.client.coroutine/-find-flow/index.html>`__. + +.. note:: + + The following ways to access and store data apply to + other iterables such as an `AggregateFlow + <{+api-kotlin+}/apidocs/mongodb-driver-kotlin-coroutine/mongodb-driver-kotlin-coroutine/com.mongodb.kotlin.client.coroutine/-aggregate-flow/index.html>`__. + +The ``find()`` method creates and returns an instance of a +``FindFlow``. A ``FindFlow`` allows you to browse the documents +matched by your search criteria and to further specify which documents +to see by setting parameters through methods. + +Terminal Methods +---------------- + +Terminal methods execute an operation on the MongoDB server after +configuring all parameters of a ``Flow`` instance controlling the +operation. + +Find the First Document +~~~~~~~~~~~~~~~~~~~~~~~ + +Use the ``firstOrNull()`` method to retrieve the first document in your query +results or ``null`` if there are no results: + +.. literalinclude:: /examples/generated/FlowTest.snippet.firstOrNull.kt + :language: kotlin + +Alternatively, you can use the ``first()`` method to retrieve the first document +in your query or throw a ``NoSuchElementException`` if there are no results: + +.. literalinclude:: /examples/generated/FlowTest.snippet.first.kt + :language: kotlin + +These methods are often used when your query filter will match one +document, such as when filtering by a unique index. + +Count Number of Results +~~~~~~~~~~~~~~~~~~~~~~~ + +Use the ``count()`` method to retrieve the number of results in the query: + +.. literalinclude:: /examples/generated/FlowTest.snippet.count.kt + :language: kotlin + +Convert Results to a List +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use the ``toList()`` method to store your query results in a ``List``: + +.. literalinclude:: /examples/generated/FlowTest.snippet.toList.kt + :language: kotlin + +This method is often used when your query filter returns a small number +of documents that can fit into available memory. + +Iterate through Results +~~~~~~~~~~~~~~~~~~~~~~~ + +Use the ``collect()`` method to iterate through fetched documents and +ensure that the flow closes if there is an early termination: + +.. literalinclude:: /examples/generated/FlowTest.snippet.iterate.kt + :language: kotlin + +Explain the Query +~~~~~~~~~~~~~~~~~ + +Use the ``explain()`` method to view information about how MongoDB +executes your operation. + +The ``explain()`` method returns **execution plans** and performance +statistics. An execution plan is a potential way MongoDB +can complete an operation. The ``explain()`` method provides both the +winning plan (the plan MongoDB executed) and rejected plans. + +.. include:: /includes/fundamentals/explain-verbosity.rst + +The following example prints the JSON representation of the +winning plan for aggregation stages that produce execution plans: + +.. io-code-block:: + + .. input:: /examples/generated/FlowTest.snippet.explain.kt + :language: kotlin + + .. output:: + :language: json + + { "stage": "COLLSCAN", "direction": "forward" } + +For more information on the explain operation, see the following +Server Manual Entries: + +- :manual:`Explain Output ` +- :manual:`Query Plans ` + +For more information about the methods and classes mentioned in this section, +see the following API Documentation: + +- `collect() <{+api-kotlin+}/apidocs/mongodb-driver-kotlin-coroutine/mongodb-driver-kotlin-coroutine/com.mongodb.kotlin.client.coroutine/-find-flow/collect.html>`__ +- `explain() <{+api-kotlin+}/apidocs/mongodb-driver-kotlin-coroutine/mongodb-driver-kotlin-coroutine/com.mongodb.kotlin.client.coroutine/-find-flow/explain.html>`__ +- `ExplainVerbosity <{+api+}/apidocs/mongodb-driver-core/com/mongodb/ExplainVerbosity>`__ diff --git a/source/quick-reference.txt b/source/quick-reference.txt index 0aaf453a..f45087d5 100644 --- a/source/quick-reference.txt +++ b/source/quick-reference.txt @@ -4,8 +4,6 @@ Quick Reference =============== -.. default-domain:: mongodb - This page shows the driver syntax for several MongoDB commands and links to their related reference and API documentation. @@ -350,21 +348,20 @@ The examples on the page use the following data class to represent MongoDB docum - .. include:: /includes/kotlin-driver-coroutine-gradle-versioned.rst -.. TODO:(DOCSP-29167) add back as part of this ticket. needs refactoring b/c cursor not thing for coroutines driver -.. * - | **Access Data from a Flow Iteratively** -.. | -.. | `API Documentation <{+api-kotlin+}/apidocs/mongodb-driver-sync/com/mongodb/client/MongoCursor.html>`__ -.. | :ref:`Fundamentals ` -.. - .. io-code-block:: -.. :copyable: true -.. .. input:: -.. :language: kotlin -.. .. output:: -.. :language: console - :visible: false -.. -.. [ -.. { title: '2001: A Space Odyssey', ... }, -.. { title: 'The Sound of Music', ... }, -.. ... -.. ] + * - | **Access Data from a Flow Iteratively** + | + | `API Documentation <{+api-kotlin+}/apidocs/mongodb-driver-kotlin-coroutine/mongodb-driver-kotlin-coroutine/com.mongodb.kotlin.client.coroutine/-find-flow/index.html>`__ + | :ref:`Fundamentals ` + + - .. io-code-block:: + :copyable: true + + .. input:: /examples/generated/FlowTest.snippet.iterate.kt + :language: kotlin + + .. output:: + :language: console + :visible: false + + Movie(title=2001: A Space Odyssey, ...) + Movie(title=The Sound of Music, ...) diff --git a/source/usage-examples/distinct.txt b/source/usage-examples/distinct.txt index 599f2340..519adb10 100644 --- a/source/usage-examples/distinct.txt +++ b/source/usage-examples/distinct.txt @@ -38,14 +38,13 @@ distinct values with a query filter as a second parameter, as follows: :language: kotlin The ``distinct()`` method returns an object that implements the -``DistinctFlow`` class, which contains methods to access,organize, and traverse -the results. ``DistinctFlow`` also inherits methods from its parent class -``Flow`` from the Kotlin Coroutines library, such as ``first()`` and +``DistinctFlow`` class, which contains methods to access, organize, and traverse +the results. ``DistinctFlow`` delegates to the ``Flow`` interface +from the Kotlin Coroutines library, allowing access to methods such as ``first()`` and ``firstOrNull()``. -.. TODO (DOCSP-29176) Add link to Flow documentation when available -.. For more information, see our -.. :doc:`guide on Accessing Data From a Flow `. +For more information, see our +:doc:`guide on Accessing Data From a Flow `. Example ------- diff --git a/source/usage-examples/find.txt b/source/usage-examples/find.txt index 1325efd8..06acbee4 100644 --- a/source/usage-examples/find.txt +++ b/source/usage-examples/find.txt @@ -28,19 +28,16 @@ For more information on the ``projection()`` method, see our The ``find()`` method returns an instance of ``FindFlow``, a class that offers several methods to access, organize, and traverse the results. -.. TODO (DOCSP-29176) Update with accurate inheritance information when available -.. ``FindFlow`` also inherits methods from its parent class ``Flow`` from the -.. Kotlin Coroutines library. - +``FindFlow`` also obtains methods from its delegate interface ``Flow`` from the +Kotlin Coroutines library. You can call the ``collect()`` method to iterate through the fetched results. You can also call terminal methods, such as ``firstOrNull()`` to return either the first document or ``null`` if there are no results, or ``first()`` to return the first document in the collection. If no documents match the query, calling ``first()`` throws a ``NoSuchElementException`` exception. -.. TODO (DOCSP-29176) Add link to Flow documentation when available -.. For more information on accessing data from a flow with the Kotlin driver, see our -.. :doc:`guide on Accessing Data From a Flow `. +For more information on accessing data from a flow with the Kotlin driver, see our +:doc:`guide on Accessing Data From a Flow `. Example ------- diff --git a/source/usage-examples/findOne.txt b/source/usage-examples/findOne.txt index a595de03..b466ace9 100644 --- a/source/usage-examples/findOne.txt +++ b/source/usage-examples/findOne.txt @@ -27,18 +27,15 @@ For more information on the ``projection()`` method, see our The ``find()`` method returns an instance of ``FindFlow``, a class that offers several methods to access, organize, and traverse the results. -.. TODO (DOCSP-29176) Update with accurate inheritance information when available -.. ``FindFlow`` also inherits methods from its parent class ``Flow`` from the -.. Kotlin Coroutines library, such as ``first()`` and ``firstOrNull()``. - +``FindFlow`` also obtains methods from its delegate interface ``Flow`` from the +Kotlin Coroutines library, such as ``first()`` and ``firstOrNull()``. The ``firstOrNull()`` method returns the first document from the retrieved results or ``null`` if there are no results. The ``first()`` method returns the first document or throws a ``NoSuchElementException`` exception if no documents match the query. -.. TODO (DOCSP-29176) Add link to Flow documentation when available -.. For more information on accessing data from a flow with the Kotlin driver, see our -.. :doc:`guide on Accessing Data From a Flow `. +For more information on accessing data from a flow with the Kotlin driver, see our +:doc:`guide on Accessing Data From a Flow `. Example -------