models 0.1.3

A migration management library for applications using PostgresSQL, MySQL or SQLite.
use super::{Compare, *};
use crate::prelude::*;
#[derive(Debug)]
pub(crate) struct Move<'table> {
    pub(super) new_cols: Vec<&'table Column>,
    pub(super) old_cols: Vec<&'table Column>,
    pub(super) constraints: Vec<&'table TableConstraint>,
}

impl<'table> Move<'table> {
    pub fn new(old: &'table Table, cons: &ConsCRUD<'table>, cols: &ColCRUD<'table>) -> Self {
        let mut new_cols = vec![];
        let mut old_cols = vec![];
        let mut constraints = vec![];
        for col in &old.columns {
            if !cols.to_delete(col) && !cols.to_update(col) {
                new_cols.push(col);
                old_cols.push(col);
            }
        }
        for &col in &cols.update {
            new_cols.push(col);
            old_cols.push(col);
        }
        for con in &old.constraints {
            let to_delete = cons.to_delete(con);
            let to_update = cons.to_update(con);
            if !to_delete && !to_update {
                constraints.push(con);
            }
        }
        for con in &cons.update {
            if !depends(con, &cols.create) || matches!(*DIALECT, SQLite) {
                constraints.push(con);
            }
        }
        for con in &cons.create {
            if !depends(con, &cols.create) || matches!(*DIALECT, SQLite) {
                constraints.push(con);
            }
        }
        Self {
            new_cols,
            old_cols,
            constraints,
        }
    }

    pub fn to_statements(self, table_name: ObjectName) -> Result<Vec<Statement>> {
        let mut stmt = vec![];
        let create_table = self.create_table();
        let insert = self.insert_statement(table_name.clone())?;
        let drop = self.drop_statement(table_name.clone());
        let rename = self.rename(table_name);
        stmt.push(create_table);
        stmt.push(insert);
        stmt.push(drop);
        stmt.push(rename);
        Ok(stmt)
    }

    fn create_table(&self) -> Statement {
        Table {
            name: ObjectName(vec![Ident::new("temp")]),
            columns: self.new_cols.iter().map(|&c| c.clone()).collect(),
            constraints: self.constraints.iter().map(|&c| c.clone()).collect(),
            if_not_exists: false,
            or_replace: false,
        }
        .into()
    }
    fn insert_statement(&self, table_name: ObjectName) -> Result<Statement> {
        let new = self
            .new_cols
            .iter()
            .map(|&col| col.ident()) //
            .collect();
        let old = self
            .old_cols
            .iter()
            .map(|&col| col.ident()) //
            .collect();

        let insert = format!(
            "INSERT INTO temp ({}) SELECT {} FROM {};",
            to_string(new),
            to_string(old),
            table_name
        );
        let insert = parse_sql(&insert)?
            .into_iter() //
            .next()
            .unwrap();

        Ok(insert)
    }

    fn drop_statement(&self, table_name: ObjectName) -> Statement {
        Statement::Drop(Drop {
            object_type: ObjectType::Table,
            if_exists: false,
            names: vec![table_name],
            cascade: !DIALECT.requires_move(),
            purge: false,
        })
    }

    fn rename(self, table_name: ObjectName) -> Statement {
        Statement::AlterTable(AlterTable {
            name: ObjectName(vec![Ident::new("temp")]),
            operation: AlterTableOperation::RenameTable {
                table_name: table_name,
            },
        })
    }
}

fn to_string<T: ToString>(collection: Vec<T>) -> String {
    let mut out = String::new();
    for (i, c) in collection.iter().enumerate() {
        out += &c.to_string();
        if collection.len() != i + 1 {
            out += ","
        }
    }
    out
}

pub fn depends(cons: &TableConstraint, tables: &[&Column]) -> bool {
    let names = match cons {
        TableConstraint::ForeignKey(fk) => &fk.columns,
        TableConstraint::Unique(unique) => &unique.columns,
        _ => return false,
    };
    let names = names.iter().map(ToString::to_string);

    for col in names {
        for table_name in tables.iter().map(|t| t.name().unwrap()) {
            if col.to_string() == table_name {
                return true;
            }
        }
    }
    false
}