use crate::map_schema::{Column, Table, Schema, Database, Root, Server};
use tokio::sync::Mutex;
pub use crate::config::ConfigClient;
use mssql_client::{Config, Ready};
use anyhow::{Ok, Result};
const MSSQL_SCHEMA: &str = include_str!("../querys/sql_schema.sql");
const MYSQL_SCHEMA: &str = include_str!("../querys/mysql_schema.sql");
const CLICKHOUSE_SCHEMA: &str = include_str!("../querys/clickhouse_schema.sql");
pub enum DbClient {
SqlServer(mssql_client::Client<Ready>),
MySql(mysql_async::Conn),
ClickHouse(clickhouse::Client)
}
pub struct Generator{
client:Mutex<DbClient>,
host:String,
alias: String,
databases_filter: Option<Vec<String>>,
}
impl Generator {
pub async fn new<'a>(cfg: &ConfigClient<'a>)->Result<Self>{
let server_name = cfg.alias.as_ref().map(|a| a.to_string()).unwrap_or_else(|| cfg.host.to_string());
let conn_mssql =format!("Server={};User Id={};Password={};TrustServerCertificate=true", cfg.host, cfg.user, cfg.password);
if let std::result::Result::Ok(config) = Config::from_connection_string(&conn_mssql){
if let std::result::Result::Ok(client) = mssql_client::Client::connect(config).await{
return Ok(Self{client: Mutex::new(DbClient::SqlServer(client)), host: cfg.host.to_string(), alias: server_name.clone(), databases_filter: cfg.databases.clone()});
}
}
let conn_mysql=format!("mysql://{}:{}@{}:3306", cfg.user, cfg.password, cfg.host);
if let std::result::Result::Ok(opts)= mysql_async::Opts::from_url(&conn_mysql){
if let std::result::Result::Ok(client)=mysql_async::Conn::new(opts).await{
return Ok(Self { client: Mutex::new(DbClient::MySql(client)), host: cfg.host.to_string(), alias: server_name.clone(), databases_filter: cfg.databases.clone()});
}
}
let ch_url = format!("http://{}:8123", cfg.host);
let client_ch = clickhouse::Client::default()
.with_url(&ch_url)
.with_user(cfg.user.to_string())
.with_password(cfg.password.to_string());
if let std::result::Result::Ok(_) = client_ch.query("Select 1").fetch_one::<u8>().await{
return Ok(Self { client: Mutex::new(DbClient::ClickHouse(client_ch)), host: cfg.host.to_string(), alias: server_name.clone(), databases_filter: cfg.databases.clone()});
}
anyhow::bail!("Erro de autentificação")
}
pub async fn build_from_toml(toml_path: &str, output_path: &str) -> anyhow::Result<()> {
let toml_str = std::fs::read_to_string(toml_path)?;
let app_config: crate::config::AppConfig = toml::from_str(&toml_str)?;
let mut merged_root = Root {
servers: std::collections::BTreeMap::new(),
};
for server_cfg in app_config.servers {
let cfg = crate::config::ConfigClient {
host: std::borrow::Cow::Owned(server_cfg.host.clone()),
user: std::borrow::Cow::Owned(server_cfg.user.clone()),
password: std::borrow::Cow::Owned(server_cfg.password.clone()),
databases: server_cfg.databases.clone(),
alias: server_cfg.alias.clone().map(std::borrow::Cow::Owned),
};
if let std::result::Result::Ok(generator) = Generator::new(&cfg).await {
if let std::result::Result::Ok(partial_root) = generator.get_schema().await {
merged_root.servers.extend(partial_root.servers);
}
}
}
let path = std::path::Path::new(output_path);
let output_dir = path.parent().unwrap_or(std::path::Path::new("."));
std::fs::create_dir_all(output_dir)?;
let mut main_file_content = String::new();
main_file_content.push_str(r#"// --- NÃO ALTERE ESTE DOCUMENTO MANUALMENTE ---
#![allow(dead_code, unused_imports, non_camel_case_types, non_snake_case)]
use std::marker::PhantomData;
// 1. COLUNAS COPY (Zero atrito com o Borrow Checker)
pub struct Column<T> {
pub nome: &'static str,
pub tipo_rust: &'static str,
pub _tipo: PhantomData<T>,
}
impl<T> Clone for Column<T> {
fn clone(&self) -> Self {
Column { nome: self.nome, tipo_rust: self.tipo_rust, _tipo: PhantomData }
}
}
impl<T> Copy for Column<T> {}
impl<T> Column<T> {
pub fn eq<V: std::fmt::Display>(&self, val: V) -> String { format!("{} = '{}'", self.nome, val) }
pub fn neq<V: std::fmt::Display>(&self, val: V) -> String { format!("{} <> '{}'", self.nome, val) }
pub fn gt<V: std::fmt::Display>(&self, val: V) -> String { format!("{} > '{}'", self.nome, val) }
pub fn lt<V: std::fmt::Display>(&self, val: V) -> String { format!("{} < '{}'", self.nome, val) }
pub fn gte<V: std::fmt::Display>(&self, val: V) -> String { format!("{} >= '{}'", self.nome, val) }
pub fn lte<V: std::fmt::Display>(&self, val: V) -> String { format!("{} <= '{}'", self.nome, val) }
pub fn is_null(&self) -> String { format!("{} IS NULL", self.nome) }
pub fn is_not_null(&self) -> String { format!("{} IS NOT NULL", self.nome) }
pub fn eq_col<U>(&self, other: Column<U>) -> String { format!("{} = {}", self.nome, other.nome) }
pub fn neq_col<U>(&self, other: Column<U>) -> String { format!("{} <> {}", self.nome, other.nome) }
pub fn gt_col<U>(&self, other: Column<U>) -> String { format!("{} > {}", self.nome, other.nome) }
pub fn lt_col<U>(&self, other: Column<U>) -> String { format!("{} < {}", self.nome, other.nome) }
pub fn gte_col<U>(&self, other: Column<U>) -> String { format!("{} >= {}", self.nome, other.nome) }
pub fn lte_col<U>(&self, other: Column<U>) -> String { format!("{} <= {}", self.nome, other.nome) }
pub fn in_list<I>(&self, vals: I) -> String
where I: IntoIterator, I::Item: std::fmt::Display {
let list = vals.into_iter().map(|v| format!("'{}'", v)).collect::<Vec<_>>().join(", ");
format!("{} IN ({})", self.nome, list)
}
pub fn not_in<I>(&self, vals: I) -> String
where I: IntoIterator, I::Item: std::fmt::Display {
let list = vals.into_iter().map(|v| format!("'{}'", v)).collect::<Vec<_>>().join(", ");
format!("{} NOT IN ({})", self.nome, list)
}
}
// OPERADORES LÓGICOS
pub trait ConditionExt {
fn and(self, other: String) -> String;
fn or(self, other: String) -> String;
}
impl ConditionExt for String {
fn and(self, other: String) -> String { format!("({}) AND ({})", self, other) }
fn or(self, other: String) -> String { format!("({}) OR ({})", self, other) }
}
// SUPORTE REFORÇADO PARA SELEÇÃO DE COLUNAS (Até 8 colunas combinadas)
pub trait IntoColumns {
fn get_columns(&self) -> Vec<&'static str>;
fn get_types(&self) -> Vec<&'static str>;
}
impl<A> IntoColumns for Column<A> {
fn get_columns(&self) -> Vec<&'static str> { vec![self.nome] }
fn get_types(&self) -> Vec<&'static str> { vec![self.tipo_rust] }
}
impl<A, B> IntoColumns for (Column<A>, Column<B>) {
fn get_columns(&self) -> Vec<&'static str> { vec![self.0.nome, self.1.nome] }
fn get_types(&self) -> Vec<&'static str> { vec![self.0.tipo_rust, self.1.tipo_rust] }
}
impl<A, B, C> IntoColumns for (Column<A>, Column<B>, Column<C>) {
fn get_columns(&self) -> Vec<&'static str> { vec![self.0.nome, self.1.nome, self.2.nome] }
fn get_types(&self) -> Vec<&'static str> { vec![self.0.tipo_rust, self.1.tipo_rust, self.2.tipo_rust] }}
impl<A, B, C, D> IntoColumns for (Column<A>, Column<B>, Column<C>, Column<D>) {
fn get_columns(&self) -> Vec<&'static str> { vec![self.0.nome, self.1.nome, self.2.nome, self.3.nome] }
fn get_types(&self) -> Vec<&'static str> { vec![self.0.tipo_rust, self.1.tipo_rust, self.2.tipo_rust, self.3.tipo_rust] }}
impl<A, B, C, D, E> IntoColumns for (Column<A>, Column<B>, Column<C>, Column<D>, Column<E>) {
fn get_columns(&self) -> Vec<&'static str> { vec![self.0.nome, self.1.nome, self.2.nome, self.3.nome, self.4.nome] }
fn get_types(&self) -> Vec<&'static str> { vec![self.0.tipo_rust, self.1.tipo_rust, self.2.tipo_rust, self.3.tipo_rust, self.4.tipo_rust] }}
impl<A, B, C, D, E, G> IntoColumns for (Column<A>, Column<B>, Column<C>, Column<D>, Column<E>, Column<G>) {
fn get_columns(&self) -> Vec<&'static str> { vec![self.0.nome, self.1.nome, self.2.nome, self.3.nome, self.4.nome, self.5.nome] }
fn get_types(&self) -> Vec<&'static str> { vec![self.0.tipo_rust, self.1.tipo_rust, self.2.tipo_rust, self.3.tipo_rust, self.4.tipo_rust, self.5.tipo_rust] } }
impl<A, B, C, D, E, G, H> IntoColumns for (Column<A>, Column<B>, Column<C>, Column<D>, Column<E>, Column<G>, Column<H>) {
fn get_columns(&self) -> Vec<&'static str> { vec![self.0.nome, self.1.nome, self.2.nome, self.3.nome, self.4.nome, self.5.nome, self.6.nome] }
fn get_types(&self) -> Vec<&'static str> { vec![self.0.tipo_rust, self.1.tipo_rust, self.2.tipo_rust, self.3.tipo_rust, self.4.tipo_rust, self.5.tipo_rust, self.6.tipo_rust] }}
impl<A, B, C, D, E, G, H, I> IntoColumns for (Column<A>, Column<B>, Column<C>, Column<D>, Column<E>, Column<G>, Column<H>, Column<I>) {
fn get_columns(&self) -> Vec<&'static str> { vec![self.0.nome, self.1.nome, self.2.nome, self.3.nome, self.4.nome, self.5.nome, self.6.nome, self.7.nome] }
fn get_types(&self) -> Vec<&'static str> { vec![self.0.tipo_rust, self.1.tipo_rust, self.2.tipo_rust, self.3.tipo_rust, self.4.tipo_rust, self.5.tipo_rust, self.6.tipo_rust, self.7.tipo_rust] }}
// CONSTRUTOR DE QUEBRACABEÇAS (TYPE-STATE PATTERN)
#[derive(Clone)]
pub struct QueryBuilder<'a, F> {
pub client: &'a crate::ClientSql,
pub server_name: String,
pub select_cols: Option<String>,
pub expected_cols: Option<Vec<&'static str>>,
pub expected_types: Option<Vec<&'static str>>,
pub table_path: String,
pub joins: Vec<String>,
pub where_clause: Option<String>,
pub is_distinct: bool,
pub top_limit: Option<u32>,
pub limit_amount: Option<u32>,
pub fields: F,
}
impl<'a, F: Clone> QueryBuilder<'a, F> {
pub fn select<W, Ret>(mut self, w: W) -> Self
where W: FnOnce(F) -> Ret, Ret: IntoColumns {
let selecionadas = w(self.fields.clone());
self.select_cols = Some(selecionadas.get_columns().join(", "));
self.expected_cols = Some(selecionadas.get_columns());
self.expected_types = Some(selecionadas.get_types());
self
}
pub fn filter<W>(mut self, w: W) -> Self
where W: FnOnce(F) -> String {
self.where_clause = Some(w(self.fields.clone()));
self
}
pub fn distinct(mut self) -> Self {
self.is_distinct = true;
self
}
pub fn top(mut self, n: u32) -> Self {
self.top_limit = Some(n);
self
}
pub fn limit(mut self, n: u32) -> Self {
self.limit_amount = Some(n);
self
}
// INNER JOIN ENCADEÁVEL (Aninha os campos antigos com os novos em Tuplas)
pub fn inner_join<J: Clone, W>(mut self, other: QueryBuilder<'a, J>, on: W) -> QueryBuilder<'a, (F, J)>
where W: FnOnce((F, J)) -> String {
let combined_fields = (self.fields.clone(), other.fields.clone());
let on_cond = on(combined_fields.clone());
self.joins.push(format!("INNER JOIN {} ON {}", other.table_path, on_cond));
QueryBuilder {
client: self.client,
server_name: self.server_name.clone(),
select_cols: self.select_cols,
expected_cols: self.expected_cols.clone(),
expected_types: self.expected_types.clone(),
table_path: self.table_path,
joins: self.joins,
where_clause: self.where_clause,
is_distinct: self.is_distinct,
top_limit: self.top_limit,
limit_amount: self.limit_amount,
fields: combined_fields,
}
}
// LEFT JOIN ENCADEÁVEL
pub fn left_join<J: Clone, W>(mut self, other: QueryBuilder<'a, J>, on: W) -> QueryBuilder<'a, (F, J)>
where W: FnOnce((F, J)) -> String {
let combined_fields = (self.fields.clone(), other.fields.clone());
let on_cond = on(combined_fields.clone());
self.joins.push(format!("LEFT JOIN {} ON {}", other.table_path, on_cond));
QueryBuilder {
client: self.client,
server_name: self.server_name.clone(),
select_cols: self.select_cols,
expected_cols: self.expected_cols.clone(),
expected_types: self.expected_types.clone(),
table_path: self.table_path,
joins: self.joins,
where_clause: self.where_clause,
is_distinct: self.is_distinct,
top_limit: self.top_limit,
limit_amount: self.limit_amount,
fields: combined_fields,
}
}
pub fn build(self) -> anyhow::Result<QueryResult<'a>> {
let cols = self.select_cols.unwrap_or_else(|| "*".to_string());
let joins_str = if self.joins.is_empty() { String::new() } else { format!(" {}", self.joins.join(" ")) };
let dist_str = if self.is_distinct { " DISTINCT" } else { "" };
let top_str = if let Some(n) = self.top_limit { format!(" TOP {}", n) } else { String::new() };
let mut sql = format!("SELECT{}{} {} FROM {}{}", dist_str, top_str, cols, self.table_path, joins_str);
if let Some(cond) = self.where_clause {
sql.push_str(" WHERE ");
sql.push_str(&cond);
}
if let Some(n) = self.limit_amount {
sql.push_str(&format!(" LIMIT {}", n));
}
Ok(QueryResult {
client: self.client,
server_name: self.server_name,
sql_gerado: sql,
expected_cols: self.expected_cols.unwrap_or_default(),
expected_types: self.expected_types.unwrap_or_default()
})
}
}
pub struct QueryResult<'a> {
pub client: &'a crate::ClientSql,
pub server_name: String,
pub sql_gerado: String,
pub expected_cols: Vec<&'static str>,
pub expected_types: Vec<&'static str>
}
impl<'a> QueryResult<'a> {
pub async fn execute(&self) -> anyhow::Result<data_goes::TabGoes> {
self.client.query_to_parquet(&self.server_name, &self.sql_gerado, &self.expected_cols, &self.expected_types).await
}
}
"#);
for (server_name, server) in &merged_root.servers {
let mod_name = Self::sanitize_field_name(server_name);
let server_code = Self::code_generator_for_server(server_name, server);
main_file_content.push_str(&format!("\npub mod {};\n", mod_name));
main_file_content.push_str(&format!("pub use {}::*;\n", mod_name));
let server_file_path = output_dir.join(format!("{}.rs", mod_name));
std::fs::write(server_file_path, server_code)?;
}
std::fs::write(output_path, main_file_content)?;
Ok(())
}
pub async fn get_schema(&self)->Result<Root>{
let mut root = Root{
servers:std::collections::BTreeMap::new(),
};
let should_include = |db_name: &str| -> bool {
match &self.databases_filter {
Some(allowed_dbs) => allowed_dbs.contains(&db_name.to_string()),
None => true,
}
};
let mut client_lock = self.client.lock().await;
match &mut *client_lock {
DbClient::SqlServer(client)=>{
let rows=client.query(MSSQL_SCHEMA, &[]).await?;
for result in rows{
let row= result?;
let db_name: String =row.get(0)?;
if should_include(&db_name) {
Self::insert_node(&mut root, &self.alias, db_name, row.get(1)?, row.get(2)?, row.get(3)?, row.get(4)?);
}
}
},
DbClient::MySql(client)=>{
use mysql_async::prelude::Queryable;
let rows: Vec<(String, String, String, String)> = client.query(MYSQL_SCHEMA).await?;
for ( schema_name, table_name, col_name, col_type) in rows {
if should_include(&schema_name){
Self::insert_node(&mut root, &self.alias, schema_name.clone(), schema_name, table_name, col_name, col_type);
}
}
},
DbClient::ClickHouse(client)=>{
#[derive(clickhouse::Row, serde::Deserialize)]
struct ColRow {
db: String,
table_name: String,
column_name: String,
data_type: String,
}
let mut cursor = client.query(CLICKHOUSE_SCHEMA).fetch::<ColRow>()?;
while let Some(row) = cursor.next().await? {
let db_name:String= row.db;
if should_include(&db_name){
Self::insert_node(&mut root, &self.alias, db_name.clone(),db_name, row.table_name, row.column_name, row.data_type);
}
}
}
}
let toml = toml::to_string_pretty(&root)?;
std::fs::write("schema.toml", toml)?;
Ok(root)
}
fn insert_node(root: &mut Root, host: &str, db: String, schema_name: String, table_name: String, col_name: String, col_type: String) {
let column = Column { name: col_name, data_type: col_type };
let server = root.servers.entry(host.to_string()).or_insert(Server { databases: std::collections::BTreeMap::new() });
let database = server.databases.entry(db).or_insert(Database { schemas: std::collections::BTreeMap::new() });
let schema = database.schemas.entry(schema_name).or_insert(Schema { tables: std::collections::BTreeMap::new() });
let table = schema.tables.entry(table_name).or_insert(Table { columns: Vec::new() });
table.columns.push(column);
}
pub fn code_generator_for_server(server_name: &str, server: &Server) -> String {
let mut code = String::new();
let trait_name = format!("ClientSqlExt{}", Self::sanitize_struct_name(server_name));
code.push_str(&format!("use super::Column;\nuse std::marker::PhantomData;\n\npub trait {} {{\n", trait_name));
let mut trait_signatures = String::new();
let mut impl_bodies = String::new();
let mut processed_fns = std::collections::HashSet::new();
for (db_name, database) in &server.databases {
for (schema_name, schema) in &database.schemas {
for table in schema.tables.keys() {
let full_name = if db_name == schema_name{
format!("{}_{}_{}", server_name, db_name, table)
} else {
format!("{}_{}_{}_{}", server_name, db_name, schema_name, table)
};
let struct_name = Self::sanitize_struct_name(&full_name);
let fn_name = Self::sanitize_field_name(&full_name);
if processed_fns.insert(fn_name.clone()) {
trait_signatures.push_str(&format!(
" fn {}(&self) -> Table{}<'_>;\n",
fn_name, struct_name
));
impl_bodies.push_str(&format!(
" fn {}(&self) -> Table{}<'_> {{ Table{} {{ client: self }} }}\n",
fn_name, struct_name, struct_name
));
}
}
}
}
code.push_str(&trait_signatures);
code.push_str("}\n\n");
code.push_str(&format!("impl {} for crate::ClientSql {{\n", trait_name));
code.push_str(&impl_bodies);
code.push_str("}\n\n");
let mut generated_structs = std::collections::HashSet::new();
for (db_name, database) in &server.databases {
for (schema_name, schema) in &database.schemas {
for (table_name, table) in &schema.tables {
let full_name = if db_name == schema_name{
format!("{}_{}_{}", server_name, db_name, table_name)
} else {
format!("{}_{}_{}_{}", server_name, db_name, schema_name, table_name)
};
let struct_name = Self::sanitize_struct_name(&full_name);
if generated_structs.insert(struct_name.clone()) {
let name_field = format!("{}Field", struct_name);
let name_table = format!("Table{}", struct_name);
code.push_str(&format!(
"pub struct {}<'a> {{\n pub(crate) client: &'a crate::ClientSql,\n}}\n\n",
name_table
));
code.push_str(&format!("#[derive(Clone, Copy)]\npub struct {} {{\n", name_field));
let mut column_seen = std::collections::HashSet::new();
for column in &table.columns {
let rust_type = Self::type_translator(&column.data_type);
let mut safe_col_name = Self::sanitize_field_name(&column.name);
let mut counter = 2;
let name_base = safe_col_name.clone();
while !column_seen.insert(safe_col_name.clone()) {
safe_col_name = format!("{}_{}", name_base, counter);
counter += 1;
}
code.push_str(&format!(" pub {}: Column<{}>,\n", safe_col_name, rust_type));
}
code.push_str("}\n\n");
let real_table_path =if db_name==schema_name{
format!("{}.{}", schema_name, table_name)
} else {
format!("{}.{}.{}", db_name, schema_name, table_name)
};
code.push_str(&format!("impl<'a> {}<'a> {{\n", name_table));
code.push_str(&format!(" pub fn query(self) -> super::QueryBuilder<'a, {}> {{\n", name_field));
code.push_str(&format!(" let fields = {} {{\n", name_field));
let mut columns_seen_impl = std::collections::HashSet::new();
for col in &table.columns {
let rust_type = Self::type_translator(&col.data_type);
let mut safe_col_name = Self::sanitize_field_name(&col.name);
let mut counter = 2;
let name_base = safe_col_name.clone();
while !columns_seen_impl.insert(safe_col_name.clone()) {
safe_col_name = format!("{}_{}", name_base, counter);
counter += 1;
}
code.push_str(&format!(
" {}: Column {{ nome: r\"{}.{}\", tipo_rust: \"{}\", _tipo: PhantomData }},\n",
safe_col_name, real_table_path, col.name, rust_type
));
}
code.push_str(" };\n");
code.push_str(&format!(" super::QueryBuilder {{\n client: self.client,\n server_name: r\"{}\".to_string(),\n select_cols: None,\n expected_cols: None,\n expected_types: None,\n table_path: r\"{}\".to_string(),\n joins: Vec::new(),\n where_clause: None,\n is_distinct: false,\n top_limit: None,\n limit_amount: None,\n fields\n }}\n", server_name, real_table_path));
code.push_str(" }\n");
code.push_str("}\n\n");
}
}
}
}
code
}
fn sanitize_struct_name(name: &str) -> String {
let clean = Self::remove_accents(name);
let mut result = String::new();
let mut capitalize = true;
for c in clean.chars() {
if c.is_ascii_alphanumeric() {
if capitalize {
result.push(c.to_ascii_uppercase());
capitalize = false;
} else {
result.push(c.to_ascii_lowercase());
}
} else {
capitalize = true;
}
}
if result.is_empty() { return "TableObj".to_string(); }
if result.starts_with(|c: char| c.is_ascii_digit()) { result.insert(0, '_'); }
result
}
fn sanitize_field_name(name: &str) -> String {
let clean = Self::remove_accents(name);
let mut result = String::new();
for c in clean.chars() {
if c.is_ascii_alphanumeric() {
result.push(c.to_ascii_lowercase());
} else {
if !result.ends_with('_') { result.push('_'); }
}
}
let mut final_str = result.trim_matches('_').to_string();
if final_str.is_empty() { final_str = "col".to_string(); }
else if final_str.starts_with(|c: char| c.is_ascii_digit()) { final_str.insert(0, '_'); }
match final_str.as_str() {
"type" | "match" | "let" | "final" | "fn" | "virtual" | "macro" |"struct" | "enum" | "trait" | "impl" | "where" | "for" | "loop" | "while" | "if" | "else" | "return" | "break" | "continue" | "mut" | "ref" | "pub" | "use" | "mod" | "crate" | "self" | "super" | "as" | "in" | "move" | "const" | "static" | "true" | "false" => {
final_str.push_str("_col");
}
_ => {}
}
final_str
}
fn remove_accents(name: &str) -> String {
name.replace("á", "a").replace("ã", "a").replace("â", "a").replace("à", "a")
.replace("é", "e").replace("ê", "e")
.replace("í", "i")
.replace("ó", "o").replace("õ", "o").replace("ô", "o")
.replace("ú", "u")
.replace("ç", "c")
.replace("Á", "A").replace("Ã", "A").replace("Â", "A").replace("À", "A")
.replace("É", "E").replace("Ê", "E")
.replace("Í", "I")
.replace("Ó", "O").replace("Õ", "O").replace("Ô", "O")
.replace("Ú", "U")
.replace("Ç", "C")
}
pub fn type_translator(sql_type: &str)->&'static str{
let clean_type=sql_type.to_uppercase();
let base = clean_type.split('(').next().unwrap_or("").trim();
match base {
"INT" | "INTEGER" => "Option<i32>",
"BIGINT" => "Option<i64>",
"SMALLINT" => "Option<i16>",
"TINYINT" => "Option<u8>",
"BIT" => "Option<bool>",
"DECIMAL" | "NUMERIC" | "MONEY" => "Option<data_goes::rust_decimal::Decimal>",
"FLOAT" => "Option<f64>",
"REAL" => "Option<f32>",
"CHAR" | "NCHAR" | "VARCHAR" | "NVARCHAR" | "TEXT" | "NTEXT" | "XML" => "Option<String>",
"DATE" => "Option<data_goes::chrono::NaiveDate>",
"TIME" => "Option<data_goes::chrono::NaiveTime>",
"DATETIME" | "DATETIME2" | "SMALLDATETIME" => {
"Option<data_goes::chrono::NaiveDateTime>"
}
"DATETIMEOFFSET" => {
"Option<data_goes::chrono::DateTime<data_goes::chrono::FixedOffset>>"
}
"UNIQUEIDENTIFIER" => "Option<data_goes::uuid::Uuid>",
"VARBINARY" | "IMAGE" | "TIMESTAMP" => "Option<Vec<u8>>",
"Int8" => "Option<i8>",
"Int16" => "Option<i16>",
"Int32" => "Option<i32>",
"Int64" => "Option<i64>",
"UInt8" => "Option<u8>",
"UInt16" => "Option<u16>",
"UInt32" => "Option<u32>",
"UInt64" => "Option<u64>",
"Float32" => "Option<f32>",
"Float64" => "Option<f64>",
"Bool" => "Option<bool>",
"String" => "Option<String>",
"FixedString" => "Option<String>",
"LowCardinality(String)" => "Option<String>",
"LowCardinality(Nullable(String))" => "Option<String>",
"Date" => "Option<data_goes::chrono::NaiveDate>",
"Date32" => "Option<data_goes::chrono::NaiveDate>",
"DateTime" => "Option<data_goes::chrono::NaiveDateTime>",
"DateTime('America/Sao_Paulo')" => {
"Option<data_goes::chrono::NaiveDateTime>"
}
"DateTime64(3)"
| "DateTime64(6)"
| "DateTime64(9)" => "Option<data_goes::chrono::NaiveDateTime>",
"UUID" => "Option<data_goes::uuid::Uuid>",
"IPv6" => "Option<std::net::Ipv6Addr>",
"Decimal(15, 2)"
| "Decimal(16, 1)"
| "Decimal(16, 4)"
| "Decimal(18, 2)"
| "Decimal(38, 4)"
| "Decimal(23, 16)" => "Option<data_goes::rust_decimal::Decimal>",
"Nullable(String)" => "Option<String>",
"Nullable(Int16)" => "Option<i16>",
"Nullable(Int32)" => "Option<i32>",
"Nullable(Int64)" => "Option<i64>",
"Nullable(UInt8)" => "Option<u8>",
"Nullable(UInt16)" => "Option<u16>",
"Nullable(UInt32)" => "Option<u32>",
"Nullable(UInt64)" => "Option<u64>",
"Nullable(Float32)" => "Option<f32>",
"Nullable(Float64)" => "Option<f64>",
"Nullable(Date)" => "Option<data_goes::chrono::NaiveDate>",
"Nullable(Date32)" => "Option<data_goes::chrono::NaiveDate>",
"Nullable(DateTime)" => "Option<data_goes::chrono::NaiveDateTime>",
"Nullable(DateTime64(3))"
| "Nullable(DateTime64(6))" => {
"Option<data_goes::chrono::NaiveDateTime>"
}
"Nullable(Decimal(15, 2))"
| "Nullable(Decimal(16, 1))"
| "Nullable(Decimal(16, 4))"
| "Nullable(Decimal(38, 4))"
| "Nullable(Decimal(23, 16))" => {
"Option<data_goes::rust_decimal::Decimal>"
}
"Array(String)" => "Option<Vec<String>>",
"Array(LowCardinality(String))" => "Option<Vec<String>>",
"Array(UInt32)" => "Option<Vec<u32>>",
"Array(UInt64)" => "Option<Vec<u64>>",
"Array(Int32)" => "Option<Vec<i32>>",
"Array(Int64)" => "Option<Vec<i64>>",
"Array(Float64)" => "Option<Vec<f64>>",
"Array(DateTime)" => "Option<Vec<data_goes::chrono::NaiveDateTime>>",
"Array(DateTime64(9))" => {
"Option<Vec<data_goes::chrono::NaiveDateTime>>"
}
"Map(String, String)" => {
"Option<std::collections::HashMap<String, String>>"
}
"Map(String, UInt8)" => {
"Option<std::collections::HashMap<String, u8>>"
}
"Map(String, UInt64)" => {
"Option<std::collections::HashMap<String, u64>>"
}
"Map(LowCardinality(String), String)" => {
"Option<std::collections::HashMap<String, String>>"
}
"Map(LowCardinality(String), UInt64)" => {
"Option<std::collections::HashMap<String, u64>>"
}
"Map(LowCardinality(String), LowCardinality(String))" => {
"Option<std::collections::HashMap<String, String>>"
}
"Tuple(UInt64, UInt64, UUID)" => {
"Option<(u64, u64, data_goes::uuid::Uuid)>"
}
t if t.starts_with("Enum8(") => "Option<String>",
t if t.starts_with("Enum16(") => "Option<String>",
t if t.starts_with("Nullable(Enum8(") => "Option<String>",
t if t.starts_with("Nullable(Enum16(") => "Option<String>",
t if t.starts_with("Array(") => "Option<String>",
t if t.starts_with("Map(") => "Option<String>",
t if t.starts_with("Tuple(") => "Option<String>",
t if t.starts_with("LowCardinality(") => "Option<String>",
t if t.starts_with("Nullable(") => "Option<String>",
_ => "Option<String>",
}
}
}