sql_reverse 0.1.21

Generate the multiple programming languages structure based on the MySQL/PostgresSQL table structure
use crate::error::Result;
use crate::reverse_impl::common::CustomConfig;
use crate::reverse_impl::gen_struct::GenStruct;
use crate::table::{Field, Table, Table2Comment};
use crate::template::kit::Kit;
use fn_macro::{btreemap, if_else};
use inflector::Inflector;
use sqlx::Row;
use std::collections::BTreeMap;
use std::sync::{LazyLock, RwLock};

pub static FIELD_TYPE: LazyLock<RwLock<BTreeMap<String, String>>> = LazyLock::new(|| {
    let map = btreemap!(
        "^varbinary\\(\\d+\\)$".to_string() => "Vec<u8>".to_string(),
        "^binary\\(\\d+\\)$".to_string() => "Vec<u8>".to_string(),
        "^bigint unsigned$".to_string() => "u64".to_string(),
        "^bigint$".to_string() => "i64".to_string(),
        "^bigint\\(\\d+\\) unsigned$".to_string() => "u64".to_string(),
        "^bigint\\(\\d+\\)$".to_string() => "i64".to_string(),
        "^date$".to_string() => "Date".to_string(),
        "^datetime$".to_string() => "chrono::NaiveDateTime".to_string(),
        "^decimal".to_string() => "sqlx::types::Decimal".to_string(),
        "^double".to_string() => "f64".to_string(),
        "^float".to_string() => "f32".to_string(),
        "^int unsigned$".to_string() => "u32".to_string(),
        "^int$".to_string() => "i32".to_string(),
        "^int\\(\\d+\\) unsigned$".to_string() => "u32".to_string(),
        "^int\\(\\d+\\)$".to_string() => "i32".to_string(),
        "^integer unsigned$".to_string() => "u32".to_string(),
        "^integer$".to_string() => "i32".to_string(),
        "^integer\\(\\d+\\) unsigned$".to_string() => "u32".to_string(),
        "^integer\\(\\d+\\)$".to_string() => "i32".to_string(),
        "^json$".to_string() => "serde_json::Value".to_string(),
        "^mediumint unsigned$".to_string() => "u32".to_string(),
        "^mediumint$".to_string() => "i32".to_string(),
        "^mediumint\\(\\d+\\) unsigned$".to_string() => "u32".to_string(),
        "^mediumint\\(\\d+\\)$".to_string() => "i32".to_string(),
        "^smallint unsigned$".to_string() => "u16".to_string(),
        "^smallint$".to_string() => "i16".to_string(),
        "^smallint\\(\\d+\\) unsigned$".to_string() => "u16".to_string(),
        "^smallint\\(\\d+\\)$".to_string() => "i16".to_string(),
        "^timestamp$".to_string() => "chrono::NaiveDateTime".to_string(),
        "^tinyint unsigned$".to_string() => "u8".to_string(),
        "^tinyint$".to_string() => "i8".to_string(),
        "^tinyint\\(\\d+\\) unsigned$".to_string() => "u8".to_string(),
        "^tinyint\\(1\\)$".to_string() => "bool".to_string(),
        "^tinyint\\(\\d+\\)$".to_string() => "i8".to_string(),
        "^bit\\(1\\)$".to_string() => "bool".to_string(),
        "^bit$".to_string() => "bool".to_string(),
        "blob".to_string() => "Vec<u8>".to_string(),
        "char".to_string() => "String".to_string(),
        "text".to_string() => "String".to_string(),
        "year".to_string() => "Year".to_string()
    );
    RwLock::new(map)
});

#[derive(Debug, Clone)]
pub struct MysqlImpl {
    pub config: CustomConfig,
    pub pool: sqlx::MySqlPool,
}

impl Kit for MysqlImpl {}

impl MysqlImpl {
    pub async fn init(config: CustomConfig) -> Result<Self> {
        let pool = sqlx::MySqlPool::connect(&config.db_url).await?;
        Ok(Self { config, pool })
    }
}
const FIELD_SQL:&str = "SELECT CAST(COLUMN_NAME as CHAR ) as field_name, CAST(COLUMN_TYPE as CHAR ) as field_type, case when CAST(IS_NULLABLE as CHAR) = 'NO' THEN 0 else 1 END as is_null,
       CAST(COLUMN_COMMENT as CHAR ) as comment, CAST(COLUMN_DEFAULT as CHAR ) as default_value
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_schema = DATABASE() AND table_name = ? ORDER BY ORDINAL_POSITION";
const TABLES_SQL: &str = "SELECT CAST(TABLE_NAME AS CHAR) as table_name, CAST(TABLE_COMMENT as CHAR) as table_comment FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE()";

impl GenStruct for MysqlImpl {
    async fn get_tables(&self) -> Result<Vec<Table2Comment>> {
        let mut pool = self.pool.acquire().await?;
        let mut tables = sqlx::query_as::<_, Table2Comment>(TABLES_SQL)
            .fetch_all(&mut *pool)
            .await?;
        self.filter_tables(
            &mut tables,
            &self.config.include_tables,
            &self.config.exclude_tables,
        )
        .await;
        Ok(tables)
    }

    async fn update_type_fields(&self, map: Option<BTreeMap<String, String>>) {
        if let Some(map) = map {
            *FIELD_TYPE.write().unwrap() = map;
        }
    }

    async fn gen_templates(&self, tables: Vec<Table2Comment>) -> Result<Vec<Table>> {
        let mut templates = vec![];
        let mut pool = self.pool.acquire().await?;
        for table in tables {
            let fields = sqlx::query_as::<_, Field>(FIELD_SQL)
                .bind(&table.table_name)
                .fetch_all(&mut *pool)
                .await?;
            let mut struct_name = table.table_name.clone().to_camel_case();
            struct_name = Self::first_char_to_uppercase(&struct_name);
            let mut table = Table {
                table_name: table.table_name.to_owned(),
                struct_name,
                fields,
                comment: table.table_comment.unwrap_or_default(),
                index_key: vec![],
                unique_key: vec![],
            };
            let (index_key, unique_key) = self.index_key(&table.table_name).await?;
            table.index_key = index_key;
            table.unique_key = unique_key;
            templates.push(table);
        }
        Ok(templates)
    }

    async fn index_key(&self, table_name: &str) -> Result<(Vec<Vec<String>>, Vec<Vec<String>>)> {
        let indexes = sqlx::query(&format!("show index from {}", table_name))
            .fetch_all(&self.pool)
            .await?;
        let mut map: BTreeMap<String, Vec<String>> = BTreeMap::new();
        let mut unique_map: BTreeMap<String, Vec<String>> = BTreeMap::new();
        for index in indexes {
            let key = String::from_utf8(index.get(2))?;
            let i: i8 = index.get(1);
            let is_unique = if_else!(i == 0, 1, 0);
            let field_name = String::from_utf8(index.get(4))?;
            if is_unique == 1 {
                let v = unique_map.get(&key);
                if v.is_none() {
                    unique_map.insert(key, vec![field_name]);
                } else {
                    let mut v = v.unwrap().to_owned();
                    v.push(field_name);
                    unique_map.insert(key, v);
                }
            } else {
                let v = map.get(&key);
                if v.is_none() {
                    map.insert(key, vec![field_name]);
                } else {
                    let mut v = v.unwrap().to_owned();
                    v.push(field_name);
                    map.insert(key, v);
                }
            }
        }
        let mut list = vec![];
        for (_, val) in map {
            list.push(val);
        }
        let mut unique_list = vec![];
        for (_, val) in unique_map {
            unique_list.push(val);
        }
        Ok((list, unique_list))
    }
}