dml-tools 0.2.1

A library for DML generation and serialization from/to code or YAML files
Documentation
use std::fs::remove_file;

use dml_tools::Loader;
use dml_tools::Processor;
use dml_tools::type_writers::*;
use dml_tools::sql::*;
use serde::{Deserialize, Serialize};
use dml_tools::util::*;

use dml_tools::macros::*;

mod common;
use common::print_if_different;

macro_rules! des_ser_base {
    () => { "local-proc-objs" };
}

const DES_SER_FILE: &str = concat!(des_ser_base!(), ".yaml");
const DES_SER_FILE_COMP: &str = concat!(des_ser_base!(), "_comp.yaml");
const NUM_STATEMENTS: usize = 18;


#[derive(Serialize, Deserialize, Debug)]
pub struct MyRoles {
    pub rw: String,
    pub ro: String,
    pub upd: String,
}
impl Default for MyRoles {
    fn default() -> Self {
        MyRoles {
            rw: "rw_user".into(),
            ro: "ro_user".into(),
            upd: "upd_user".into(),
        }
    }
}

type BxTypeWriter = Box<dyn TypeWriter>;

fn generate_from_code(type_writer:Option<BxTypeWriter>) -> Vec<String> {
    let mut proc = Processor::new(type_writer);
    assert_eq!(proc.objects().len(), 0);
    let roles = MyRoles::default();
    let my_schema = String::from("my_schema");
    let schema = Schema::new(&my_schema, &roles.rw);
    proc.add(&schema);
    let oschema = ObjectPath::new_table_only(&schema.name);
    
    // add_grant!(proc, g, GrantType::Usage, &roles.rw, &oschema);
    add_grant!(proc, GrantType::All, &roles.rw, &oschema);
    add_grant!(proc, GrantType::Usage, &roles.upd, &oschema);
    add_grant!(proc, GrantType::Usage, &roles.ro, &oschema);

    let u_fields = vec![
        Field::new("workspace", &FieldAttributes::new_uk(FieldType::Txt)),
        Field::new("is_archived", &FieldAttributes::new(FieldType::Bool)),
        Field::new("creation_date", &FieldAttributes::new_nn(FieldType::Txt)),
        Field::new("user_id", &FieldAttributes::new_nn(FieldType::Txt)),
        Field::new("user_name", &FieldAttributes::new_uk(FieldType::Txt)),
        Field::new("full_name", &FieldAttributes::new(FieldType::Txt)),
        Field::new("role", &FieldAttributes::new_nn(FieldType::Txt)),
        Field::new("is_locked", &FieldAttributes::new_nn(FieldType::Bool)),
        Field::new("email", &FieldAttributes::new(FieldType::Txt)),
        Field::new("phone_number", &FieldAttributes::new(FieldType::Txt)),
        Field::new("supervisor_id", &FieldAttributes::new(FieldType::Txt)),
        Field::new("is_locked_by_supervisor", &FieldAttributes::new_nn_def(FieldType::Bool, "false")),
        Field::new("is_locked_by_headquarters", &FieldAttributes::new_nn_def(FieldType::Bool, "false")),
    ];
    // println!("{u_fields:#?}");
    let t_users = Table::new(&ObjectPath::new_table(&my_schema, "users"), u_fields, None);
    // println!("{}", t_users);
    proc.add(&t_users);
    grant_perms!(&mut proc, &roles, &t_users.path);

    let c_fields = vec![
        Field::new("id", &FieldAttributes::new_nn(FieldType::AutoInc)),
        Field::new("pk", &FieldAttributes::new(FieldType::Txt)),
        Field::new("responsible", &FieldAttributes::new_nn(FieldType::Txt)),
        Field::new("responsible_id", &FieldAttributes::new_nn(FieldType::Txt)),
        Field::new("workspace", &FieldAttributes::new_pk(FieldType::Txt)),
        Field::new("survey_id", &FieldAttributes::new_pk(FieldType::Int)),
        Field::new("questionnaire_id", &FieldAttributes::new_nn(FieldType::Txt)),
        Field::new("date_created", &FieldAttributes::new_nn(FieldType::Txt)),
        Field::new("date_updated", &FieldAttributes::new_nn(FieldType::Txt)),
        Field::new("otro", &FieldAttributes::new_meta(FieldType::Int, "4")),
    ];
    // println!("{c_fields:#?}");
    let t_cache = Table::new(&ObjectPath::new_table(&my_schema, "cache"), c_fields, None);
    // println!("{}", t_cache);
    proc.add(&t_cache);
    grant_perms!(&mut proc, &roles, &t_cache.path);

    let o_seq = ObjectPath::new_sequence(&my_schema, "asignaciones_cache_id_seq");
    grant_perms!(&mut proc, &roles, &o_seq);
    proc.serialize_to_yaml_file(DES_SER_FILE).expect("to write proc to file");

    proc.sql_statements()

}

