Skip to main content

remodel_core/sql/
dialect.rs

1//! SQL dialects supported by the DDL exporter.
2
3use serde::{Deserialize, Serialize};
4
5use crate::models::logical::ReferentialAction;
6use crate::models::types::DataType;
7
8/// One of the dialects RemodelCore knows how to render.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10pub enum SqlDialect {
11    /// PostgreSQL 12+.
12    Postgres,
13    /// MySQL 8 / MariaDB 10+.
14    MySql,
15    /// SQLite 3.
16    Sqlite,
17}
18
19impl SqlDialect {
20    /// Get a [`Dialect`] trait object for this enum value.
21    pub fn renderer(self) -> Box<dyn Dialect> {
22        match self {
23            SqlDialect::Postgres => Box::new(Postgres),
24            SqlDialect::MySql => Box::new(MySql),
25            SqlDialect::Sqlite => Box::new(Sqlite),
26        }
27    }
28}
29
30/// Dialect-specific rendering hooks.
31pub trait Dialect {
32    /// Render a [`DataType`] as a SQL type expression.
33    fn render_type(&self, ty: &DataType) -> String;
34
35    /// Quote an identifier (column / table name).
36    fn quote_ident(&self, ident: &str) -> String;
37
38    /// Render a referential action verbatim.
39    fn render_action(&self, action: ReferentialAction) -> &'static str {
40        match action {
41            ReferentialAction::NoAction => "NO ACTION",
42            ReferentialAction::Restrict => "RESTRICT",
43            ReferentialAction::Cascade => "CASCADE",
44            ReferentialAction::SetNull => "SET NULL",
45            ReferentialAction::SetDefault => "SET DEFAULT",
46        }
47    }
48
49    /// Statement terminator (almost always `;`).
50    fn terminator(&self) -> &'static str {
51        ";"
52    }
53}
54
55/// PostgreSQL renderer.
56pub struct Postgres;
57impl Dialect for Postgres {
58    fn render_type(&self, ty: &DataType) -> String {
59        match ty {
60            DataType::Integer => "INTEGER".into(),
61            DataType::BigInt => "BIGINT".into(),
62            DataType::SmallInt => "SMALLINT".into(),
63            DataType::Real => "DOUBLE PRECISION".into(),
64            DataType::Decimal(p, s) => format!("NUMERIC({p},{s})"),
65            DataType::Boolean => "BOOLEAN".into(),
66            DataType::Varchar(n) => format!("VARCHAR({n})"),
67            DataType::Char(n) => format!("CHAR({n})"),
68            DataType::Text => "TEXT".into(),
69            DataType::Date => "DATE".into(),
70            DataType::Time => "TIME".into(),
71            DataType::Timestamp => "TIMESTAMP".into(),
72            DataType::Uuid => "UUID".into(),
73            DataType::Bytes => "BYTEA".into(),
74            DataType::Custom(s) => s.clone(),
75        }
76    }
77
78    fn quote_ident(&self, ident: &str) -> String {
79        format!("\"{}\"", ident.replace('"', "\"\""))
80    }
81}
82
83/// MySQL / MariaDB renderer.
84pub struct MySql;
85impl Dialect for MySql {
86    fn render_type(&self, ty: &DataType) -> String {
87        match ty {
88            DataType::Integer => "INT".into(),
89            DataType::BigInt => "BIGINT".into(),
90            DataType::SmallInt => "SMALLINT".into(),
91            DataType::Real => "DOUBLE".into(),
92            DataType::Decimal(p, s) => format!("DECIMAL({p},{s})"),
93            DataType::Boolean => "TINYINT(1)".into(),
94            DataType::Varchar(n) => format!("VARCHAR({n})"),
95            DataType::Char(n) => format!("CHAR({n})"),
96            DataType::Text => "TEXT".into(),
97            DataType::Date => "DATE".into(),
98            DataType::Time => "TIME".into(),
99            DataType::Timestamp => "DATETIME".into(),
100            DataType::Uuid => "CHAR(36)".into(),
101            DataType::Bytes => "BLOB".into(),
102            DataType::Custom(s) => s.clone(),
103        }
104    }
105
106    fn quote_ident(&self, ident: &str) -> String {
107        format!("`{}`", ident.replace('`', "``"))
108    }
109}
110
111/// SQLite 3 renderer.
112pub struct Sqlite;
113impl Dialect for Sqlite {
114    fn render_type(&self, ty: &DataType) -> String {
115        match ty {
116            DataType::Integer | DataType::SmallInt | DataType::BigInt => "INTEGER".into(),
117            DataType::Real | DataType::Decimal(_, _) => "REAL".into(),
118            DataType::Boolean => "INTEGER".into(),
119            DataType::Varchar(_) | DataType::Char(_) | DataType::Text | DataType::Uuid => "TEXT".into(),
120            DataType::Date | DataType::Time | DataType::Timestamp => "TEXT".into(),
121            DataType::Bytes => "BLOB".into(),
122            DataType::Custom(s) => s.clone(),
123        }
124    }
125
126    fn quote_ident(&self, ident: &str) -> String {
127        format!("\"{}\"", ident.replace('"', "\"\""))
128    }
129}