use async_trait::async_trait;
use barrel::Migration;
use sqlx::{any::AnyKind, any::AnyPool, Any, Database, Executor};
use std::str::FromStr;
use thiserror::Error;
pub use barrel::types;
pub mod prelude {
pub use crate::db;
pub use crate::table;
pub use crate::table::CreateTable;
pub use crate::types;
pub use crate::types::boolean;
pub use crate::types::date;
pub use crate::types::integer;
pub use crate::types::primary;
pub use crate::types::text;
pub use crate::types::varchar;
pub use crate::Statement;
}
pub mod db {
pub struct DropDatabase {}
pub struct CreateDatabase {}
pub fn drop() -> DropDatabase {
DropDatabase {}
}
pub fn create() -> CreateDatabase {
CreateDatabase {}
}
}
pub mod table {
use barrel::types::Type;
pub struct CreateTable {
pub(crate) name: String,
pub(crate) columns: Vec<(String, Type)>,
pub(crate) if_not_exists: bool,
}
impl CreateTable {
pub fn if_not_exists(mut self) -> Self {
self.if_not_exists = true;
self
}
pub fn column<N: Into<String>>(mut self, name: N, _type: Type) -> Self {
self.columns.push((name.into(), _type));
self
}
}
pub fn create<N: Into<String>>(name: N) -> CreateTable {
CreateTable {
name: name.into(),
columns: Vec::new(),
if_not_exists: false,
}
}
pub struct DropTable {
#[allow(dead_code)]
pub(crate) name: String,
pub(crate) if_exists: bool,
}
impl DropTable {
pub fn if_exists(mut self) -> Self {
self.if_exists = true;
self
}
}
pub fn drop<N: Into<String>>(name: N) -> DropTable {
DropTable {
name: name.into(),
if_exists: false,
}
}
}
#[derive(Debug, Error)]
enum Error {
#[error("Attempted to use an unsupported SQL dialect; available options are SQLite and MySQL")]
UnsupportedDialect,
}
#[async_trait]
pub trait Dialect {
fn dialect() -> AnyKind;
async fn execute_statement(&self, statement: &str) -> Result<(), sqlx::Error>;
}
#[async_trait]
impl Dialect for sqlx::Pool<sqlx::Sqlite> {
fn dialect() -> AnyKind {
AnyKind::Sqlite
}
async fn execute_statement(&self, statement: &str) -> Result<(), sqlx::Error> {
sqlx::query(statement).execute(self).await?;
Ok(())
}
}
#[async_trait]
impl Dialect for sqlx::Pool<sqlx::MySql> {
fn dialect() -> AnyKind {
AnyKind::MySql
}
async fn execute_statement(&self, statement: &str) -> Result<(), sqlx::Error> {
sqlx::query(statement).execute(self).await?;
Ok(())
}
}
#[async_trait]
pub trait Statement: Sized {
fn into_string(self, dialect: AnyKind) -> Result<String, sqlx::Error>;
async fn execute<D: Dialect + Sync + Send>(self, conn: D) -> Result<(), sqlx::Error> {
let statement = self.into_string(D::dialect())?;
conn.execute_statement(statement.as_str()).await
}
}
#[async_trait]
pub trait SystemStatement {
async fn execute(self, uri: &str) -> Result<(), sqlx::Error>;
}
#[async_trait]
impl SystemStatement for db::CreateDatabase {
async fn execute(self, uri: &str) -> Result<(), sqlx::Error> {
use sqlx::migrate::MigrateDatabase;
Any::create_database(uri).await?;
Ok(())
}
}
#[async_trait]
impl SystemStatement for db::DropDatabase {
async fn execute(self, uri: &str) -> Result<(), sqlx::Error> {
use sqlx::migrate::MigrateDatabase;
if Any::database_exists(uri).await? {
Any::drop_database(uri).await?;
}
return Ok(());
}
}
impl Statement for table::CreateTable {
fn into_string(self, dialect: AnyKind) -> Result<String, sqlx::Error> {
let mut migration = Migration::new();
if self.if_not_exists {
migration.create_table_if_not_exists(self.name.clone(), move |table| {
for (name, ty) in self.columns.clone() {
table.add_column(name, ty);
}
});
} else {
migration.create_table(self.name.clone(), move |table| {
for (name, ty) in self.columns.clone() {
table.add_column(name, ty);
}
});
}
migration.into_string(dialect)
}
}
impl Statement for Migration {
fn into_string(self, dialect: AnyKind) -> Result<String, sqlx::Error> {
let variant = match dialect {
AnyKind::MySql => Some(barrel::backend::SqlVariant::Mysql),
AnyKind::Sqlite => Some(barrel::backend::SqlVariant::Sqlite),
#[allow(unreachable_patterns)]
_ => None,
};
let variant = variant.map(Ok).unwrap_or_else(|| {
Err(sqlx::Error::Configuration(Box::new(
Error::UnsupportedDialect,
)))
})?;
Ok(self.make_from(variant))
}
}
#[cfg(test)]
mod tests {
}