How to use Mongodb::cursor in Rust? - mongodb

It's a very simple project to learn how to use mongodb with Rust. I'm using the official mongodb driver here: https://github.com/mongodb/mongo-rust-driver. The problem is that if I'm using aggregate, I cannot read the result
// main.rs
use mongodb::bson::{doc, Bson};
use mongodb::{options::AggregateOptions, options::ClientOptions, Client};
use std::error::Error;
use tokio;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// Load the MongoDB connection string:
let client_uri = "mongodb://127.0.0.1:27017";
// A Client is needed to connect to MongoDB:
let mut options = ClientOptions::parse(&client_uri).await?;
options.app_name = Some("testing".to_string());
let client = Client::with_options(options)?;
// get the collection here
let items = client.database("my_database").collection("inventory");
// aggregate options and pipeline
let pipeline = vec![doc! {"$match": {"name": "FOO"}}];
let options = AggregateOptions::builder().allow_disk_use(true).build();
// I'm using tokio for async-await library
let data = items
.aggregate(pipeline, options)
.await
.map_err(|e| println!("{}", e));
// data is a Result<mongodb::Cursor> type
match data {
Ok(cursor) => {
// I want to iterate the returned documents here
// this doesn't compiles
while let Some(doc) = cursor.next().await {
println!("{}", doc?)
}
},
Err(e) => println!("{:?}", e),
}
The code above returns an error. It complains that the cursor has no next() function within.
while let Some(doc) = cursor.next().await {
| ^^^^ method not found in `mongodb::Cursor`
I read the manual book for mongodb::Cursor here: https://docs.rs/mongodb/1.2.1/mongodb/struct.Cursor.html
and aggregate function here https://docs.rs/mongodb/1.2.1/mongodb/struct.Collection.html#method.aggregate
As you can see the aggregate method should return Result<Cursor>. As the manual stated:
A cursor can be used like any other Stream. The simplest way is just
to iterate over the documents it yields:
while let Some(doc) = cursor.next().await {
println!("{}", doc?)
}
So why is it doesn't work ?
My dependencies in Cargo.toml:
[dependencies]
tokio = { version = "0.2", features = ["macros", "rt-threaded"] }
serde = { version = "1.0", features = ["derive"] }
mongodb = "1.2.0"
If I print the cursor println!("{:?}", cursor);. It contains data in it. How to get the data out from this cursor ?

I found it! Just add use tokio::stream::StreamExt; on top of the file and the rest is good to go.
...all the other methods that an Stream has are available on Cursor as
well. This includes all of the functionality provided by StreamExt,
which provides similar functionality to the standard library Iterator
trait.
// main.rs
use mongodb::bson::{doc, Bson};
use mongodb::{options::AggregateOptions, options::ClientOptions, Client};
use std::error::Error;
use tokio;
// don't forget this!
use tokio::stream::StreamExt;

Fixed it by adding use futures::stream::StreamExt;

Related

Learning Rust, having trouble with async_traits and borrowing. `xxxxx` dropped here while still borrowed

I've viewed so many stack overflows, but can't get my head around how to fix this.
Here are my deps:
[dependencies]
mongodb = { version = "2.3.1"}
tokio = "1.22.0"
twilio-openapi = "1.0.0"
async-trait = "0.1.58"
futures = "0.3.25"
I've created this async_trait repository trait for implementing various data access repositories:
use std::pin::Pin;
use async_trait::async_trait;
use futures::Future;
use mongodb::{bson::Document, error::Error, options::FindOneOptions, Client};
pub mod notification_settings;
#[async_trait]
pub trait Repository<'a> {
fn with_client(client: &'a mut &Client) -> Self;
async fn find_one<'b>(
self,
filter: Document,
find_one_options: FindOneOptions,
) -> Pin<Box<(dyn Future<Output = Result<Option<Document>, Error>> + Send + 'b)>>;
}
It seems like async_trait sort of requires returning a Pin<Box<dyn Future<Output = X> + Send + 'b)>> Where you can specify a certain lifetime on the borrow. I'm not sure exactly why or if there are ways around this, but again still learning this.
And then here is my struct implementing the trait:
use std::{marker::Send, pin::Pin};
use async_trait::async_trait;
use futures::Future;
use mongodb::{bson::Document, error::Error, options::FindOneOptions, Client};
pub struct NotificationSettingsRepository<'a> {
pub(crate) client: &'a Client,
}
#[async_trait]
impl<'a> super::Repository<'a> for NotificationSettingsRepository<'a> {
fn with_client(client: &'a mut &Client) -> Self {
NotificationSettingsRepository { client: client }
}
async fn find_one<'b>(
self,
filter: Document,
find_one_options: FindOneOptions,
) -> Pin<Box<(dyn Future<Output = Result<Option<Document>, Error>> + Send + 'b)>> {
let collection = self
.client
.database("Occasionally")
.collection::<Document>("NotificationSettings");
let document = collection.find_one(filter, find_one_options);
Box::pin(async { document.await })
}
}
Problem is on collection.find_one(filter, find_one_options); I get an error about:
`collection` does not live long enough
borrowed value does not live long enoughrustcClick for full compiler diagnostic
notification_settings.rs(30, 5): `collection` dropped here while still borrowed
notification_settings.rs(17, 23): lifetime `'b` defined here
notification_settings.rs(21, 10): type annotation requires that `collection` is borrowed for `'b`
This makes some sense because the definition of find_one looks like this:
find_one(&self, filter: impl Into<Option<Document>>, options: impl Into<Option<FindOneOptions>>) -> Result<Option<T>>
So it is borrowing the collection via &self
I've tried a few things like adding an async { } wrapper on the Future of find_one. But I'm not sure what I'm doing there. Please help and thank you!

