How to access database connection from a Rocket's fairing? - postgresql

I'm trying to use database connection from a Rocket's on_ignite fairing:
use sqlx::{ self, FromRow };
use rocket::fairing::{self, Fairing, Info, Kind};
use rocket::{Build, Rocket};
use crate::database::PostgresDb;
#[derive(FromRow)]
struct TableRow {
column_a: String,
column_b: String
}
#[rocket::async_trait]
impl Fairing for TableRow {
fn info(&self) -> Info {
Info {
name: "Cache table row",
kind: Kind::Ignite,
}
}
async fn on_ignite(&self, rocket: Rocket<Build>) -> fairing::Result {
let mut db = rocket
.state::<Connection<PostgresDb>>()
.expect("Unable to find db connection.");
let row = sqlx::query_as::<_, TableRow>("SELECT * FROM table WHERE id = 1;")
.fetch_one(&mut db)
.await
.unwrap();
fairing::Result::Ok(rocket.manage(row))
}
}
The problem is I get following rust error during .fetch_one(&mut db):
the trait bound `&mut rocket_db_pools::Connection<PostgresDb>: Executor<'_>` is not satisfied
the following other types implement trait `Executor<'c>`:
<&'c mut PgConnection as Executor<'c>>
<&'c mut PgListener as Executor<'c>>
<&'c mut PoolConnection<Postgres> as Executor<'c>>
<&'t mut Transaction<'c, Postgres> as Executor<'t>>
<&sqlx::Pool<DB> as Executor<'p>>rustcClick for full compiler diagnostic
cache_rbac_on_ignite.rs(56, 14): required by a bound introduced by this call
query_as.rs(132, 17): required by a bound in `QueryAs::<'q, DB, O, A>::fetch_all`
I tried solution suggested here: How to get the database Connection in rocket.rs Fairing. but it did not work out.
Here is the code:
use sqlx::{ self, FromRow, Database };
use rocket::fairing::{self, Fairing, Info, Kind};
use rocket::{Build, Rocket};
use crate::database::PostgresDb;
#[derive(FromRow)]
struct TableRow {
column_a: String,
column_b: String
}
#[rocket::async_trait]
impl Fairing for TableRow {
fn info(&self) -> Info {
Info {
name: "Cache table row",
kind: Kind::Ignite,
}
}
async fn on_ignite(&self, rocket: Rocket<Build>) -> fairing::Result {
let mut db = PostgresDb::get_one(rocket).await.unwrap();
let row = sqlx::query_as::<_, TableRow>("SELECT * FROM table WHERE id = 1;")
.fetch_one(&mut db)
.await
.unwrap();
fairing::Result::Ok(rocket.manage(row))
}
}
I get following rust error on line let mut db = PostgresDb::get_one(rocket).await.unwrap();:
no function or associated item named `get_one` found for struct `PostgresDb` in the current scope
function or associated item not found in `PostgresDb`rustcClick for full compiler diagnostic
mod.rs(8, 1): function or associated item `get_one` not found for this struct
What is the right way to use database connection inside of the fairing? Thank you!

Finally found an answer. Here is what worked for me:
use rocket::fairing::{self, Fairing, Info, Kind};
use rocket::{Build, Rocket};
use rocket_db_pools::{ sqlx::{ self, FromRow }, Database };
use crate::database::PostgresDb;
#[derive(FromRow)]
struct TableRow {
column_a: String,
column_b: String
}
#[rocket::async_trait]
impl Fairing for TableRow {
fn info(&self) -> Info {
Info {
name: "Cache table row",
kind: Kind::Ignite,
}
}
async fn on_ignite(&self, rocket: Rocket<Build>) -> fairing::Result {
let db = PostgresDb::fetch(&rocket).unwrap();
let mut conn = db.aquire().await.unwrap();
let row = sqlx::query_as::<_, TableRow>("SELECT * FROM table WHERE id = 1;")
.fetch_one(&mut conn)
.await
.unwrap();
fairing::Result::Ok(rocket.manage(row))
}
}

