From ae03d1fcdb11d0fd94d495713b962fe8714bd63e Mon Sep 17 00:00:00 2001 From: ThouCheese Date: Tue, 21 Nov 2023 15:44:31 +0100 Subject: [PATCH] Modify todo example to use postgres and diesel_async --- examples/todo/Cargo.toml | 8 +++--- examples/todo/Rocket.toml | 6 +++-- examples/todo/db/DB_LIVES_HERE | 1 + examples/todo/src/main.rs | 47 +++++++++++++++++++--------------- examples/todo/src/task.rs | 41 +++++++++++++---------------- examples/todo/src/tests.rs | 29 ++++++++++++--------- 6 files changed, 70 insertions(+), 62 deletions(-) diff --git a/examples/todo/Cargo.toml b/examples/todo/Cargo.toml index 3aac187464..086b3a1a05 100644 --- a/examples/todo/Cargo.toml +++ b/examples/todo/Cargo.toml @@ -7,16 +7,16 @@ publish = false [dependencies] rocket = { path = "../../core/lib" } -diesel = { version = "2.0.0", features = ["sqlite", "r2d2"] } +diesel = { version = "2.0.0", features = ["postgres", "r2d2"] } diesel_migrations = "2.0.0" [dev-dependencies] parking_lot = "0.12" rand = "0.8" -[dependencies.rocket_sync_db_pools] -path = "../../contrib/sync_db_pools/lib/" -features = ["diesel_sqlite_pool"] +[dependencies.rocket_db_pools] +path = "../../contrib/db_pools/lib/" +features = ["diesel_postgres"] [dependencies.rocket_dyn_templates] path = "../../contrib/dyn_templates" diff --git a/examples/todo/Rocket.toml b/examples/todo/Rocket.toml index 48620aa16d..1a6dc1e002 100644 --- a/examples/todo/Rocket.toml +++ b/examples/todo/Rocket.toml @@ -1,5 +1,7 @@ [default] template_dir = "static" -[default.databases.sqlite_database] -url = "db/db.sqlite" +[default.databases.epic_todo_database] +url = "postgresql://postgres@localhost:5432/epic_todo_database" +max_connections = 1 +connect_timeout = 5 diff --git a/examples/todo/db/DB_LIVES_HERE b/examples/todo/db/DB_LIVES_HERE index e69de29bb2..1ab912506a 100644 --- a/examples/todo/db/DB_LIVES_HERE +++ b/examples/todo/db/DB_LIVES_HERE @@ -0,0 +1 @@ +db does not live here :( diff --git a/examples/todo/src/main.rs b/examples/todo/src/main.rs index 96763545aa..599534a95a 100644 --- a/examples/todo/src/main.rs +++ b/examples/todo/src/main.rs @@ -1,6 +1,4 @@ #[macro_use] extern crate rocket; -#[macro_use] extern crate rocket_sync_db_pools; -#[macro_use] extern crate diesel; #[cfg(test)] mod tests; @@ -13,13 +11,15 @@ use rocket::response::{Flash, Redirect}; use rocket::serde::Serialize; use rocket::form::Form; use rocket::fs::{FileServer, relative}; +use rocket_db_pools::{Connection, Database}; use rocket_dyn_templates::Template; use crate::task::{Task, Todo}; -#[database("sqlite_database")] -pub struct DbConn(diesel::SqliteConnection); +#[derive(Database)] +#[database("epic_todo_database")] +pub struct Db(rocket_db_pools::diesel::PgPool); #[derive(Debug, Serialize)] #[serde(crate = "rocket::serde")] @@ -29,14 +29,14 @@ struct Context { } impl Context { - pub async fn err(conn: &DbConn, msg: M) -> Context { + pub async fn err(conn: &mut Connection, msg: M) -> Context { Context { flash: Some(("error".into(), msg.to_string())), tasks: Task::all(conn).await.unwrap_or_default() } } - pub async fn raw(conn: &DbConn, flash: Option<(String, String)>) -> Context { + pub async fn raw(conn: &mut Connection, flash: Option<(String, String)>) -> Context { match Task::all(conn).await { Ok(tasks) => Context { flash, tasks }, Err(e) => { @@ -51,11 +51,11 @@ impl Context { } #[post("/", data = "")] -async fn new(todo_form: Form, conn: DbConn) -> Flash { +async fn new(todo_form: Form, mut conn: Connection) -> Flash { let todo = todo_form.into_inner(); if todo.description.is_empty() { Flash::error(Redirect::to("/"), "Description cannot be empty.") - } else if let Err(e) = Task::insert(todo, &conn).await { + } else if let Err(e) = Task::insert(todo, &mut conn).await { error_!("DB insertion error: {}", e); Flash::error(Redirect::to("/"), "Todo could not be inserted due an internal error.") } else { @@ -64,42 +64,47 @@ async fn new(todo_form: Form, conn: DbConn) -> Flash { } #[put("/")] -async fn toggle(id: i32, conn: DbConn) -> Result { - match Task::toggle_with_id(id, &conn).await { +async fn toggle(id: i32, mut conn: Connection) -> Result { + match Task::toggle_with_id(id, &mut conn).await { Ok(_) => Ok(Redirect::to("/")), Err(e) => { error_!("DB toggle({}) error: {}", id, e); - Err(Template::render("index", Context::err(&conn, "Failed to toggle task.").await)) + Err(Template::render("index", Context::err(&mut conn, "Failed to toggle task.").await)) } } } #[delete("/")] -async fn delete(id: i32, conn: DbConn) -> Result, Template> { - match Task::delete_with_id(id, &conn).await { +async fn delete(id: i32, mut conn: Connection) -> Result, Template> { + match Task::delete_with_id(id, &mut conn).await { Ok(_) => Ok(Flash::success(Redirect::to("/"), "Todo was deleted.")), Err(e) => { error_!("DB deletion({}) error: {}", id, e); - Err(Template::render("index", Context::err(&conn, "Failed to delete task.").await)) + Err(Template::render("index", Context::err(&mut conn, "Failed to delete task.").await)) } } } #[get("/")] -async fn index(flash: Option>, conn: DbConn) -> Template { +async fn index(flash: Option>, mut conn: Connection) -> Template { let flash = flash.map(FlashMessage::into_inner); - Template::render("index", Context::raw(&conn, flash).await) + Template::render("index", Context::raw(&mut conn, flash).await) } async fn run_migrations(rocket: Rocket) -> Rocket { + use diesel::Connection; use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations"); + let config: rocket_db_pools::Config = dbg!(rocket.figment()).extract_inner("databases.epic_todo_database").expect("Db not configured"); - DbConn::get_one(&rocket).await - .expect("database connection") - .run(|conn| { conn.run_pending_migrations(MIGRATIONS).expect("diesel migrations"); }) - .await; + rocket::tokio::task::spawn_blocking(move || { + diesel::PgConnection::establish(&config.url) + .expect("No database") + .run_pending_migrations(MIGRATIONS) + .expect("Invalid migrations"); + }) + .await.expect("tokio doesn't work"); rocket } @@ -107,7 +112,7 @@ async fn run_migrations(rocket: Rocket) -> Rocket { #[launch] fn rocket() -> _ { rocket::build() - .attach(DbConn::fairing()) + .attach(Db::init()) .attach(Template::fairing()) .attach(AdHoc::on_ignite("Run Migrations", run_migrations)) .mount("/", FileServer::from(relative!("static"))) diff --git a/examples/todo/src/task.rs b/examples/todo/src/task.rs index eb93ffe74c..976063b9de 100644 --- a/examples/todo/src/task.rs +++ b/examples/todo/src/task.rs @@ -1,8 +1,9 @@ use rocket::serde::Serialize; use diesel::{self, result::QueryResult, prelude::*}; +use rocket_db_pools::diesel::RunQueryDsl; mod schema { - table! { + diesel::table! { tasks { id -> Nullable, description -> Text, @@ -13,7 +14,7 @@ mod schema { use self::schema::tasks; -use crate::DbConn; +type DbConn = rocket_db_pools::diesel::AsyncPgConnection; #[derive(Serialize, Queryable, Insertable, Debug, Clone)] #[serde(crate = "rocket::serde")] @@ -31,41 +32,35 @@ pub struct Todo { } impl Task { - pub async fn all(conn: &DbConn) -> QueryResult> { - conn.run(|c| { - tasks::table.order(tasks::id.desc()).load::(c) - }).await + pub async fn all(conn: &mut DbConn) -> QueryResult> { + tasks::table.order(tasks::id.desc()).load::(conn).await } /// Returns the number of affected rows: 1. - pub async fn insert(todo: Todo, conn: &DbConn) -> QueryResult { - conn.run(|c| { - let t = Task { id: None, description: todo.description, completed: false }; - diesel::insert_into(tasks::table).values(&t).execute(c) - }).await + pub async fn insert(todo: Todo, conn: &mut DbConn) -> QueryResult { + let t = Task { id: None, description: todo.description, completed: false }; + diesel::insert_into(tasks::table).values(&t).execute(conn).await } /// Returns the number of affected rows: 1. - pub async fn toggle_with_id(id: i32, conn: &DbConn) -> QueryResult { - conn.run(move |c| { - let task = tasks::table.filter(tasks::id.eq(id)).get_result::(c)?; - let new_status = !task.completed; - let updated_task = diesel::update(tasks::table.filter(tasks::id.eq(id))); - updated_task.set(tasks::completed.eq(new_status)).execute(c) - }).await + pub async fn toggle_with_id(id: i32, conn: &mut DbConn) -> QueryResult { + let task = tasks::table.filter(tasks::id.eq(id)).get_result::(conn).await?; + let new_status = !task.completed; + let updated_task = diesel::update(tasks::table.filter(tasks::id.eq(id))); + updated_task.set(tasks::completed.eq(new_status)).execute(conn).await } /// Returns the number of affected rows: 1. - pub async fn delete_with_id(id: i32, conn: &DbConn) -> QueryResult { - conn.run(move |c| diesel::delete(tasks::table) + pub async fn delete_with_id(id: i32, conn: &mut DbConn) -> QueryResult { + diesel::delete(tasks::table) .filter(tasks::id.eq(id)) - .execute(c)) + .execute(conn) .await } /// Returns the number of affected rows. #[cfg(test)] - pub async fn delete_all(conn: &DbConn) -> QueryResult { - conn.run(|c| diesel::delete(tasks::table).execute(c)).await + pub async fn delete_all(conn: &mut DbConn) -> QueryResult { + diesel::delete(tasks::table).execute(conn).await } } diff --git a/examples/todo/src/tests.rs b/examples/todo/src/tests.rs index 5c2eadd7bd..4f185c1b84 100644 --- a/examples/todo/src/tests.rs +++ b/examples/todo/src/tests.rs @@ -4,6 +4,7 @@ use rand::{Rng, thread_rng, distributions::Alphanumeric}; use rocket::local::asynchronous::Client; use rocket::http::{Status, ContentType}; +use rocket_db_pools::Database; // We use a lock to synchronize between tests so DB operations don't collide. // For now. In the future, we'll have a nice way to run each test in a DB @@ -15,10 +16,14 @@ macro_rules! run_test { let _lock = DB_LOCK.lock(); rocket::async_test(async move { - let $client = Client::tracked(super::rocket()).await.expect("Rocket client"); - let db = super::DbConn::get_one($client.rocket()).await; - let $conn = db.expect("failed to get database connection for testing"); - Task::delete_all(&$conn).await.expect("failed to delete all tasks for testing"); + let rocket = super::rocket(); + let mut $conn = super::Db::fetch(&rocket) + .expect("database") + .get() + .await + .expect("database connection"); + let $client = Client::tracked(rocket).await.expect("Rocket client"); + Task::delete_all(&mut $conn).await.expect("failed to delete all tasks for testing"); $block }) @@ -39,7 +44,7 @@ fn test_index() { fn test_insertion_deletion() { run_test!(|client, conn| { // Get the tasks before making changes. - let init_tasks = Task::all(&conn).await.unwrap(); + let init_tasks = Task::all(&mut conn).await.unwrap(); // Issue a request to insert a new task. client.post("/todo") @@ -49,7 +54,7 @@ fn test_insertion_deletion() { .await; // Ensure we have one more task in the database. - let new_tasks = Task::all(&conn).await.unwrap(); + let new_tasks = Task::all(&mut conn).await.unwrap(); assert_eq!(new_tasks.len(), init_tasks.len() + 1); // Ensure the task is what we expect. @@ -61,7 +66,7 @@ fn test_insertion_deletion() { client.delete(format!("/todo/{}", id)).dispatch().await; // Ensure it's gone. - let final_tasks = Task::all(&conn).await.unwrap(); + let final_tasks = Task::all(&mut conn).await.unwrap(); assert_eq!(final_tasks.len(), init_tasks.len()); if final_tasks.len() > 0 { assert_ne!(final_tasks[0].description, "My first task"); @@ -79,16 +84,16 @@ fn test_toggle() { .dispatch() .await; - let task = Task::all(&conn).await.unwrap()[0].clone(); + let task = Task::all(&mut conn).await.unwrap()[0].clone(); assert_eq!(task.completed, false); // Issue a request to toggle the task; ensure it is completed. client.put(format!("/todo/{}", task.id.unwrap())).dispatch().await; - assert_eq!(Task::all(&conn).await.unwrap()[0].completed, true); + assert_eq!(Task::all(&mut conn).await.unwrap()[0].completed, true); // Issue a request to toggle the task; ensure it's not completed again. client.put(format!("/todo/{}", task.id.unwrap())).dispatch().await; - assert_eq!(Task::all(&conn).await.unwrap()[0].completed, false); + assert_eq!(Task::all(&mut conn).await.unwrap()[0].completed, false); }) } @@ -98,7 +103,7 @@ fn test_many_insertions() { run_test!(|client, conn| { // Get the number of tasks initially. - let init_num = Task::all(&conn).await.unwrap().len(); + let init_num = Task::all(&mut conn).await.unwrap().len(); let mut descs = Vec::new(); for i in 0..ITER { @@ -119,7 +124,7 @@ fn test_many_insertions() { descs.insert(0, desc); // Ensure the task was inserted properly and all other tasks remain. - let tasks = Task::all(&conn).await.unwrap(); + let tasks = Task::all(&mut conn).await.unwrap(); assert_eq!(tasks.len(), init_num + i + 1); for j in 0..i {