Skip to main content

rusticx_sql/
dialect.rs

1use rusticx_core::column::ColumnType;
2
3/// SQL dialect controls syntax differences between backends.
4pub trait SqlDialect: Send + Sync {
5    /// Placeholder style: `$1` for Postgres, `?` for MySQL.
6    fn placeholder(&self, index: usize) -> String;
7
8    /// Quote an identifier (table name, column name).
9    fn quote_ident(&self, name: &str) -> String;
10
11    /// Map a ColumnType to dialect-specific SQL type string.
12    fn sql_type(&self, col_type: &ColumnType) -> String;
13
14    /// Whether RETURNING clause is supported (Postgres yes, MySQL no).
15    fn supports_returning(&self) -> bool;
16
17    /// Whether INSERT OR IGNORE / ON CONFLICT DO NOTHING is supported.
18    fn upsert_syntax(&self) -> UpsertSyntax;
19}
20
21#[derive(Debug, Clone, Copy)]
22pub enum UpsertSyntax {
23    /// PostgreSQL: ON CONFLICT DO NOTHING
24    OnConflict,
25    /// MySQL: INSERT IGNORE
26    InsertIgnore,
27    /// Not supported
28    None,
29}
30
31// ── Postgres dialect ──────────────────────────────────────────────────────────
32
33pub struct PostgresDialect;
34
35impl SqlDialect for PostgresDialect {
36    fn placeholder(&self, index: usize) -> String {
37        format!("${index}")
38    }
39
40    fn quote_ident(&self, name: &str) -> String {
41        format!("\"{name}\"")
42    }
43
44    fn sql_type(&self, col_type: &ColumnType) -> String {
45        match col_type {
46            ColumnType::Bool => "BOOLEAN".into(),
47            ColumnType::SmallInt => "SMALLINT".into(),
48            ColumnType::Int => "INTEGER".into(),
49            ColumnType::BigInt => "BIGINT".into(),
50            ColumnType::Float => "REAL".into(),
51            ColumnType::Double => "DOUBLE PRECISION".into(),
52            ColumnType::Decimal { precision, scale } => format!("DECIMAL({precision},{scale})"),
53            ColumnType::Text => "TEXT".into(),
54            ColumnType::Varchar(n) => format!("VARCHAR({n})"),
55            ColumnType::Char(n) => format!("CHAR({n})"),
56            ColumnType::Bytes => "BYTEA".into(),
57            ColumnType::Uuid => "UUID".into(),
58            ColumnType::Timestamp => "TIMESTAMP".into(),
59            ColumnType::TimestampTz => "TIMESTAMPTZ".into(),
60            ColumnType::Date => "DATE".into(),
61            ColumnType::Time => "TIME".into(),
62            ColumnType::Json => "JSON".into(),
63            ColumnType::Jsonb => "JSONB".into(),
64            ColumnType::Array(inner) => format!("{}[]", self.sql_type(inner)),
65            ColumnType::Dynamic => "JSONB".into(),
66        }
67    }
68
69    fn supports_returning(&self) -> bool {
70        true
71    }
72
73    fn upsert_syntax(&self) -> UpsertSyntax {
74        UpsertSyntax::OnConflict
75    }
76}
77
78// ── MySQL / MariaDB dialect ───────────────────────────────────────────────────
79
80pub struct MysqlDialect;
81
82impl SqlDialect for MysqlDialect {
83    fn placeholder(&self, _index: usize) -> String {
84        "?".into()
85    }
86
87    fn quote_ident(&self, name: &str) -> String {
88        format!("`{name}`")
89    }
90
91    fn sql_type(&self, col_type: &ColumnType) -> String {
92        match col_type {
93            ColumnType::Bool => "TINYINT(1)".into(),
94            ColumnType::SmallInt => "SMALLINT".into(),
95            ColumnType::Int => "INT".into(),
96            ColumnType::BigInt => "BIGINT".into(),
97            ColumnType::Float => "FLOAT".into(),
98            ColumnType::Double => "DOUBLE".into(),
99            ColumnType::Decimal { precision, scale } => format!("DECIMAL({precision},{scale})"),
100            ColumnType::Text => "TEXT".into(),
101            ColumnType::Varchar(n) => format!("VARCHAR({n})"),
102            ColumnType::Char(n) => format!("CHAR({n})"),
103            ColumnType::Bytes => "BLOB".into(),
104            ColumnType::Uuid => "CHAR(36)".into(),
105            ColumnType::Timestamp | ColumnType::TimestampTz => "DATETIME(6)".into(),
106            ColumnType::Date => "DATE".into(),
107            ColumnType::Time => "TIME".into(),
108            ColumnType::Json | ColumnType::Jsonb => "JSON".into(),
109            ColumnType::Array(_) => "JSON".into(), // MySQL has no array type
110            ColumnType::Dynamic => "JSON".into(),
111        }
112    }
113
114    fn supports_returning(&self) -> bool {
115        false
116    }
117
118    fn upsert_syntax(&self) -> UpsertSyntax {
119        UpsertSyntax::InsertIgnore
120    }
121}