Related

the trait `LoadConnection` is not implemented for `&diesel::PgConnection`

I want to create a rest api with rust and can't make it work.
My relevant code so far:
In the main.rs:
#[actix_web::main]
async fn main() -> std::io::Result<()> {
// Loading .env into environment variable.
dotenv::dotenv().ok();
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
// set up database connection pool
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL");
let manager = ConnectionManager::<PgConnection>::new(database_url);
let pool: DbPool = r2d2::Pool::builder()
.test_on_check_out(true)
.build(manager)
.expect("Could not build connection pool");
let port = std::env::var("PORT").expect("$PORT is not set.");
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(pool.clone()))
.wrap(middleware::Logger::default())
.route("/", web::get().to(|| async { "Actix REST API" }))
.service(handlers::common::houses::index)
})
.bind(("0.0.0.0", port.parse().unwrap()))?
.run()
.await
}
The schema:
diesel::table! {
houses (id) {
id -> Int4,
user_id -> Varchar,
street -> Varchar,
figure -> Varchar,
floor -> Varchar,
create_date -> Timestamp,
update_date -> Timestamp,
is_deleted -> Bool,
}
}
The model:
#[derive(Debug, Serialize, Deserialize, Queryable)]
pub struct House {
pub id: i32,
pub user_id: String,
pub street: String,
pub figure: String,
pub floor: String,
pub create_date: chrono::NaiveDateTime,
pub update_date: chrono::NaiveDateTime,
pub is_deleted: bool,
}
The handler:
#[get("/houses")]
async fn index(pool: web::Data<DbPool>) -> Result<HttpResponse, Error> {
let houses = web::block(move || {
let conn = &pool.get()?;
find_all(&conn)
})
.await?
.map_err(actix_web::error::ErrorInternalServerError)?;
Ok(HttpResponse::Ok().json(houses))
}
fn find_all(conn: &PgConnection) -> Result<Vec<House>, DbError> {
use crate::schemas::common::houses::houses::dsl::*;
let items =houses.load::<House>(&mut conn)?;
Ok(items)
}
The dependencies are:
[dependencies]
actix-web = "4"
chrono = { version = "0.4.19", features = ["serde"] }
diesel = { version = "2.0.3", features = ["postgres", "r2d2", "chrono"] }
dotenv = "0.15.0"
env_logger = "0.10.0"
serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0"`
It keeps giving an error, and I don't understand why.
The error is:
`error[E0277]: the trait bound `&diesel::PgConnection: LoadConnection` is not satisfied src\handlers\common\houses.rs:25:37
| 25 | let items =houses.load::<House>(&mut conn)?;
| ---- -^^^^^^^^
| | | the trait `LoadConnection` is not implemented for `&diesel::PgConnection` help: consider removing the leading `&`-reference required by a bound introduced by this call
| note: required for `table` to implement `LoadQuery<'_, &diesel::PgConnection, House>` note: required by a bound in `diesel::RunQueryDsl::load`
I've seen a similar error with the diesel version 1.4, but I think that this version is different.
Plus I'm starting with rust and I'm a little lost in general at the moment.
I was hopping someone knows what the problem is and how to fix it.
PgConnection implements LoadConnection but &PgConnection does not (note the extra &).
Make conn mutable and pass as a mutable reference:
#[get("/houses")]
async fn index(pool: web::Data<DbPool>) -> Result<HttpResponse, Error> {
let houses = web::block(move || {
let mut conn = pool.get()?; // <------------
find_all(&mut conn) // <------------
})
.await?
.map_err(actix_web::error::ErrorInternalServerError)?;
Ok(HttpResponse::Ok().json(houses))
}
fn find_all(conn: &mut PgConnection) -> Result<Vec<House>, DbError> {
// ^^^ <------------
use crate::schemas::common::houses::houses::dsl::*;
let items = houses.load::<House>(conn)?; // <------------
Ok(items)
}

Using the cursor in mongodb-rust sync

