1mod expr;
9mod function;
10mod lookup;
11#[cfg(test)]
12mod tests;
13#[cfg(test)]
14mod tests_mysql;
15#[cfg(all(test, feature = "mysql"))]
16mod tests_write_bare;
17
18use super::dialect::Dialect;
19use siderite_orm::expr::Ident;
20use siderite_orm::{
21 BackendKind, DistinctMode, Expr, JoinKind, LockMode, OrderDirection, OrmError, QueryError,
22 QueryPlan, QuerySource, SetOp, Value, WritePlan,
23};
24use std::fmt::Write;
25
26#[derive(Debug, Clone, PartialEq)]
28pub struct CompiledQuery {
29 pub sql: String,
31 pub params: Vec<Value>,
33}
34
35pub fn compile(plan: &QueryPlan, dialect: &dyn Dialect) -> Result<CompiledQuery, OrmError> {
42 plan.check(&dialect.capabilities())?;
43 let mut c = Compiler::new(dialect);
44 c.plan(plan);
45 c.finish()
46}
47
48pub fn compile_write(plan: &WritePlan, dialect: &dyn Dialect) -> Result<CompiledQuery, OrmError> {
55 compile_write_inner(plan, dialect, true)
56}
57
58#[cfg(feature = "mysql")]
68pub(crate) fn compile_write_bare(
69 plan: &WritePlan,
70 dialect: &dyn Dialect,
71) -> Result<CompiledQuery, OrmError> {
72 compile_write_inner(plan, dialect, false)
73}
74
75fn compile_write_inner(
76 plan: &WritePlan,
77 dialect: &dyn Dialect,
78 returning: bool,
79) -> Result<CompiledQuery, OrmError> {
80 let caps = dialect.capabilities();
81 for feature in plan.required_features() {
82 if returning || feature != siderite_orm::Feature::Returning {
83 caps.require(feature)?;
84 }
85 }
86 let mut c = Compiler::new(dialect);
87 match plan {
88 WritePlan::Insert(p) => {
89 if p.rows.is_empty() || p.rows.iter().any(|r| r.len() != p.columns.len()) {
90 return Err(QueryError::InvalidPlan(
91 "insert rows must be non-empty and match the column list".into(),
92 )
93 .into());
94 }
95 c.push("INSERT INTO ");
96 c.ident(&p.table);
97 if p.columns.is_empty() {
98 if p.rows.len() > 1 {
100 return Err(QueryError::InvalidPlan(
101 "multi-row insert needs at least one explicit column".into(),
102 )
103 .into());
104 }
105 c.push(dialect.default_values_sql());
106 } else {
107 c.push(" (");
108 c.list(&p.columns, ", ", |c, col| c.ident(col));
109 c.push(") VALUES ");
110 c.list(&p.rows, ", ", |c, row| {
111 c.push("(");
112 c.list(row, ", ", |c, v| c.value(v));
113 c.push(")");
114 });
115 }
116 }
117 WritePlan::Update(p) => {
118 if p.assignments.is_empty() {
119 return Err(QueryError::InvalidPlan("update without assignments".into()).into());
120 }
121 let assignments = if dialect.evaluates_assignments_in_order() {
122 order_assignments(&p.assignments)?
123 } else {
124 p.assignments.clone()
125 };
126 c.write_scope(&p.table);
127 c.push("UPDATE ");
128 c.ident(&p.table);
129 c.push(" SET ");
130 c.list(&assignments, ", ", |c, (col, e)| {
131 c.ident(col);
132 c.push(" = ");
133 c.expr(e);
134 });
135 c.where_clause(p.filter.as_ref());
136 }
137 WritePlan::Delete(p) => {
138 c.write_scope(&p.table);
139 c.push("DELETE FROM ");
140 c.ident(&p.table);
141 c.where_clause(p.filter.as_ref());
142 }
143 }
144 if returning {
147 c.returning(plan.returning());
148 }
149 c.finish()
150}
151
152fn order_assignments(assignments: &[(Ident, Expr)]) -> Result<Vec<(Ident, Expr)>, OrmError> {
161 let reads: Vec<Vec<String>> = assignments
162 .iter()
163 .map(|(_, e)| {
164 let mut columns = Vec::new();
165 expr_reads(e, 0, &mut columns);
166 columns
167 })
168 .collect();
169 let n = assignments.len();
170 let before: Vec<Vec<usize>> = (0..n)
172 .map(|i| {
173 (0..n)
174 .filter(|&j| j != i && reads[j].iter().any(|c| *c == *assignments[i].0))
175 .collect()
176 })
177 .collect();
178 let mut order: Vec<usize> = Vec::with_capacity(n);
179 while order.len() < n {
180 let next =
181 (0..n).find(|i| !order.contains(i) && before[*i].iter().all(|j| order.contains(j)));
182 match next {
183 Some(i) => order.push(i),
184 None => {
185 return Err(QueryError::InvalidPlan(
186 "MySQL evaluates SET assignments left to right; assignments that read \
187 each other's columns in a cycle cannot be expressed"
188 .into(),
189 )
190 .into());
191 }
192 }
193 }
194 Ok(order.into_iter().map(|i| assignments[i].clone()).collect())
195}
196
197fn expr_reads(expr: &Expr, depth: usize, out: &mut Vec<String>) {
200 match expr {
201 Expr::Column(c) if depth == 0 => out.push(c.name.to_string()),
202 Expr::OuterRef(c) if depth == 1 => out.push(c.name.to_string()),
203 _ => {}
204 }
205 for child in expr.children() {
206 expr_reads(child, depth, out);
207 }
208 if let Some(plan) = expr.subplan() {
209 plan_reads(plan, depth + 1, out);
210 }
211}
212
213fn plan_reads(plan: &QueryPlan, depth: usize, out: &mut Vec<String>) {
214 let distinct_on = match &plan.distinct {
215 DistinctMode::On(exprs) => exprs.as_slice(),
216 _ => &[],
217 };
218 let exprs = plan
219 .projection
220 .iter()
221 .map(|s| &s.expr)
222 .chain(plan.joins.iter().map(|j| &j.on))
223 .chain(plan.filter.iter())
224 .chain(plan.having.iter())
225 .chain(plan.grouping.iter())
226 .chain(plan.ordering.iter().map(|o| &o.expr))
227 .chain(distinct_on);
228 for e in exprs {
229 expr_reads(e, depth, out);
230 }
231 if let Some(inner) = plan.source.subquery.as_deref() {
232 plan_reads(inner, depth + 1, out);
233 }
234 for member in &plan.compound {
235 plan_reads(&member.plan, depth, out);
236 }
237}
238
239fn reads_table(plan: &QueryPlan, table: &str) -> bool {
242 plan.source.name == table
243 || plan.joins.iter().any(|j| j.source.name == table)
244 || plan
245 .source
246 .subquery
247 .as_deref()
248 .is_some_and(|inner| reads_table(inner, table))
249 || plan.compound.iter().any(|c| reads_table(&c.plan, table))
250}
251
252struct Scope {
255 reference: String,
256 qualify: bool,
257}
258
259struct Compiler<'d> {
260 dialect: &'d dyn Dialect,
261 native_ilike: bool,
262 kind: BackendKind,
263 sql: String,
264 params: Vec<Value>,
265 scopes: Vec<Scope>,
267 derived: usize,
269 write_table: Option<String>,
271 error: Option<QueryError>,
273}
274
275impl<'d> Compiler<'d> {
276 fn new(dialect: &'d dyn Dialect) -> Self {
277 let caps = dialect.capabilities();
278 Self {
279 dialect,
280 native_ilike: caps.ilike,
281 kind: caps.kind,
282 sql: String::new(),
283 params: Vec::new(),
284 scopes: Vec::new(),
285 derived: 0,
286 write_table: None,
287 error: None,
288 }
289 }
290
291 fn finish(self) -> Result<CompiledQuery, OrmError> {
292 match self.error {
293 Some(err) => Err(err.into()),
294 None => Ok(CompiledQuery {
295 sql: self.sql,
296 params: self.params,
297 }),
298 }
299 }
300
301 fn fail(&mut self, message: impl Into<String>) {
303 self.error
304 .get_or_insert_with(|| QueryError::InvalidPlan(message.into()));
305 }
306
307 fn returning(&mut self, columns: &[Ident]) {
308 if !columns.is_empty() {
309 self.push(" RETURNING ");
310 self.list(columns, ", ", |c, col| c.ident(col));
311 }
312 }
313
314 fn where_clause(&mut self, filter: Option<&siderite_orm::Expr>) {
315 if let Some(f) = filter {
316 self.push(" WHERE ");
317 self.expr(f);
318 }
319 }
320
321 fn push(&mut self, s: &str) {
322 self.sql.push_str(s);
323 }
324
325 fn ident(&mut self, ident: &str) {
326 self.dialect.write_ident(&mut self.sql, ident);
327 }
328
329 fn write_scope(&mut self, table: &str) {
332 self.write_table = Some(table.to_owned());
333 self.scopes.push(Scope {
334 reference: table.to_owned(),
335 qualify: false,
336 });
337 }
338
339 fn operand_subquery(&mut self, plan: &QueryPlan, scalar: bool) {
345 let wrap = self.kind == BackendKind::MySql
346 && match &self.write_table {
347 Some(table) => !scalar || reads_table(plan, table),
348 None => !scalar && (plan.limit.is_some() || plan.offset.is_some()),
349 };
350 if !wrap {
351 self.plan(plan);
352 return;
353 }
354 self.derived += 1;
355 let alias = format!("u{}", self.derived);
356 self.push("SELECT * FROM (");
357 self.plan(plan);
358 self.push(") AS ");
359 self.ident(&alias);
360 }
361
362 fn value(&mut self, v: &Value) {
370 if v.is_null() {
371 self.push("NULL");
372 } else {
373 self.bind(v.clone());
374 }
375 }
376
377 fn bind(&mut self, v: Value) {
378 self.params.push(v);
379 self.dialect
380 .write_placeholder(&mut self.sql, self.params.len());
381 }
382
383 fn list<T>(&mut self, items: &[T], sep: &str, mut f: impl FnMut(&mut Self, &T)) {
384 for (i, item) in items.iter().enumerate() {
385 if i > 0 {
386 self.push(sep);
387 }
388 f(self, item);
389 }
390 }
391
392 fn plan(&mut self, p: &QueryPlan) {
395 self.scopes.push(Scope {
396 reference: p.source.reference().to_string(),
397 qualify: !p.joins.is_empty(),
398 });
399 self.select(p);
400 for member in &p.compound {
401 self.push(match member.op {
402 SetOp::Union => " UNION ",
403 SetOp::UnionAll => " UNION ALL ",
404 SetOp::Intersect => " INTERSECT ",
405 SetOp::Except => " EXCEPT ",
406 });
407 self.set_member(&member.plan);
408 }
409 self.tail(p);
410 self.scopes.pop();
411 }
412
413 fn set_member(&mut self, member: &QueryPlan) {
417 let needs_wrap = !member.ordering.is_empty()
418 || member.limit.is_some()
419 || member.offset.is_some()
420 || member.lock.is_some()
421 || !member.compound.is_empty();
422 if !needs_wrap {
423 self.plan(member);
424 return;
425 }
426 self.derived += 1;
427 let alias = format!("u{}", self.derived);
428 self.push("SELECT * FROM (");
429 self.plan(member);
430 self.push(") AS ");
431 self.ident(&alias);
432 }
433
434 fn select(&mut self, p: &QueryPlan) {
436 self.push("SELECT ");
437 match &p.distinct {
438 DistinctMode::None => {}
439 DistinctMode::All => self.push("DISTINCT "),
440 DistinctMode::On(exprs) => {
441 self.push("DISTINCT ON (");
442 self.list(exprs, ", ", Self::expr);
443 self.push(") ");
444 }
445 }
446 if !p.projection.is_empty() {
447 self.list(&p.projection, ", ", |c, s| {
448 c.expr(&s.expr);
449 if let Some(alias) = &s.alias {
450 c.push(" AS ");
451 c.ident(alias);
452 }
453 });
454 } else if p.joins.is_empty() {
455 self.push("*");
456 } else {
457 let root = p.source.reference().clone();
459 self.ident(&root);
460 self.push(".*");
461 }
462 self.push(" FROM ");
463 self.source(&p.source);
464 for j in &p.joins {
465 self.push(match j.kind {
466 JoinKind::Inner => " INNER JOIN ",
467 JoinKind::Left => " LEFT JOIN ",
468 });
469 self.source(&j.source);
470 self.push(" ON ");
471 self.expr(&j.on);
472 }
473 self.where_clause(p.filter.as_ref());
474 if !p.grouping.is_empty() {
475 self.push(" GROUP BY ");
476 self.list(&p.grouping, ", ", Self::expr);
477 }
478 if let Some(h) = &p.having {
479 self.push(" HAVING ");
480 self.expr(h);
481 }
482 }
483
484 fn tail(&mut self, p: &QueryPlan) {
486 if !p.ordering.is_empty() {
487 self.push(" ORDER BY ");
488 self.list(&p.ordering, ", ", |c, o| {
489 c.expr(&o.expr);
490 c.push(match o.direction {
491 OrderDirection::Asc => " ASC",
492 OrderDirection::Desc => " DESC",
493 });
494 });
495 }
496 match (p.limit, p.offset) {
497 (Some(l), _) => {
498 let _ = write!(self.sql, " LIMIT {l}");
499 }
500 (None, Some(_)) if self.dialect.offset_requires_limit() => {
501 let _ = write!(self.sql, " LIMIT {}", self.dialect.unbounded_limit());
502 }
503 _ => {}
504 }
505 if let Some(o) = p.offset {
506 let _ = write!(self.sql, " OFFSET {o}");
507 }
508 if let Some(lock) = p.lock {
509 self.push(match lock {
510 LockMode::ForUpdate => " FOR UPDATE",
511 LockMode::ForUpdateNoWait => " FOR UPDATE NOWAIT",
512 LockMode::ForUpdateSkipLocked => " FOR UPDATE SKIP LOCKED",
513 });
514 }
515 }
516
517 fn source(&mut self, s: &QuerySource) {
518 if let Some(inner) = &s.subquery {
519 self.push("(");
520 self.plan(inner);
521 self.push(")");
522 } else {
523 self.ident(&s.name);
524 if let Some(alias) = &s.alias {
525 self.push(" AS ");
526 self.ident(alias);
527 }
528 return;
529 }
530 self.push(" AS ");
531 self.ident(s.reference());
532 }
533}