1use rusticx_core::{
2 column::ColumnDef,
3 model::TableSchema,
4 query::{CondOp, Conjunction, Direction, QueryBuilder},
5 value::Value,
6};
7use crate::dialect::SqlDialect;
8
9pub struct SqlCompiler<'d, D: SqlDialect> {
11 dialect: &'d D,
12}
13
14impl<'d, D: SqlDialect> SqlCompiler<'d, D> {
15 pub fn new(dialect: &'d D) -> Self {
16 Self { dialect }
17 }
18
19 pub fn create_table(&self, schema: &TableSchema) -> String {
22 let table = self.dialect.quote_ident(&schema.table);
23 let col_defs: Vec<String> = schema.columns.iter().map(|c| self.col_def(c)).collect();
24
25 let pks: Vec<String> = schema
27 .columns
28 .iter()
29 .filter(|c| c.primary_key)
30 .map(|c| self.dialect.quote_ident(&c.name))
31 .collect();
32
33 let mut parts = col_defs;
34 if !pks.is_empty() {
35 parts.push(format!("PRIMARY KEY ({})", pks.join(", ")));
36 }
37
38 for col in schema.columns.iter().filter(|c| c.unique && !c.primary_key) {
40 parts.push(format!(
41 "UNIQUE ({})",
42 self.dialect.quote_ident(&col.name)
43 ));
44 }
45
46 let mut sql = format!(
47 "CREATE TABLE IF NOT EXISTS {table} (\n {}\n)",
48 parts.join(",\n ")
49 );
50
51 let mut index_stmts = vec![];
53 for idx in &schema.indexes {
54 let cols: Vec<String> = idx.columns.iter().map(|c| self.dialect.quote_ident(c)).collect();
55 let unique = if idx.unique { "UNIQUE " } else { "" };
56 index_stmts.push(format!(
57 "CREATE {unique}INDEX IF NOT EXISTS {} ON {table} ({})",
58 self.dialect.quote_ident(&idx.name),
59 cols.join(", ")
60 ));
61 }
62
63 if !index_stmts.is_empty() {
64 sql.push(';');
65 sql.push('\n');
66 sql.push_str(&index_stmts.join(";\n"));
67 }
68
69 sql
70 }
71
72 fn col_def(&self, col: &ColumnDef) -> String {
73 let name = self.dialect.quote_ident(&col.name);
74 let ty = self.dialect.sql_type(&col.col_type);
75 let mut parts = vec![format!("{name} {ty}")];
76
77 if !col.nullable {
78 parts.push("NOT NULL".into());
79 }
80 if let Some(ref d) = col.default {
81 parts.push(format!("DEFAULT {d}"));
82 }
83
84 parts.join(" ")
85 }
86
87 pub fn drop_table(&self, table: &str) -> String {
88 format!("DROP TABLE IF EXISTS {}", self.dialect.quote_ident(table))
89 }
90
91 pub fn table_exists(&self, table: &str) -> (String, Vec<Value>) {
92 let sql = "SELECT COUNT(*) FROM information_schema.tables WHERE table_name = $1".to_owned();
94 (sql, vec![Value::Text(table.to_owned())])
95 }
96
97 pub fn select(&self, qb: &QueryBuilder) -> (String, Vec<Value>) {
101 let table = self.dialect.quote_ident(&qb.table);
102 let cols = if qb.columns.is_empty() {
103 "*".to_owned()
104 } else {
105 qb.columns.iter().map(|c| self.dialect.quote_ident(c)).collect::<Vec<_>>().join(", ")
106 };
107
108 let mut sql = format!("SELECT {cols} FROM {table}");
109 let mut bindings: Vec<Value> = vec![];
110 let mut idx = 1usize;
111
112 if !qb.conditions.is_empty() {
113 let (where_clause, b, next_idx) = self.where_clause(&qb.conditions, idx);
114 sql.push_str(&format!(" WHERE {where_clause}"));
115 bindings.extend(b);
116 idx = next_idx;
117 }
118
119 if !qb.order_by.is_empty() {
120 let orders: Vec<String> = qb
121 .order_by
122 .iter()
123 .map(|o| {
124 let dir = match o.direction {
125 Direction::Asc => "ASC",
126 Direction::Desc => "DESC",
127 };
128 format!("{} {dir}", self.dialect.quote_ident(&o.column))
129 })
130 .collect();
131 sql.push_str(&format!(" ORDER BY {}", orders.join(", ")));
132 }
133
134 if let Some(limit) = qb.limit {
135 sql.push_str(&format!(" LIMIT {limit}"));
136 }
137 if let Some(offset) = qb.offset {
138 sql.push_str(&format!(" OFFSET {offset}"));
139 }
140
141 (sql, bindings)
142 }
143
144 pub fn insert(&self, table: &str, row: &[(String, Value)]) -> (String, Vec<Value>) {
146 let t = self.dialect.quote_ident(table);
147 let cols: Vec<String> = row.iter().map(|(c, _)| self.dialect.quote_ident(c)).collect();
148 let mut bindings: Vec<Value> = vec![];
149 let placeholders: Vec<String> = row
150 .iter()
151 .enumerate()
152 .map(|(i, (_, v))| {
153 bindings.push(v.clone());
154 self.dialect.placeholder(i + 1)
155 })
156 .collect();
157
158 let mut sql = format!(
159 "INSERT INTO {t} ({}) VALUES ({})",
160 cols.join(", "),
161 placeholders.join(", ")
162 );
163
164 if self.dialect.supports_returning() {
165 sql.push_str(" RETURNING *");
166 }
167
168 (sql, bindings)
169 }
170
171 pub fn update(&self, qb: &QueryBuilder) -> (String, Vec<Value>) {
173 let table = self.dialect.quote_ident(&qb.table);
174 let mut bindings: Vec<Value> = vec![];
175 let mut idx = 1usize;
176
177 let sets: Vec<String> = qb
178 .values
179 .iter()
180 .map(|(col, val)| {
181 let ph = self.dialect.placeholder(idx);
182 idx += 1;
183 bindings.push(val.clone());
184 format!("{} = {ph}", self.dialect.quote_ident(col))
185 })
186 .collect();
187
188 let mut sql = format!("UPDATE {table} SET {}", sets.join(", "));
189
190 if !qb.conditions.is_empty() {
191 let (where_clause, b, _) = self.where_clause(&qb.conditions, idx);
192 sql.push_str(&format!(" WHERE {where_clause}"));
193 bindings.extend(b);
194 }
195
196 (sql, bindings)
197 }
198
199 pub fn delete(&self, qb: &QueryBuilder) -> (String, Vec<Value>) {
201 let table = self.dialect.quote_ident(&qb.table);
202 let mut bindings: Vec<Value> = vec![];
203
204 let mut sql = format!("DELETE FROM {table}");
205
206 if !qb.conditions.is_empty() {
207 let (where_clause, b, _) = self.where_clause(&qb.conditions, 1);
208 sql.push_str(&format!(" WHERE {where_clause}"));
209 bindings.extend(b);
210 }
211
212 (sql, bindings)
213 }
214
215 pub fn count(&self, qb: &QueryBuilder) -> (String, Vec<Value>) {
217 let table = self.dialect.quote_ident(&qb.table);
218 let mut sql = format!("SELECT COUNT(*) AS count FROM {table}");
219 let mut bindings: Vec<Value> = vec![];
220
221 if !qb.conditions.is_empty() {
222 let (where_clause, b, _) = self.where_clause(&qb.conditions, 1);
223 sql.push_str(&format!(" WHERE {where_clause}"));
224 bindings.extend(b);
225 }
226
227 (sql, bindings)
228 }
229
230 fn where_clause(
233 &self,
234 conditions: &[rusticx_core::query::Condition],
235 start_idx: usize,
236 ) -> (String, Vec<Value>, usize) {
237 let mut parts: Vec<String> = vec![];
238 let mut bindings: Vec<Value> = vec![];
239 let mut idx = start_idx;
240
241 for (i, cond) in conditions.iter().enumerate() {
242 let col = self.dialect.quote_ident(&cond.column);
243 let (op_str, needs_binding) = match &cond.op {
244 CondOp::Eq => ("=", true),
245 CondOp::Ne => ("!=", true),
246 CondOp::Gt => (">", true),
247 CondOp::Gte => (">=", true),
248 CondOp::Lt => ("<", true),
249 CondOp::Lte => ("<=", true),
250 CondOp::Like => ("LIKE", true),
251 CondOp::ILike => ("ILIKE", true),
252 CondOp::In => ("IN", true),
253 CondOp::NotIn => ("NOT IN", true),
254 CondOp::IsNull => ("IS NULL", false),
255 CondOp::IsNotNull => ("IS NOT NULL", false),
256 };
257
258 let part = if needs_binding {
259 let ph = self.dialect.placeholder(idx);
260 idx += 1;
261 bindings.push(cond.value.clone());
262 format!("{col} {op_str} {ph}")
263 } else {
264 format!("{col} {op_str}")
265 };
266
267 let conj = if i == 0 {
268 String::new()
269 } else {
270 match cond.conjunction {
271 Conjunction::And => "AND ".to_owned(),
272 Conjunction::Or => "OR ".to_owned(),
273 }
274 };
275 parts.push(format!("{conj}{part}"));
276 }
277
278 (parts.join(" "), bindings, idx)
279 }
280}