Access Data by Using a Cursor
On this page
Overview
In this guide, you can learn how to use the Rust driver to access data returned from a read operation or aggregation by using a cursor. A cursor is a mechanism that enables you to iterate through multiple documents while holding only a subset of them in memory at a given time.
The driver offers the Cursor
type to retrieve documents from a cursor.
For example, when you run a find operation that can return multiple
documents, the driver returns a Cursor
instance from which you can access
the matched documents.
After you run a read operation or aggregation, the returned Cursor
instance
contains the first batch of results from the operation. As you iterate through
the cursor, the server returns more individual results. If there are more
matching documents after you reach the end of a batch of results, the Cursor
instance fetches the next batch of documents until all the results are returned.
This guide includes the following sections:
Sample Data for Examples presents the sample data that is used by the cursor examples
Retrieve Documents Individually describes how to use iteration or a stream to access results one at a time
Retrieve Documents as an Array describes how to access all results as a single array by collecting the returned cursor results
Specify Cursor Behavior describes how to configure the cursor that a method returns
Additional Information provides links to resources and API documentation for types and methods mentioned in this guide
Sample Data for Examples
The examples in this guide use the following data stored in a struct:
let docs = vec! [ Fruit { name: "strawberry".to_string(), color: "red".to_string() }, Fruit { name: "banana".to_string(), color: "yellow".to_string() }, Fruit { name: "pomegranate".to_string(), color: "red".to_string() }, Fruit { name: "pineapple".to_string(), color: "yellow".to_string() } ];
Retrieve Documents Individually
The driver provides the following access patterns to iterate through
documents returned by a Cursor
instance:
Built-in Pattern: advance the cursor, then retrieve and deserialize the current document
Stream Implementation Pattern: iterate over the cursor and call methods provided by
Stream
to process single or multiple documents
The following sections describe these access patterns and corresponding methods in more detail.
Built-in Pattern
You can use the driver's built-in access pattern to retrieve and process documents one by one.
The Cursor
type includes the advance()
and
deserialize_current()
methods to iterate through a cursor and
access documents individually.
The advance()
method moves the cursor forward and sends a request to the
database for more results when the local buffer is exhausted, which occurs
when the cursor reaches the end of a batch of results. Each time the cursor
reaches the end of a batch of results, it requests the next batch. The cursor
is exhausted when it has no more matching documents to return and is no longer
usable. The advance()
method returns a true
result if new results are
successfully returned and a false
result if the cursor is closed.
The deserialize_current()
method returns a reference to the current
result in the cursor and deserializes the result into the type
associated with the cursor. Unless you specify a type, the method uses the
same type that your collection is parameterized with.
Important
You can call the deserialize_current()
method only if the advance()
method returns a true
result. The driver generates an error if you call
deserialize_current()
on the cursor without a true
result or
without calling previously calling advance()
.
The following example shows how to implement this access pattern to
iterate through the results of a find operation on the fruits
collection:
let mut cursor = my_coll.find(doc! { "color": "red" }).await?; while cursor.advance().await? { println!("{:?}", cursor.deserialize_current()?); }
Fruit { name: "strawberry", color: "red" } Fruit { name: "pomegranate", color: "red" }
Stream Implementation Pattern
You can access cursor results as a stream to retrieve individual documents or collect multiple documents at once.
The Cursor
type implements the Stream
trait, so you can iterate
through a cursor as a stream. You can use this pattern to help you
write more concise code than with the built-in pattern, because the
Stream
extension trait StreamExt
provides numerous functions to
combine operations and consolidate code.
You can use the following methods to use the stream pattern:
next()
: advances the cursor to the next result and returns anOption<Result<T>>
typetry_next()
: advances the cursor to the next result and returns aResult<Option<T>>
type
Important
Required Imports for Stream Pattern Methods
To use the next()
method, you must import the StreamExt
trait. To use the try_next()
method, you must import the
TryStreamExt
trait.
The following example shows how to implement the two stream methods to
iterate through the results of find operations on the fruits
collection:
let mut cursor = my_coll.find(doc! { "color": "red" }).await?; println!("Output from next() iteration:"); while let Some(doc) = cursor.next().await { println!("{:?}", doc?); } println!(); let mut cursor = my_coll.find(doc! { "color": "yellow" }).await?; println!("Output from try_next() iteration:"); while let Some(doc) = cursor.try_next().await? { println!("{:?}", doc); }
Output from next() iteration: Fruit { name: "strawberry", color: "red" } Fruit { name: "pomegranate", color: "red" } Output from try_next() iteration: Fruit { name: "banana", color: "yellow" } Fruit { name: "pineapple", color: "yellow" }
Retrieve Documents as an Array
Because the Cursor
type implements the Stream
trait, you can
collect the results from a cursor into an array.
You can use the following methods to retrieve documents as an array:
collect()
: collects results from a cursor into aVec<Result<T>>
typetry_collect()
: collects results from a cursor into aResult<Vec<T>>
type
Note
To use the collect()
method, you must import the StreamExt
trait. To use the try_collect()
method, you must import the
TryStreamExt
trait.
let cursor = my_coll.find(doc! { "color": "red" }).await?; println!("Output from collect():"); let v: Vec<Result<Fruit>> = cursor.collect().await; println!("{:?}", v); println!(); let cursor = my_coll.find(doc! { "color": "yellow" }).await?; println!("Output from try_collect():"); let v: Vec<Fruit> = cursor.try_collect().await?; println!("{:?}", v);
Output from collect(): [Ok(Fruit { name: "strawberry", color: "red" }), Ok(Fruit { name: "pomegranate", color: "red" })] Output from try_collect(): [Fruit { name: "banana", color: "yellow" }, Fruit { name: "pineapple", color: "yellow" }]
Warning
Avoid Exceeding Application Memory Limits
Avoid converting large sets of results to arrays. If the array exceeds the size of available application memory, your application might crash. If you expect a large result set, retrieve documents from the cursor individually. To learn how to iterate through the cursor, see the Retrieve Documents Individually section of this guide.
Specify Cursor Behavior
To modify the cursor that an operation returns, chain option builder methods to
the method that returns the Cursor
instance. For example, you can
chain cursor-related option builder methods to the find()
method.
Note
Setting Options
You can set FindOptions
fields by chaining option builder methods directly
to the find()
method call. If you're using an earlier version of the
driver, you must construct a FindOptions
instance by chaining option
builder methods to the builder()
method. Then, pass your FindOptions
instance as a parameter to find()
.
The following table describes cursor-related options that you can set by calling their corresponding builder method:
Setting | Description |
---|---|
batch_size | Specifies the maximum number of documents the server returns per
cursor batch. This option sets the number of documents the cursor
keeps in memory rather than the number of documents the cursor
returns. Type: u32 Default: 101 documents initially, 16 MB maximum for
subsequent batches |
cursor_type | Specifies the type of cursor to return. You can set this option
to produce a tailable cursor. To learn more about tailable
cursors, see Tailable Cursors in the Server manual. Type: CursorType Default: CursorType::NonTailable |
no_cursor_timeout | Specifies whether the server closes the cursor after a period
of inactivity. IMPORTANT:
Because the Cursor type implements the Drop trait, the
server closes a cursor when it goes out of scope. The server
runs an asynchronous killCursors command to close the
cursor. See killCursors
in the Server manual to learn more.Type: bool Default: false |
The following code shows how to specify cursor-related settings by chaining
option builder methods to the find()
method:
let mut cursor = my_coll.find(doc! { "color": "red" }) .batch_size(5) .cursor_type(CursorType::Tailable) .no_cursor_timeout(true) .await?;
Additional Information
To learn more about the operations in this guide, see the following documentation:
Retrieve Data guide
Specify a Query guide
To learn more about converting between Rust types and BSON, see the guide on Data Modeling and Serialization.
API Documentation
To learn more about the methods and types mentioned in this guide, see the following API documentation:
next() in the
StreamExt
traittry_next() in the
TryStreamExt
traitcollect() in the
StreamExt
traittry_collect() in the
TryStreamExt
trait