I took the code from the documentation, but it doesn't work.
pub fn get_countries(&self) {
let cursor = self.countries.find(None, None);
for doc in cursor {
println!("{}", doc?)
}
}
mongodb::sync::Cursor<bson::Document> doesn't implement std::fmt::Display
mongodb::sync::Cursor<bson::Document> cannot be formatted with the default formatter
the ? operator can only be applied to values that implement std::ops::Try
the ? operator cannot be applied to type mongodb::sync::Cursor<bson::Document>
Also the cursor.collect() does not work correctly.
the method collect exists for enum std::result::Result<mongodb::sync::Cursor<bson::Document>, mongodb::error::Error>, but its trait bounds were not satisfied
method cannot be called on std::result::Result<mongodb::sync::Cursor<bson::Document>, mongodb::error::Error> due to unsatisfied trait bounds
I tried using cursor.iter() or cursor.into_iter(), the result was the same
Full code of module
use bson::Document;
use mongodb::{
error::Error,
sync::{ Collection, Database},
};
pub struct Core {
db: Database,
countries: Collection<Document>,
}
impl Core {
pub fn new(db: &Database) -> Core {
Core {
db: db.clone(),
countries: db.collection("countries"),
}
}
pub fn get_country(&self, name: &String) -> Result<Option<Document>, Error> {
self.countries.find_one(bson::doc! { "idc": name }, None)
}
pub fn get_countries(&self) {
let cursor = self.countries.find(None, None);
for doc in cursor {
println!("{}", doc?)
}
}
}
It seems that the doc value is returning a Cursor, so I'm guessing that cursor must be rather the Result<Cursor<T>> type returned by the Collection::find method. https://docs.rs/mongodb/latest/mongodb/sync/struct.Collection.html#method.find
Shouldn't you unwrap (or handle the result with a proper match) your self.countries.find(None, None) result ?
pub fn get_countries(&self) {
let cursor = self.countries.find(None, None).unwrap();
for doc in cursor {
println!("{}", doc?)
}
}
My solution
pub fn get_countries(&self) -> Vec<Document> {
let cursor = self.countries.find(None, None).unwrap();
let mut total: Vec<Document> = Vec::new();
for doc in cursor {
total.push(doc.unwrap());
}
total
}

Typing of return type after mongodb projection

I am making a graphql resolver in rust, and am only fetching the fields from the graphql query in my mongodb database. However Rust complains that the fetched data, of course, is now not of the same type as the specified return type. What is the right way to do something like this.
I guess I could do #[serde(default)], but that doesn't work exactly as expected (I will explain later)
use async_graphql::*;
use serde::{Deserialize, Serialize};
use mongodb::{bson::doc, bson::oid::ObjectId, options::FindOptions, Collection};
#[derive(SimpleObject, Serialize, Deserialize, Debug)]
#[graphql(complex)]
struct Post {
#[serde(rename = "_id")]
pub id: ObjectId,
pub title: String,
// I could do something like
// #[serde(default)]
pub body: String,
}
#[ComplexObject]
impl Post {
async fn text_snippet(&self) -> &str {
let length = self.body.len();
let end = min(length, 5);
&self.body[0..end]
}
}
struct Query;
#[Object]
impl Query {
// fetching posts
async fn posts<'ctx>(&self, ctx: &Context<'ctx>) -> Vec<Post> {
let posts = ctx.data_unchecked::<Collection<Post>>();
let projection = // getting the projection doc here based on graphql fields, lets say doc! {"title": 1}
let options = FindOptions::builder().limit(10).projection(projection).build();
let cursor = posts.find(None, options).await.unwrap();
cursor.try_collect().await.unwrap_or_else(|_| vec![])
}
}
But when I run the query
{
posts {
id
title
textSnippet
}
}
i get
thread 'actix-rt:worker:0' panicked at 'called `Result::unwrap()` on an `Err` value: Error { kind: BsonDecode(DeserializationError { message: "missing field `body`" }), labels: [] }', server/src/schema/post.rs:20:46
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
and when i do the #[serde(default)] stuff on body, and I then query textSnippet and not body, the textSnippet is an empty string.
How do i fix this?
Could you wrap every field in Post with an Option and let the try_collect fill the returned fields for you?
You can create a struct with those fileds you need and use a collection of the new struct.
use async_graphql::*;
use serde::{Deserialize, Serialize};
use mongodb::{bson::doc, bson::oid::ObjectId, options::FindOptions, Collection};
#[derive(SimpleObject, Serialize, Deserialize, Debug)]
#[graphql(complex)]
struct Post {
#[serde(rename = "_id")]
pub id: ObjectId,
pub title: String,
// I could do something like
// #[serde(default)]
pub body: String,
}
#[derive(SimpleObject, Serialize, Deserialize, Debug)]
#[graphql(complex)]
struct PostTitle {
#[serde(rename = "_id")]
pub id: ObjectId,
pub title: String,
}
struct Query;
#[Object]
impl Query {
// fetching posts
async fn posts<'ctx>(&self, ctx: &Context<'ctx>) -> Vec<PostTitle> {
let posts = ctx.data_unchecked::<Collection<PostTitle>>();
let projection = doc! {"title": 1}
let options = FindOptions::builder().limit(10).projection(projection).build();
let cursor = posts.find(None, options).await.unwrap();
cursor.try_collect().await.unwrap_or_else(|_| vec![])
}
}