Is there a way to return the Client object from this function?

This is my async function that uses rust to connect to an existing mongoDB database. Is there a way to return / export the client variable / object and make it usable in other Files / Functions?
async fn connect_to_db() -> Result<(), Box<dyn Error>> {
// Load the MongoDB connection string from an environment variable (or string):
let client_uri = "mongodb://localhost:27017";
let options =
ClientOptions::parse_with_resolver_config(&client_uri, ResolverConfig::cloudflare())
.await?;
let client = Client::with_options(options)?;
let db = client.database("fiesta");
// Select Collection(s)
let user_col: Collection<User> = db.collection("users");
let skills_col: Collection<Skill> = db.collection("skills");
//ok.
Ok(())
}
Help would be appreciated.
Dependencies:
rocketrs
mongodb
tokio & serde
I have tried multiple things such as changing the lifetimes and changing the return type of the function, but to no avail.

cannot infer type for type parameter `T` declared on the associated function `collection`

I am trying to connect to Mongo DB from rust. I am following the tutorial here.
But I am getting the below error. I did run rustup update and I have the latest 1.59 version.
error[E0282]: type annotations needed for `mongodb::Collection<T>`
--> src/main.rs:27:46
|
27 | let movies = client.database("sample_mflix").collection("movies");
| ------ ^^^^^^^^^^ cannot infer type for type parameter `T` declared on the associated function `collection`
| |
| consider giving `movies` the explicit type `mongodb::Collection<T>`, where the type parameter `T` is specified
For more information about this error, try `rustc --explain E0282`.
error: could not compile `try-mongo` due to previous error
Below is the code I have written.
use mongodb::{Client, options::{ClientOptions, ResolverConfig}};
use std::env;
use std::error::Error;
use tokio;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// Load the MongoDB connection string from an environment variable:
let client_uri =
env::var("MONGODB_URI").expect("You must set the MONGODB_URI environment var!");
// A Client is needed to connect to MongoDB:
// An extra line of code to work around a DNS issue on Windows:
let options =
ClientOptions::parse_with_resolver_config(&client_uri, ResolverConfig::cloudflare())
.await?;
let client = Client::with_options(options)?;
// Print the databases in our MongoDB cluster:
println!("Databases:");
for name in client.list_database_names(None, None).await? {
println!("- {}", name);
}
// Get the 'movies' collection from the 'sample_mflix' database:
let movies = client.database("sample_mflix").collection("movies");
Ok(())
}
In Cargo.toml I have the below dependecies added -
[package]
name = "try-mongo"
version = "0.1.0"
edition = "2021"
[dependencies]
mongodb = "2.1"
bson = { version = "2", features = ["chrono-0_4"] } # Needed for using chrono datetime in doc
tokio = "1"
chrono = "0.4" # Used for setting DateTimes
serde = "1" # Used in the Map Data into Structs section`
So the solution to this is to specify the mongodb::bson::Document type to the collection function call. Like below -
let movies = client.database("sample_mflix").collection::<Document>("movies");

