dinoco_engine 0.0.7

Database adapters, query execution, and migration engine components for Dinoco.
Documentation
use async_trait::async_trait;
use dinoco_compiler::ParsedSchema;
use std::future::Future;
use std::pin::Pin;

use crate::{
    ColumnDefinition, DatabaseColumn, DatabaseEnumRaw, DatabaseForeignKey, DatabaseIndex, DatabaseParsedTable,
    DinocoClientConfig, DinocoError, DinocoResult, DinocoValue, MigrationStep,
};

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ExecutionResult {
    pub affected_rows: u64,
    pub last_insert_id: Option<i64>,
}

#[async_trait]
pub trait DinocoAdapter: Sized + Sync {
    type Dialect: AdapterDialect;

    fn dialect(&self) -> &Self::Dialect;

    async fn connect(url: String, config: DinocoClientConfig) -> DinocoResult<Self>;

    async fn execute_result(&self, query: &str, params: &[DinocoValue]) -> DinocoResult<ExecutionResult>;
    async fn execute(&self, query: &str, params: &[DinocoValue]) -> DinocoResult<()> {
        self.execute_result(query, params).await.map(|_| ())
    }
    async fn execute_script(&self, sql_content: &str) -> DinocoResult<()> {
        let clean_sql = sql_content.trim();

        if clean_sql.is_empty() {
            return Ok(());
        }

        self.execute(clean_sql, &[]).await
    }

    async fn query_as<T: DinocoRow>(&self, query: &str, params: &[DinocoValue]) -> DinocoResult<Vec<T>>;
}

#[async_trait]
pub trait DinocoAdapterHandler: Sized {
    async fn fetch_tables(&self) -> DinocoResult<Vec<DatabaseParsedTable>>;
    async fn fetch_columns(&self, table_name: String) -> DinocoResult<Vec<DatabaseColumn>>;
    async fn fetch_foreign_keys(&self) -> DinocoResult<Vec<DatabaseForeignKey>>;
    async fn fetch_enums(&self) -> DinocoResult<Vec<DatabaseEnumRaw>>;
    async fn fetch_indexes(&self) -> DinocoResult<Vec<DatabaseIndex>>;

    async fn reset_database(&self) -> DinocoResult<()>;
}

pub trait AdapterDialect {
    fn bind_param(&self, index: usize) -> String;
    fn bind_value(&self, index: usize, value: &DinocoValue) -> String {
        let _ = value;

        self.bind_param(index)
    }
    fn cast_numeric_for_division(&self, expr: &str) -> String {
        expr.to_string()
    }
    fn identifier(&self, v: &str) -> String;
    fn literal_string(&self, v: &str) -> String;
    fn column_type(&self, t: &ColumnDefinition, is_primary: bool, auto_increment: bool) -> String;
    fn offset_without_limit(&self) -> String {
        "-1".to_string()
    }

    fn supports_native_enums(&self) -> bool {
        false
    }

    fn supports_insert_returning(&self) -> bool {
        false
    }
}

pub trait DinocoGenericRow {
    fn get_value(&self, idx: usize) -> DinocoResult<DinocoValue>;

    fn get_optional<T>(&self, idx: usize) -> DinocoResult<Option<T>>
    where
        T: TryFrom<DinocoValue, Error = DinocoError>,
    {
        let value = self.get_value(idx)?;

        match value {
            DinocoValue::Null => Ok(None),
            v => Ok(Some(T::try_from(v)?)),
        }
    }

    fn get<T>(&self, idx: usize) -> DinocoResult<T>
    where
        T: TryFrom<DinocoValue, Error = DinocoError>,
    {
        let value = self.get_value(idx)?;

        T::try_from(value)
    }
}

pub trait DinocoRow: Sized + Send + 'static {
    fn from_row<R: DinocoGenericRow>(row: &R) -> DinocoResult<Self>;
}

pub trait MigrationExecutor {
    fn build_step(&self, step: &MigrationStep, schema: &ParsedSchema) -> Vec<String>;
    fn build_reverse_step(&self, step: &MigrationStep, schema: &ParsedSchema) -> Vec<String>;

    fn build_migration(&self, steps: &[MigrationStep], schema: &ParsedSchema, reverse: bool) -> Vec<String> {
        let mut sqls = Vec::new();

        for step in steps {
            let mut step_sqls =
                if reverse { self.build_reverse_step(step, schema) } else { self.build_step(step, schema) };

            for sql in &mut step_sqls {
                let trimmed = sql.trim_end();

                if !trimmed.ends_with(';') {
                    *sql = format!("{};", trimmed);
                }
            }

            sqls.extend(step_sqls);
        }

        sqls
    }
}

pub trait DinocoTransactionAdapter: DinocoAdapter {
    fn with_transaction<'a, T, F>(&'a self, operation: F) -> Pin<Box<dyn Future<Output = DinocoResult<T>> + Send + 'a>>
    where
        T: Send + 'a,
        F: FnOnce() -> Pin<Box<dyn Future<Output = DinocoResult<T>> + Send + 'a>> + Send + 'a;
}