fn check_processor_from_code(type_writer:BxTypeWriter) {
    let id = type_writer.as_ref().id().to_owned();
    let sqlfile = format!("tests/fixtures/proc_{}.sql", id);
    // println!("Proc SQL file [{sqlfile}]");
    let pstr = read_file_into_string(&sqlfile);
    let sqls = generate_from_code(Some(type_writer));
    let re_mnl = regex::Regex::new(r"\n+").unwrap();
    let rsqls = sqls.join("\n");
    let sqlsj = re_mnl.replace_all(&rsqls, "\n");
    print_if_different(&sqlsj, &pstr);
    assert_eq!(sqlsj, pstr);
    // assert_eq!(sqls.len(), NUM_STATEMENTS);
    let psqls:Vec<&str> = pstr.split(";").filter(|s| ! s.is_empty()).collect();
    // println!("{psqls:#?}");
    if id == "pgsql" {
        // assert_eq!(psqls.len(), NUM_STATEMENTS);
        for (i, sql) in sqls.iter().enumerate() {
            assert_eq!(sql, format!("{};", psqls[i].trim()).as_str())
        }    
    }
}

// WARNING: tests get runned in alphabetic order!
#[test]
fn test_processor_from_code_mysql() {
    check_processor_from_code(Box::new(Mysql{}))
}

#[test]
fn test_processor_from_code_sqlite() {
    check_processor_from_code(Box::new(Sqlite{}))
}

#[test]
fn test_processor_from_code_pgsql() {
    check_processor_from_code(Box::new(Postgresql{}))
}

#[test]
fn test_processor_from_file() {
    _ = generate_from_code(None);
    let loader = Loader::new_from_file(DES_SER_FILE).unwrap();
    let proc = Processor::new_with_objects(loader.objects(), None);
    assert_eq!(proc.objects().len(), NUM_STATEMENTS);
    proc.serialize_to_yaml_file(DES_SER_FILE_COMP).expect("to write comp file");
    assert_eq!(read_file_into_string(DES_SER_FILE), read_file_into_string(DES_SER_FILE_COMP));

    remove_file(DES_SER_FILE).unwrap();
    remove_file(DES_SER_FILE_COMP).unwrap();
}

const SQLI_YAML : &str = "tests/fixtures/sqlite-init.yaml";
const SQLI_SQL : &str = "tests/fixtures/sqlite-init.sql";
#[test]
fn test_processor_sqlite_from_yaml() {
    let loader = Loader::new_from_file(SQLI_YAML).unwrap();
    let tr: BxTypeWriter = Box::new(Sqlite{});
    let proc = Processor::new_with_objects(loader.objects(),Some(tr));

    let expected = read_file_into_string(SQLI_SQL);
    let generated = proc.join_sql_statements();
    print_if_different(&expected, &generated);
    assert_eq!(generated, expected)
}

// // const DEL_FILE : &str = "tests/fixtures/delayed.yaml";
// #[test]
// fn test_processor_delayed() {
//     // let loader = Loader::new_from_file(DEL_FILE).unwrap();
//     let loader = Loader::new_from_file(SQLI_YAML).unwrap();
//     // println!("objecs: {:#?}", loader.objects());
//     let tr: BxTypeWriter = Box::new(Sqlite{});
//     // let tr: BxTypeWriter = Box::new(Postgresql{});
//     let proc = Processor::new_with_objects(loader.objects(),Some(tr));
//     proc.write_to_sql_file("local-delayed.sql").expect("to write delayed file");
// }