I can't capture the DB reference

I'm trying to create an API using Actix-web, async-grahpql and sqlx with postgresql
In the QueryRoot of the async-graphql I am trying to capture the reference of the DB and make the query the database with sqlx, but it gives me an error
let items = Todo::list(&pool).await?;
| ^^^^^ expected struct `sqlx::Pool`, found enum `std::result::Result`
Here I want to capture the reference
use crate::todo::*;
use async_graphql::{Context, FieldResult};
use sqlx::postgres::PgPool;
pub struct QueryRoot;
#[async_graphql::Object]
impl QueryRoot {
async fn todos(&self, ctx: &Context<'_>) -> FieldResult<Vec<Todo>> {
let pool = ctx.data::<PgPool>();
let items = Todo::list(&pool).await?; //<-- This line generates an error
Ok(items)
}
}
Here I define the references
pub fn run(listener: TcpListener, db_pool: PgPool) -> Result<Server, std::io::Error> {
let data_db_pool = Data::new(db_pool);
//GraphQL
let schema = Schema::build(QueryRoot, MutationRoot, EmptySubscription)
.data(data_db_pool.clone()) //<- DB reference
.finish();
let server = HttpServer::new(move || {
App::new()
.app_data(db_pool.clone()) //<- DB reference
.data(schema.clone())
.route("/graphql", web::post().to(graphql))
.route("/graphql", web::get().to(graphql_playground))
})
.listen(listener)?
.run();
Ok(server)
}
What am I doing wrong? The complete code can be found here.
ctx.data::<T>() returns a Result wrapping a reference to T. You probably want.
let pool = ctx.data::<PgPool>()?;
// ^ return on Err, otherwise yield the &PgPool
let items = Todo::list(pool).await?;
// ^^^^ shouldn't need & here

the trait `std::convert::From<mongodb::error::Error>` is not implemented for `std::io::Error`

Trying to make server with actix-web & mongodb in rust. Getting error
the trait std::convert::From<mongodb::error::Error> is not implemented for std::io::Error
here is my code
use actix_web::{web, App, HttpRequest, HttpServer, Responder};
use mongodb::{options::ClientOptions, Client};
async fn greet(req: HttpRequest) -> impl Responder {
let name = req.match_info().get("name").unwrap_or("World");
format!("Hello {}!", &name)
}
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
// Parse a connection string into an options struct.
let mut client_options = ClientOptions::parse("mongodb://localhost:27017")?;
// Manually set an option.
client_options.app_name = Some("My App".to_string());
// Get a handle to the deployment.
let client = Client::with_options(client_options)?;
// List the names of the databases in that deployment.
for db_name in client.list_database_names(None)? {
println!("{}", db_name);
}
HttpServer::new(|| {
App::new()
.route("/", web::get().to(greet))
.route("/{name}", web::get().to(greet))
})
.bind("127.0.0.1:8000")?
.run()
.await
}
Did I missed anything?
It means that one of the functions you are calling with a ? at the end can return a mongodb::error::Error. But the signature of the main is a std::io::Result<()>, wich is an implied Result<(), std::io::Error>. The only error type it can accept is a io::Error, not a mongodb::Error.
It looks like all the functions you are escaping might return this mongodb::error::Error, so you can try to change the main signature to such a result: Result<(). mongodb::error::Error>.
But I would recommend you do proper error handling on those potential errors, as this is your main(). Change those ? to .expect("Some error message"); at least. The program will still crash, but it will crash in a way that is meaningful to you.