How to insert HashMap into PostgreSQL as JSON type?

contacts has a data structure as HashMap, I'm using PostgreSQL client -rust-postgres to insert contact's key and value into a table, then I want to select from the table. Below is what I tried so far. I need help with writing the right syntax.
use postgres::{Client, NoTls};
use std::collections::HashMap;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = Client::connect("host=127.0.0.1 user=postgres", NoTls)?;
client.simple_query("
DROP TABLE
IF EXISTS following_relation;
")?;
client.simple_query("
CREATE TABLE following_relation (
id SERIAL NOT NULL PRIMARY KEY,
relation JSON NOT NULL
)
")?;
let mut contacts = HashMap::new();
let mut v: Vec<String> = Vec::new();
v = vec!["jump".to_owned(), "jog".to_string()];
contacts.insert("Ashley", v.clone());
for (name, hobby) in contacts.iter() {
// 1. How to write sql statement with parameters?
client.execute(
"INSERT INTO following_relation(relation)
VALUE ('{"name" : $1, "hobby" : $2}')",
&[&name, &hobby],
)?;
}
for row in client.query("SELECT id, relation FROM following_relation", &[])? {
// 2. How to read from parse the result?
let id: i32 = row.get(0);
let relation = row.get(1);
//println!("found person: {} {} {:?}", id, relation["name"], relation["hobby"]);
}
Ok(())
}
I've been given the hints
Like the error message says, your query has VALUE but it needs to be VALUES.
Query parameters cannot be interpolated into strings. You should build the object in Rust, and use https://docs.rs/postgres/0.17.0/postgres/types/struct.Json.html to wrap the types when inserting.
I have no idea how to apply pub struct Json<T>(pub T); here.
How to build the query required in function execute?
pub fn execute<T: ?Sized>(
&mut self,
query: &T,
params: &[&(dyn ToSql + Sync)]
) -> Result<u64, Error>
where
T: ToStatement,
UPDATED, I tried with a more brief code sample
use postgres::{Client, NoTls};
use postgres::types::Json;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct relations {
name : String,
hobby: Vec<String>
}
pub struct Json<T>(pub T);
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = Client::connect("host=127.0.0.1 user=postgres", NoTls)?;
client.simple_query("
DROP TABLE
IF EXISTS following_relation;
")?;
client.simple_query("
CREATE TABLE following_relation (
id SERIAL PRIMARY KEY,
relation JSON NOT NULL
)
")?;
let rel = relations {
name: "czfzdxx".to_string(),
hobby: vec![
"basketball".to_string(),
"jogging".to_string()
],
};
client.execute(
r#"INSERT INTO following_relation(relation)
VALUE ($1)"#,
&[&Json(&rel)]
)?;
Ok(())
}
I get
error[E0432]: unresolved import `postgres::types::Json`
You want Rust raw string literal:
for (name, hobby) in contacts.iter() {
client.execute(
r#"INSERT INTO following_relation(relation)
VALUE ('{"name" : ($1), "hobby" : ($2)}')"#,
&[&name, &following],
)?;
}
Between the start r#" and the end "#, your string literals can have any character except # itself without escaping. If you also want # itself, then starts the raw string literals with multiple #s and ends with matching number of #s.
Here is main.rs:
use postgres::{Client, NoTls};
use serde::{Deserialize, Serialize};
use postgres_types::Json;
use postgres_types::{FromSql};
#[derive(Debug, Deserialize, Serialize, FromSql)]
struct Relation {
name : String,
hobby: Vec<String>
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = Client::connect("host=127.0.0.1 user=postgres", NoTls)?;
client.simple_query("
DROP TABLE
IF EXISTS following_relation;
")?;
client.simple_query("
CREATE TABLE following_relation (
id SERIAL PRIMARY KEY,
relation JSON NOT NULL
)
")?;
let rel = Relation {
name: "czfzdxx".to_string(),
hobby: vec![
"basketball".to_string(),
"jogging".to_string()
],
};
client.execute(
"INSERT INTO following_relation (relation) VALUES ($1)",
&[&Json::<Relation>(rel)]
)?;
for row in &client.query("SELECT relation FROM following_relation", &[]).unwrap() {
let rel: Json<Relation> = row.get(0);
println!("{:?}", rel);
}
Ok(())
}
and Cargo.toml:
[package]
name = "testapp"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
postgres = {version = "0.17.0"}
tokio-postgres = "0.5.1"
serde = {version = "1.0.104", features = ["derive"]}
postgres-types = {version = "0.1.0", features = ["derive", "with-serde_json-1"]}
serde_json = "1.0.45"
And here is the relevant documentation used: postgres_types and postgres. Search for serde_json, ToSql and FromSql traits are implemented for this third-party type.

How to store actix_web Json to mongodb?

Trying to store incomming data into mongo using r2d2-mongodb and actix_web.
#[derive(Serialize, Deserialize, Debug)]
struct Weight {
desc: String,
grams: u32,
}
fn store_weights(weights: web::Json<Vec<Weight>>, db: web::Data<Pool<MongodbConnectionManager>>) -> Result<String> {
let conn = db.get().unwrap();
let coll = conn.collection("weights");
for weight in weights.iter() {
coll.insert_one(weight.into(), None).unwrap();
}
Ok(String::from("ok"))
}
I can't seem to understand what/how I need to convert weight into to use with insert_one.
The above code errors into error[E0277]: the trait bound 'bson::ordered::OrderedDocument: std::convert::From<&api::weight::Weight>' is not satisfied
The signature for insert_one is:
pub fn insert_one(
&self,
doc: Document,
options: impl Into<Option<InsertOneOptions>>
) -> Result<InsertOneResult>
Document is bson::Document, an alias for bson::ordered::OrderedDocument.
Your type Weight does not implement the trait Into<Document>, which is required for weight::into(). You could implement it, but a more idiomatic way would be using the Serialize trait with bson::to_bson:
fn store_weights(weights: Vec<Weight>) -> Result<&'static str, Box<dyn std::error::Error>> {
let conn = db.get()?;
let coll = conn.collection("weights");
for weight in weights.iter() {
let document = match bson::to_bson(weight)? {
Document(doc) => doc,
_ => unreachable!(), // Weight should always serialize to a document
};
coll.insert_one(document, None)?;
}
Ok("ok")
}
Notes:
to_bson returns an enum, Bson, which can be Array, Boolean, Document, etc. We use match to make sure it is Document.
I've used ? instead of unwrap, to make use of the Result return type. Make sure the errors are Into<Error> for your Result type.
Returning &'static str instead of allocating a new String for each request.