From f60eb616d323ae35fff33e16aa62da7d047d0651 Mon Sep 17 00:00:00 2001 From: Philippe Loctaux Date: Mon, 27 Feb 2023 14:36:48 +0100 Subject: [PATCH] added database_pool crate, for pool handling and migrations --- .gitignore | 3 ++ Cargo.lock | 42 ++++++++++++++++++++++ Cargo.toml | 5 +++ crates/database_pool/Cargo.toml | 8 +++++ crates/database_pool/src/lib.rs | 50 ++++++++++++++++++++++++++ crates/database_pool/src/migrations.rs | 15 ++++++++ crates/database_pool/src/pool.rs | 29 +++++++++++++++ crates/ezidam/Cargo.toml | 3 ++ crates/ezidam/Rocket.toml | 2 ++ crates/ezidam/src/database.rs | 36 +++++++++++++++++++ crates/ezidam/src/main.rs | 4 +++ 11 files changed, 197 insertions(+) create mode 100644 crates/database_pool/Cargo.toml create mode 100644 crates/database_pool/src/lib.rs create mode 100644 crates/database_pool/src/migrations.rs create mode 100644 crates/database_pool/src/pool.rs create mode 100644 crates/ezidam/Rocket.toml create mode 100644 crates/ezidam/src/database.rs diff --git a/.gitignore b/.gitignore index d46a0bc..f1ef1ea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# database +/database + # rust /target diff --git a/Cargo.lock b/Cargo.lock index bfaacbb..e85e215 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -300,6 +300,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49fc9a695bca7f35f5f4c15cddc84415f66a74ea78eef08e90c5024f2b540e23" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403" + [[package]] name = "crossbeam-queue" version = "0.3.8" @@ -383,6 +398,21 @@ dependencies = [ "syn", ] +[[package]] +name = "database" +version = "0.0.0" +dependencies = [ + "sqlx", + "thiserror", +] + +[[package]] +name = "database_pool" +version = "0.0.0" +dependencies = [ + "sqlx", +] + [[package]] name = "deunicode" version = "0.4.3" @@ -444,6 +474,9 @@ name = "either" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +dependencies = [ + "serde", +] [[package]] name = "encoding_rs" @@ -485,6 +518,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" name = "ezidam" version = "0.1.0" dependencies = [ + "database_pool", "infer", "rocket", "rocket_db_pools", @@ -2058,6 +2092,8 @@ dependencies = [ "bitflags", "byteorder", "bytes", + "chrono", + "crc", "crossbeam-queue", "either", "event-listener", @@ -2079,6 +2115,8 @@ dependencies = [ "paste", "percent-encoding", "rustls", + "serde", + "sha2", "smallvec", "sqlformat", "sqlx-rt", @@ -2099,9 +2137,13 @@ dependencies = [ "dotenv", "either", "heck", + "hex", "once_cell", "proc-macro2", "quote", + "serde", + "serde_json", + "sha2", "sqlx-core", "sqlx-rt", "syn", diff --git a/Cargo.toml b/Cargo.toml index ed8b330..871b7f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,3 +3,8 @@ members = [ "crates/*" ] + +[workspace.dependencies] +thiserror = "1" +chrono = "0.4.23" +sqlx = "0.5.13" \ No newline at end of file diff --git a/crates/database_pool/Cargo.toml b/crates/database_pool/Cargo.toml new file mode 100644 index 0000000..322c793 --- /dev/null +++ b/crates/database_pool/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "database_pool" +version = "0.0.0" +edition = "2021" + +[dependencies.sqlx] +workspace = true +features = ["sqlite", "migrate", "runtime-tokio-rustls"] diff --git a/crates/database_pool/src/lib.rs b/crates/database_pool/src/lib.rs new file mode 100644 index 0000000..6c4de7a --- /dev/null +++ b/crates/database_pool/src/lib.rs @@ -0,0 +1,50 @@ +//! # Initialize database pool connection +//! +//! ``` +//! use database_pool::Pool; +//! +//! # async fn get_database() { +//! let database = Pool::init("/path/to/database.sqlite").await.expect("Failed to init pool"); +//! +//! // Close pool +//! database.close().await; +//! # } +//! ``` +//! +//! # Run migrations to pool +//! +//! ``` +//! use database_pool::Pool; +//! use database_pool::run_migrations; +//! +//! # async fn migrations() { +//! # let database = Pool::init("/path/to/database.sqlite").await.unwrap(); +//! run_migrations(&database.pool).await.expect("Failed to run migrations"); +//! # } +//! ``` +//! +//! # Run transaction on database +//! +//! ``` +//! use database_pool::Pool; +//! +//! # async fn run_transaction() { +//! # let database = Pool::init("/path/to/database.sqlite").await.unwrap(); +//! // Start transaction +//! let mut transaction = database.pool.begin().await.expect("Failed to start transaction"); +//! +//! // Run functions requiring database +//! +//! // Commit transaction +//! transaction.commit().await.expect("Failed to commit transaction"); +//! +//! // Do what you need :) +//! +//! # } +//! ``` + +mod migrations; +mod pool; + +pub use migrations::run_migrations; +pub use pool::Pool; diff --git a/crates/database_pool/src/migrations.rs b/crates/database_pool/src/migrations.rs new file mode 100644 index 0000000..7873def --- /dev/null +++ b/crates/database_pool/src/migrations.rs @@ -0,0 +1,15 @@ +use sqlx::migrate::MigrateError; +use sqlx::{Pool, Sqlite}; + +pub async fn run_migrations(pool: &Pool) -> Result<(), MigrateError> { + match sqlx::migrate!("../database").run(pool).await { + Ok(ok) => { + println!("Migrations are OK"); + Ok(ok) + } + Err(e) => { + eprintln!("Failed to run migrations!"); + Err(e) + } + } +} diff --git a/crates/database_pool/src/pool.rs b/crates/database_pool/src/pool.rs new file mode 100644 index 0000000..7de7b84 --- /dev/null +++ b/crates/database_pool/src/pool.rs @@ -0,0 +1,29 @@ +use sqlx::sqlite::SqlitePoolOptions; +use sqlx::{Error, Pool as SqlxPool, Sqlite}; + +pub struct Pool { + url: String, + pub pool: SqlxPool, +} + +impl Pool { + /// Initializes a new connection pool to database + /// + /// # Arguments + /// + /// * `url` - Path to SQLite database, must be full path + pub async fn init(url: impl Into) -> Result { + let url = url.into(); + let pool = SqlitePoolOptions::new().connect(&url).await?; + + println!("SQLite pool established for `{url}`"); + + Ok(Self { url, pool }) + } + + /// Close connection pool to database + pub async fn close(&self) { + self.pool.close().await; + println!("SQLite pool closed for `{}`", self.url); + } +} diff --git a/crates/ezidam/Cargo.toml b/crates/ezidam/Cargo.toml index 2b83288..8fb2050 100644 --- a/crates/ezidam/Cargo.toml +++ b/crates/ezidam/Cargo.toml @@ -8,3 +8,6 @@ rocket = "0.5.0-rc.2" rocket_db_pools = { version = "0.1.0-rc.2", features = ["sqlx_sqlite"] } rocket_dyn_templates = { version = "0.1.0-rc.2", features = ["tera"] } infer = { version = "0.12.0", default-features = false } + +# local crates +database_pool = { path = "../database_pool" } \ No newline at end of file diff --git a/crates/ezidam/Rocket.toml b/crates/ezidam/Rocket.toml new file mode 100644 index 0000000..1a7264a --- /dev/null +++ b/crates/ezidam/Rocket.toml @@ -0,0 +1,2 @@ +[default.databases.ezidam] +url = "../../database/ezidam.sqlite" diff --git a/crates/ezidam/src/database.rs b/crates/ezidam/src/database.rs new file mode 100644 index 0000000..03a6b1f --- /dev/null +++ b/crates/ezidam/src/database.rs @@ -0,0 +1,36 @@ +use database_pool::run_migrations; +use rocket::fairing::AdHoc; +use rocket::{error, fairing, info, Build, Rocket}; +use rocket_db_pools::{sqlx, Database as RocketDatabase}; + +#[derive(RocketDatabase)] +#[database("ezidam")] +pub struct Database(sqlx::SqlitePool); + +impl Database { + pub fn rocket(rocket_builder: Rocket) -> Rocket { + rocket_builder + // Init + .attach(Self::init()) + // SQL Migrations + .attach(AdHoc::try_on_ignite("SQL Startup", Self::startup)) + } + + async fn startup(rocket: Rocket) -> fairing::Result { + let Some(db) = Self::fetch(&rocket) else { + error!("Failed to get database"); + return Err(rocket); + }; + + match run_migrations(&db.0).await { + Ok(()) => { + info!("Migrations ran successfully"); + Ok(rocket) + } + Err(e) => { + error!("Failed to run migrations: {}", e); + Err(rocket) + } + } + } +} diff --git a/crates/ezidam/src/main.rs b/crates/ezidam/src/main.rs index ddb338a..f881159 100644 --- a/crates/ezidam/src/main.rs +++ b/crates/ezidam/src/main.rs @@ -1,3 +1,4 @@ +mod database; mod shutdown; // see for using rocket with main function https://github.com/intellij-rust/intellij-rust/issues/5975#issuecomment-920620289 @@ -6,6 +7,9 @@ async fn main() -> std::result::Result<(), rocket::Error> { // Build server let rocket_builder = rocket::build(); + // Database + let rocket_builder = database::Database::rocket(rocket_builder); + // Shutdown let rocket_builder = shutdown::shutdown(rocket_builder);