datasources-sqlite 0.1.39

SQLite datasource provider
Documentation
use datasources::{SqlSeedColumn, SqlSeedStatement};

pub struct SqliteSqlSeedStatementRenderer;

impl SqliteSqlSeedStatementRenderer {
    pub fn render(&self, statement: &SqlSeedStatement) -> String {
        let table = quote(statement.table());
        if statement.kind() == "delete" {
            let key = &statement.columns()[0];
            return format!(
                "DELETE FROM {table} WHERE {} = {}",
                quote(&key.column),
                literal(key)
            );
        }
        let cols = statement
            .columns()
            .iter()
            .map(|c| quote(&c.column))
            .collect::<Vec<_>>()
            .join(", ");
        let values = statement.columns().iter().map(literal).collect::<Vec<_>>().join(", ");
        format!("INSERT INTO {table} ({cols}) VALUES ({values})")
    }
}

fn quote(ident: &str) -> String {
    format!("\"{ident}\"")
}

fn quote_string(value: &str) -> String {
    format!("'{}'", value.replace('\'', "''"))
}

fn truthy(value: &str) -> bool {
    value == "true" || value == "True" || value == "1"
}

fn is_numeric(ty: &str) -> bool {
    matches!(
        ty,
        "integer"
            | "unsignedinteger"
            | "smallinteger"
            | "unsignedsmallinteger"
            | "biginteger"
            | "unsignedbiginteger"
            | "reference"
            | "float"
            | "decimal"
    )
}

fn default_sql(ty: &str, def: &str) -> String {
    match (ty, def) {
        ("datetime", "UtcNow") => "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))".into(),
        ("datetime", "Now") => "(strftime('%Y-%m-%dT%H:%M:%f', 'now', 'localtime'))".into(),
        ("uuid", "NewId") => "(lower(hex(randomblob(4))) || '-' || lower(hex(randomblob(2))) || '-4' || substr(lower(hex(randomblob(2))), 2) || '-' || substr('89ab', abs(random()) % 4 + 1, 1) || substr(lower(hex(randomblob(2))), 2) || '-' || lower(hex(randomblob(6))))".into(),
        ("boolean", text) => {
            if text == "true" || text == "True" {
                "1".into()
            } else {
                "0".into()
            }
        }
        _ => quote_string(def),
    }
}

fn literal(column: &SqlSeedColumn) -> String {
    match &column.value {
        None => column
            .field
            .default_value
            .as_deref()
            .map(|def| default_sql(&column.field.type_name, def))
            .unwrap_or_else(|| "NULL".into()),
        Some(value) if column.field.type_name == "boolean" => {
            if truthy(value) { "1".into() } else { "0".into() }
        }
        Some(value) if is_numeric(&column.field.type_name) => value.clone(),
        Some(value) if column.field.type_name == "binary" => format!("X'{value}'"),
        Some(value) => quote_string(value),
    }
}