datasources 0.1.5

Async datasource interfaces, middleware, registry, and test doubles
Documentation
use super::column_default::ColumnDefault;
use super::create_table_statement::CreateTableStatement;
use super::foreign_key::{ForeignKey, ReferentialAction};
use super::not_null::NotNull;
use super::table_constraint::TableConstraint;
use crate::builders::Column;

pub trait CreateTableStatementTextRenderer {
    fn quote(&self, name: &str) -> String {
        name.to_string()
    }

    fn suffix(&self) -> String {
        String::new()
    }

    fn type_name(&self, column: &Column) -> String {
        column.type_name.clone()
    }

    fn column_default(&self, column: &Column) -> String {
        self.write_default(column.default.as_ref())
    }

    fn write_default(&self, value: Option<&ColumnDefault>) -> String {
        match value {
            None => String::new(),
            Some(ColumnDefault { value, name: None }) => format!(" DEFAULT {value}"),
            Some(ColumnDefault {
                value,
                name: Some(name),
            }) => format!(" CONSTRAINT {} DEFAULT {value}", self.quote(name)),
        }
    }

    fn identity(&self, _column: &Column) -> String {
        String::new()
    }

    fn column(&self, column: &Column) -> String {
        format!(
            "{} {}{}{}{}",
            self.quote(&column.name),
            self.type_name(column),
            self.identity(column),
            self.column_default(column),
            not_null_sql(self, column.not_null.as_ref())
        )
    }

    fn constraint(&self, constraint: &TableConstraint) -> String {
        match constraint {
            TableConstraint::PrimaryKey(pk) => format!(
                "CONSTRAINT {} PRIMARY KEY ({})",
                self.quote(&pk.name),
                quoted(self, &pk.columns)
            ),
            TableConstraint::ForeignKey(fk) => foreign_key(self, fk),
            TableConstraint::Unique(unique) => format!(
                "CONSTRAINT {} UNIQUE ({})",
                self.quote(&unique.name),
                quoted(self, &unique.columns)
            ),
            TableConstraint::Check(check) => format!(
                "CONSTRAINT {} CHECK ({})",
                self.quote(&check.name),
                check.expression
            ),
        }
    }

    fn render(&self, statement: &CreateTableStatement) -> String {
        let mut parts = statement
            .columns
            .iter()
            .map(|column| self.column(column))
            .collect::<Vec<_>>();
        parts.extend(
            statement
                .constraints
                .iter()
                .map(|constraint| self.constraint(constraint)),
        );
        format!(
            "CREATE TABLE {} ({}){}",
            self.quote(&statement.table),
            parts.join(", "),
            self.suffix()
        )
    }
}

struct DefaultRenderer;

impl CreateTableStatementTextRenderer for DefaultRenderer {}

pub(crate) fn render(statement: &CreateTableStatement) -> String {
    DefaultRenderer.render(statement)
}

fn not_null_sql<T: CreateTableStatementTextRenderer + ?Sized>(
    sql: &T,
    value: Option<&NotNull>,
) -> String {
    match value {
        None => String::new(),
        Some(NotNull { name: None }) => " NOT NULL".into(),
        Some(NotNull { name: Some(name) }) => format!(" CONSTRAINT {} NOT NULL", sql.quote(name)),
    }
}

fn quoted<T: CreateTableStatementTextRenderer + ?Sized>(sql: &T, names: &[String]) -> String {
    names
        .iter()
        .map(|name| sql.quote(name))
        .collect::<Vec<_>>()
        .join(", ")
}

fn foreign_key<T: CreateTableStatementTextRenderer + ?Sized>(sql: &T, fk: &ForeignKey) -> String {
    format!(
        "CONSTRAINT {} FOREIGN KEY ({}) REFERENCES {} ({}){}{}",
        sql.quote(&fk.name),
        quoted(sql, &fk.columns),
        sql.quote(&fk.references_table),
        quoted(sql, &fk.references_columns),
        action_sql("DELETE", fk.on_delete),
        action_sql("UPDATE", fk.on_update)
    )
}

fn action_sql(kind: &str, action: Option<ReferentialAction>) -> String {
    match action {
        None => String::new(),
        Some(action) => format!(" ON {kind} {}", action_keyword(action)),
    }
}

fn action_keyword(action: ReferentialAction) -> &'static str {
    match action {
        ReferentialAction::Cascade => "CASCADE",
        ReferentialAction::SetNull => "SET NULL",
        ReferentialAction::SetDefault => "SET DEFAULT",
        ReferentialAction::Restrict => "RESTRICT",
        ReferentialAction::NoAction => "NO ACTION",
    }
}