How to pass multiple parameters for a query statement in tokio_postgres? - postgresql

I need to pass several parameters into the tokio_postgres::query Statement. How can I properly do this?
The way I do it below doesn't work, what gets passed into the SQL database is the unconverted $2, instead of the date 2021-04-06.
use tokio_postgres::{Error, NoTls};
#[tokio::main]
async fn main() -> Result<(), Error> {
let (client, connection) = tokio_postgres::connect(
"dbname=database user=admin password=postgres host=db port=5432",
NoTls,
)
.await?;
// Spawn the connector in a separate async task
tokio::spawn(async move {
if let Err(e) = connection.await {
eprintln!("connection error: {}", e);
}
});
// How do I pass several parameters correctly here?
client
.query(
"INSERT INTO table_name (url, date_added) \
VALUES ('$1', '$2');",
&[&url, &"2021-04-06"],
)
.await?;
}

Related

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.

Succesfully return a connection instance MongoDb - Rust

I'm learning rust, and I have been following the rust book. Usually, I try to implement something to strengthen my knowledge.
Recently I was looking into how to use the MongoDb create to create an instace and return the name of the database.
Here's a sample of what I have:
A folder containing a implementation to build a mongo connection string.
A .env file
A main function to get the info I need.
mongo_connection.rs
#[derive(Debug)]
pub struct ConnectionString {
pub username: String,
pub password: String,
pub cluster: String,
}
impl ConnectionString {
pub fn build_connection_string() -> String {
return format!("mongodb+srv://{}:{}#a{}.k1jklnx.mongodb.net/?retryWrites=true&w=majority",
Self.username, Self.password, Self.cluster)
}
}
main.rs
mod database;
use crate::database::mongo_connection;
use mongodb::{Client, options::ClientOptions};
use std::error::Error;
use dotenv::dotenv;
use std::env;
use tokio;
async fn create_database_connection() -> Client {
dotenv().ok(); //Loading environment variables from .env file
let connection_parameters = mongo_connection::ConnectionString{
username: env::var("USERNAME").expect("No username found on .env"),
password: env::var("PASSWORD").expect("No password found on .env"),
cluster: env::var("CLUSTER").expect("No cluster found on .env")
};
let mut url: String = mongo_connection::ConnectionString::build_connection_string();
println!("{}", url);
let options = ClientOptions::parse(&url).await?;
return Client::with_options(options).await;
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let client = create_database_connection().await?;
let db = client.database(&"runt");
println!("{:?}", db.name());
Ok(())
}
The problem I am facing is that I need to return a connection client. But I keep getting the Error:
let options = ClientOptions::parse(&url).await?;
^ cannot use the `?` operator in an async function that returns `Client`
If I change the result of the function to something like: Result<Client, dyn Eq> then I get the error:
async fn create_database_connection() -> Result<Client, dyn Eq> {
`Eq` cannot be made into an object
^^^^^^^^^^^^^^^ the trait cannot be made into an object because it uses `Self` as a type parameter
|
Any advice on how to solve the error or any workaround is much appreciated.
You could change the return type to either Result<Client, mongodb::error::Error> or Result<Client, Box<dyn Error>>. Either should work, but here's the code with the first version:
async fn create_database_connection() -> Result<Client, mongodb::error::Error> {
dotenv().ok(); //Loading environment variables from .env file
let connection_parameters = mongo_connection::ConnectionString{
username: env::var("USERNAME").expect("No username found on .env"),
password: env::var("PASSWORD").expect("No password found on .env"),
cluster: env::var("CLUSTER").expect("No cluster found on .env")
};
let mut url: String = mongo_connection::ConnectionString::build_connection_string();
println!("{}", url);
let options = ClientOptions::parse(&url).await?;
return Client::with_options(options).await;
}
In case you want to use the Box<dyn Error> version, the return value also needs a .into() to convert the Result<Client, mongodb::error::Error> of Client::with_options() into a Result<Client, Box<dyn Error>>.

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

tokio-postgres and database query

There is such a module code (for working with a database):
use tokio_postgres::{NoTls, Error};
pub async fn hello() -> Result<(), Error> {
// Connect to the database.
let (client, connection) =
tokio_postgres::connect("host=localhost user=postgres", NoTls).await?;
// The connection object performs the actual communication with the database,
// so spawn it off to run on its own.
tokio::spawn(async move {
if let Err(e) = connection.await {
eprintln!("connection error: {}", e);
}
});
// Now we can execute a simple statement that just returns its parameter.
let rows = client
.query("SELECT $1::TEXT", &[&"hello world"])
.await?;
// And then check that we got back the same string we sent over.
let value: &str = rows[0].get(0);
assert_eq!(value, "hello world");
Ok(())
}
Question:
How, in this case, the access to the database should be written?
(the guide doesn't say anything about it - or I didn't fully understand it.)
https://docs.rs/tokio-postgres/0.5.5/tokio_postgres/
What mechanisms in this case will protect access to the database from sql injections?
The simplest general use case is needed.
client.query(statement, params) will convert the first argument statement to a prepared statement and execute it with the params.
To be safe from sql injection, make sure that all user data is passed in the second params argument.
DO NOT DO THIS:
let id = "SOME DATA FROM THE USER";
let rows = client
.query(format!("SELECT * FROM SomeTable WHERE id = {}", id), &[])
.await?;
DO THIS:
let id = "SOME DATA FROM THE USER";
let rows = client
.query("SELECT * FROM SomeTable WHERE id = $1", &[&id])
.await?;
Explanation:
In tokio-postgres most client methods (query* or execute*) can accept either a &str or Statement for the sql statement. If passed a &str it will create a prepared statement (Statement object) for you.

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.