From c1f17c75d24c76ce71df3048f83e24cdb260f65f Mon Sep 17 00:00:00 2001 From: Jesper Axelsson Date: Fri, 17 Apr 2020 09:38:40 +0200 Subject: [PATCH] Add suport for sqlite --- cargo-sqlx/src/database_migrator.rs | 4 +- cargo-sqlx/src/main.rs | 9 +- cargo-sqlx/src/postgres.rs | 12 +- cargo-sqlx/src/sqlite.rs | 229 +++++++++++++++++----------- 4 files changed, 156 insertions(+), 98 deletions(-) diff --git a/cargo-sqlx/src/database_migrator.rs b/cargo-sqlx/src/database_migrator.rs index 9c74e15f..4dd420a6 100644 --- a/cargo-sqlx/src/database_migrator.rs +++ b/cargo-sqlx/src/database_migrator.rs @@ -2,7 +2,7 @@ use anyhow::Result; use async_trait::async_trait; #[async_trait] -pub trait MigTrans { +pub trait MigrationTransaction { async fn commit(self: Box) -> Result<()>; async fn rollback(self: Box) -> Result<()>; async fn check_if_applied(&mut self, migration: &str) -> Result; @@ -28,5 +28,5 @@ pub trait DatabaseMigrator { // Migration async fn create_migration_table(&self) -> Result<()>; - async fn begin_migration(&self) -> Result>; + async fn begin_migration(&self) -> Result>; } diff --git a/cargo-sqlx/src/main.rs b/cargo-sqlx/src/main.rs index 038d1280..22cf2f39 100644 --- a/cargo-sqlx/src/main.rs +++ b/cargo-sqlx/src/main.rs @@ -12,11 +12,11 @@ use anyhow::{anyhow, Context, Result}; mod database_migrator; mod postgres; -// mod sqlite; +mod sqlite; use database_migrator::DatabaseMigrator; use postgres::Postgres; -// use sqlite::Sqlite; +use sqlite::Sqlite; const MIGRATION_FOLDER: &'static str = "migrations"; @@ -63,8 +63,7 @@ async fn main() -> Result<()> { // This code is taken from: https://github.com/launchbadge/sqlx/blob/master/sqlx-macros/src/lib.rs#L63 match db_url.scheme() { #[cfg(feature = "sqlite")] - // "sqlite" => run_command(&Sqlite { db_url: &db_url_raw }).await?, - "sqlite" => return Err(anyhow!("error")), + "sqlite" => run_command(&Sqlite::new(db_url_raw )).await?, #[cfg(not(feature = "sqlite"))] "sqlite" => return Err(anyhow!("Not implemented. DATABASE_URL {} has the scheme of a SQLite database but the `sqlite` feature of sqlx was not enabled", db_url)), @@ -110,7 +109,7 @@ async fn run_command(db_creator: &dyn DatabaseMigrator) -> Result<()> { async fn run_create_database(db_creator: &dyn DatabaseMigrator) -> Result<()> { if !db_creator.can_create_database() { return Err(anyhow!( - "Database drop is not implemented for {}", + "Database creation is not implemented for {}", db_creator.database_type() )); } diff --git a/cargo-sqlx/src/postgres.rs b/cargo-sqlx/src/postgres.rs index fd5bcc93..d3e92c0d 100644 --- a/cargo-sqlx/src/postgres.rs +++ b/cargo-sqlx/src/postgres.rs @@ -9,7 +9,7 @@ use sqlx::Row; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; -use crate::database_migrator::{DatabaseMigrator, MigTrans}; +use crate::database_migrator::{DatabaseMigrator, MigrationTransaction}; pub struct Postgres { pub db_url: String, @@ -130,23 +130,23 @@ impl DatabaseMigrator for Postgres { Ok(()) } - async fn begin_migration(&self) -> Result> { + async fn begin_migration(&self) -> Result> { let pool = PgPool::new(&self.db_url) .await .context("Failed to connect to pool")?; let tx = pool.begin().await?; - Ok(Box::new(MigTransaction { transaction: tx })) + Ok(Box::new(PostgresMigration { transaction: tx })) } } -pub struct MigTransaction { - pub transaction: sqlx::Transaction>, +pub struct PostgresMigration { + transaction: sqlx::Transaction>, } #[async_trait] -impl MigTrans for MigTransaction { +impl MigrationTransaction for PostgresMigration { async fn commit(self: Box) -> Result<()> { self.transaction.commit().await?; Ok(()) diff --git a/cargo-sqlx/src/sqlite.rs b/cargo-sqlx/src/sqlite.rs index aa54de20..74ab5eaa 100644 --- a/cargo-sqlx/src/sqlite.rs +++ b/cargo-sqlx/src/sqlite.rs @@ -1,108 +1,167 @@ -use sqlx::postgres::PgRow; +// use sqlx::pool::PoolConnection; +use sqlx::sqlite::SqliteRow; use sqlx::Connect; -use sqlx::PgConnection; +use sqlx::Executor; use sqlx::Row; +use sqlx::SqliteConnection; +// use sqlx::SqlitePool; -use async_trait::async_trait; use anyhow::{anyhow, Context, Result}; +use async_trait::async_trait; -use crate::database_migrator::DatabaseMigrator; +use crate::database_migrator::{DatabaseMigrator, MigrationTransaction}; -pub struct Sqlite<'a> { - pub db_url: &'a str, +pub struct Sqlite { + db_url: String, + path: String, } -struct DbUrl<'a> { - base_url: &'a str, - db_name: &'a str, +impl Sqlite { + pub fn new(db_url: String) -> Self { + let path = crop_letters(&db_url, "sqlite://".len()); + Sqlite { + db_url: db_url.clone(), + path: path.to_string(), + } + } } -fn get_base_url<'a>(db_url: &'a str) -> Result { - let split: Vec<&str> = db_url.rsplitn(2, '/').collect(); +fn crop_letters(s: &str, pos: usize) -> &str { + match s.char_indices().skip(pos).next() { + Some((pos, _)) => &s[pos..], + None => "", + } +} - if split.len() != 2 { - return Err(anyhow!("Failed to find database name in connection string")); +#[async_trait] +impl DatabaseMigrator for Sqlite { + fn database_type(&self) -> String { + "Sqlite".to_string() } - let db_name = split[0]; - let base_url = split[1]; + fn can_migrate_database(&self) -> bool { + true + } - Ok(DbUrl { base_url, db_name }) + fn can_create_database(&self) -> bool { + true + } + + fn can_drop_database(&self) -> bool { + true + } + + fn get_database_name(&self) -> Result { + let split: Vec<&str> = self.db_url.rsplitn(2, '/').collect(); + + if split.len() != 2 { + return Err(anyhow!("Failed to find database name in connection string")); + } + + let db_name = split[0]; + + Ok(db_name.to_string()) + } + + async fn check_if_database_exists(&self, _db_name: &str) -> Result { + use std::path::Path; + Ok(Path::new(&self.path).exists()) + } + + async fn create_database(&self, _db_name: &str) -> Result<()> { + use std::fs::OpenOptions; + + println!("DB {}", self.path); + + OpenOptions::new() + .write(true) + .create_new(true) + .open(&self.path)?; + + Ok(()) + } + + async fn drop_database(&self, _db_name: &str) -> Result<()> { + std::fs::remove_file(&self.path)?; + Ok(()) + } + + async fn create_migration_table(&self) -> Result<()> { + let mut conn = SqliteConnection::connect(&self.db_url).await?; + + sqlx::query( + r#" + CREATE TABLE IF NOT EXISTS __migrations ( + migration VARCHAR (255) PRIMARY KEY, + created TIMESTAMP NOT NULL DEFAULT current_timestamp + ); + "#, + ) + .execute(&mut conn) + .await + .context("Failed to create migration table")?; + + Ok(()) + } + + // + async fn begin_migration(&self) -> Result> { + // let pool = SqlitePool::new(&self.db_url) + // .await + // .context("Failed to connect to pool")?; + + // let tx = pool.begin().await?; + + // Ok(Box::new(MigrationTransaction { transaction: tx })) + Ok(Box::new(SqliteMigration { + db_url: self.db_url.clone(), + })) + } } +pub struct SqliteMigration { + db_url: String, + // pub transaction: sqlx::Transaction>, +} -// #[async_trait] -// impl DatabaseMigrator for Sqlite<'_> { -// fn database_type(&self) -> String { -// "Sqlite".to_string() -// } +#[async_trait] +impl MigrationTransaction for SqliteMigration { + async fn commit(self: Box) -> Result<()> { + // self.transaction.commit().await?; + Ok(()) + } -// fn can_migrate_database(&self) -> bool { -// false -// } + async fn rollback(self: Box) -> Result<()> { + // self.transaction.rollback().await?; + Ok(()) + } -// fn can_create_database(&self) -> bool { -// false -// } + async fn check_if_applied(&mut self, migration_name: &str) -> Result { + let mut conn = SqliteConnection::connect(&self.db_url).await?; -// fn can_drop_database(&self) -> bool { -// false -// } + let result = + sqlx::query("select exists(select migration from __migrations where migration = $1)") + .bind(migration_name.to_string()) + .try_map(|row: SqliteRow| row.try_get(0)) + .fetch_one(&mut conn) + .await?; -// fn get_database_name(&self) -> Result { -// let db_url = get_base_url(self.db_url)?; -// Ok(db_url.db_name.to_string()) -// } + Ok(result) + } -// async fn check_if_database_exists(&self, db_name: &str) -> Result { -// let db_url = get_base_url(self.db_url)?; + async fn execute_migration(&mut self, migration_sql: &str) -> Result<()> { + let mut conn = SqliteConnection::connect(&self.db_url).await?; + conn.execute(migration_sql).await?; + // self.transaction.execute(migration_sql).await?; + Ok(()) + } -// let base_url = db_url.base_url; - -// let mut conn = PgConnection::connect(base_url).await?; - -// let result: bool = -// sqlx::query("select exists(SELECT 1 from pg_database WHERE datname = $1) as exists") -// .bind(db_name) -// .try_map(|row: PgRow| row.try_get("exists")) -// .fetch_one(&mut conn) -// .await -// .context("Failed to check if database exists")?; - -// Ok(result) -// } - -// async fn create_database(&self, db_name: &str) -> Result<()> { -// let db_url = get_base_url(self.db_url)?; - -// let base_url = db_url.base_url; - -// let mut conn = PgConnection::connect(base_url).await?; - -// sqlx::query(&format!("CREATE DATABASE {}", db_name)) -// .execute(&mut conn) -// .await -// .with_context(|| format!("Failed to create database: {}", db_name))?; - -// Ok(()) -// } - -// async fn drop_database(&self, db_name: &str) -> Result<()> { -// let db_url = get_base_url(self.db_url)?; - -// let base_url = db_url.base_url; - -// let mut conn = PgConnection::connect(base_url).await?; - -// sqlx::query(&format!("DROP DATABASE {}", db_name)) -// .execute(&mut conn) -// .await -// .with_context(|| format!("Failed to create database: {}", db_name))?; - -// Ok(()) -// } - -// async fn create_migration_table(&self) -> Result<()> { -// Err(anyhow!("Not supported")) -// } -// } + async fn save_applied_migration(&mut self, migration_name: &str) -> Result<()> { + let mut conn = SqliteConnection::connect(&self.db_url).await?; + sqlx::query("insert into __migrations (migration) values ($1)") + .bind(migration_name.to_string()) + .execute(&mut conn) + .await?; + Ok(()) + } +}