Add suport for sqlite

This commit is contained in:
Jesper Axelsson 2020-04-17 09:38:40 +02:00 committed by Ryan Leckey
parent 961ade18d0
commit c1f17c75d2
4 changed files with 156 additions and 98 deletions

View File

@ -2,7 +2,7 @@ use anyhow::Result;
use async_trait::async_trait; use async_trait::async_trait;
#[async_trait] #[async_trait]
pub trait MigTrans { pub trait MigrationTransaction {
async fn commit(self: Box<Self>) -> Result<()>; async fn commit(self: Box<Self>) -> Result<()>;
async fn rollback(self: Box<Self>) -> Result<()>; async fn rollback(self: Box<Self>) -> Result<()>;
async fn check_if_applied(&mut self, migration: &str) -> Result<bool>; async fn check_if_applied(&mut self, migration: &str) -> Result<bool>;
@ -28,5 +28,5 @@ pub trait DatabaseMigrator {
// Migration // Migration
async fn create_migration_table(&self) -> Result<()>; async fn create_migration_table(&self) -> Result<()>;
async fn begin_migration(&self) -> Result<Box<dyn MigTrans>>; async fn begin_migration(&self) -> Result<Box<dyn MigrationTransaction>>;
} }

View File

@ -12,11 +12,11 @@ use anyhow::{anyhow, Context, Result};
mod database_migrator; mod database_migrator;
mod postgres; mod postgres;
// mod sqlite; mod sqlite;
use database_migrator::DatabaseMigrator; use database_migrator::DatabaseMigrator;
use postgres::Postgres; use postgres::Postgres;
// use sqlite::Sqlite; use sqlite::Sqlite;
const MIGRATION_FOLDER: &'static str = "migrations"; 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 // This code is taken from: https://github.com/launchbadge/sqlx/blob/master/sqlx-macros/src/lib.rs#L63
match db_url.scheme() { match db_url.scheme() {
#[cfg(feature = "sqlite")] #[cfg(feature = "sqlite")]
// "sqlite" => run_command(&Sqlite { db_url: &db_url_raw }).await?, "sqlite" => run_command(&Sqlite::new(db_url_raw )).await?,
"sqlite" => return Err(anyhow!("error")),
#[cfg(not(feature = "sqlite"))] #[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", "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)), 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<()> { async fn run_create_database(db_creator: &dyn DatabaseMigrator) -> Result<()> {
if !db_creator.can_create_database() { if !db_creator.can_create_database() {
return Err(anyhow!( return Err(anyhow!(
"Database drop is not implemented for {}", "Database creation is not implemented for {}",
db_creator.database_type() db_creator.database_type()
)); ));
} }

View File

@ -9,7 +9,7 @@ use sqlx::Row;
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use async_trait::async_trait; use async_trait::async_trait;
use crate::database_migrator::{DatabaseMigrator, MigTrans}; use crate::database_migrator::{DatabaseMigrator, MigrationTransaction};
pub struct Postgres { pub struct Postgres {
pub db_url: String, pub db_url: String,
@ -130,23 +130,23 @@ impl DatabaseMigrator for Postgres {
Ok(()) Ok(())
} }
async fn begin_migration(&self) -> Result<Box<dyn MigTrans>> { async fn begin_migration(&self) -> Result<Box<dyn MigrationTransaction>> {
let pool = PgPool::new(&self.db_url) let pool = PgPool::new(&self.db_url)
.await .await
.context("Failed to connect to pool")?; .context("Failed to connect to pool")?;
let tx = pool.begin().await?; let tx = pool.begin().await?;
Ok(Box::new(MigTransaction { transaction: tx })) Ok(Box::new(PostgresMigration { transaction: tx }))
} }
} }
pub struct MigTransaction { pub struct PostgresMigration {
pub transaction: sqlx::Transaction<PoolConnection<PgConnection>>, transaction: sqlx::Transaction<PoolConnection<PgConnection>>,
} }
#[async_trait] #[async_trait]
impl MigTrans for MigTransaction { impl MigrationTransaction for PostgresMigration {
async fn commit(self: Box<Self>) -> Result<()> { async fn commit(self: Box<Self>) -> Result<()> {
self.transaction.commit().await?; self.transaction.commit().await?;
Ok(()) Ok(())

View File

@ -1,108 +1,167 @@
use sqlx::postgres::PgRow; // use sqlx::pool::PoolConnection;
use sqlx::sqlite::SqliteRow;
use sqlx::Connect; use sqlx::Connect;
use sqlx::PgConnection; use sqlx::Executor;
use sqlx::Row; use sqlx::Row;
use sqlx::SqliteConnection;
// use sqlx::SqlitePool;
use async_trait::async_trait;
use anyhow::{anyhow, Context, Result}; 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 struct Sqlite {
pub db_url: &'a str, db_url: String,
path: String,
} }
struct DbUrl<'a> { impl Sqlite {
base_url: &'a str, pub fn new(db_url: String) -> Self {
db_name: &'a str, 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<DbUrl> { fn crop_letters(s: &str, pos: usize) -> &str {
let split: Vec<&str> = db_url.rsplitn(2, '/').collect(); match s.char_indices().skip(pos).next() {
Some((pos, _)) => &s[pos..],
None => "",
}
}
if split.len() != 2 { #[async_trait]
return Err(anyhow!("Failed to find database name in connection string")); impl DatabaseMigrator for Sqlite {
fn database_type(&self) -> String {
"Sqlite".to_string()
} }
let db_name = split[0]; fn can_migrate_database(&self) -> bool {
let base_url = split[1]; 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<String> {
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<bool> {
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<Box<dyn MigrationTransaction>> {
// 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<PoolConnection<SqliteConnection>>,
}
// #[async_trait] #[async_trait]
// impl DatabaseMigrator for Sqlite<'_> { impl MigrationTransaction for SqliteMigration {
// fn database_type(&self) -> String { async fn commit(self: Box<Self>) -> Result<()> {
// "Sqlite".to_string() // self.transaction.commit().await?;
// } Ok(())
}
// fn can_migrate_database(&self) -> bool { async fn rollback(self: Box<Self>) -> Result<()> {
// false // self.transaction.rollback().await?;
// } Ok(())
}
// fn can_create_database(&self) -> bool { async fn check_if_applied(&mut self, migration_name: &str) -> Result<bool> {
// false let mut conn = SqliteConnection::connect(&self.db_url).await?;
// }
// fn can_drop_database(&self) -> bool { let result =
// false 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<String> { Ok(result)
// let db_url = get_base_url(self.db_url)?; }
// Ok(db_url.db_name.to_string())
// }
// async fn check_if_database_exists(&self, db_name: &str) -> Result<bool> { async fn execute_migration(&mut self, migration_sql: &str) -> Result<()> {
// let db_url = get_base_url(self.db_url)?; 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; async fn save_applied_migration(&mut self, migration_name: &str) -> Result<()> {
let mut conn = SqliteConnection::connect(&self.db_url).await?;
// let mut conn = PgConnection::connect(base_url).await?; sqlx::query("insert into __migrations (migration) values ($1)")
.bind(migration_name.to_string())
// let result: bool = .execute(&mut conn)
// sqlx::query("select exists(SELECT 1 from pg_database WHERE datname = $1) as exists") .await?;
// .bind(db_name) Ok(())
// .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"))
// }
// }