Skip to main content

reifydb_sql/
emit.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use std::collections::HashSet;
5
6use crate::{Error, ast::*};
7
8pub fn emit(stmt: &Statement) -> Result<String, Error> {
9	match stmt {
10		Statement::Select(sel) => emit_select_top(sel),
11		Statement::Insert(ins) => emit_insert(ins),
12		Statement::Update(upd) => emit_update(upd),
13		Statement::Delete(del) => emit_delete(del),
14		Statement::CreateTable(ct) => emit_create_table(ct),
15		Statement::CreateIndex(ci) => emit_create_index(ci),
16		Statement::DropTable(dt) => emit_drop_table(dt),
17	}
18}
19
20fn emit_select_top(sel: &SelectStatement) -> Result<String, Error> {
21	let mut cte_names = HashSet::new();
22	let mut parts = Vec::new();
23
24	for cte in &sel.ctes {
25		let name_lower = cte.name.to_ascii_lowercase();
26		let pipeline = emit_select_inner(&cte.query, &cte_names)?;
27		parts.push(format!("LET ${name_lower} = {pipeline}"));
28		cte_names.insert(name_lower);
29	}
30
31	parts.push(emit_select_inner(sel, &cte_names)?);
32
33	Ok(parts.join("; "))
34}
35
36fn emit_select(sel: &SelectStatement) -> Result<String, Error> {
37	emit_select_inner(sel, &HashSet::new())
38}
39
40fn emit_select_inner(sel: &SelectStatement, cte_names: &HashSet<String>) -> Result<String, Error> {
41	if sel.from.is_none() {
42		let base = emit_select_no_from(sel)?;
43
44		if let Some((op, right)) = &sel.set_op {
45			let right_rql = emit_select_inner(right, cte_names)?;
46			let op_str = match op {
47				SetOp::Union => "UNION",
48				SetOp::UnionAll => "UNION ALL",
49				SetOp::Intersect => "INTERSECT",
50				SetOp::Except => "EXCEPT",
51			};
52			return Ok(format!("{op_str} {{{base}}} {{{right_rql}}}"));
53		}
54		return Ok(base);
55	}
56
57	let mut parts = Vec::new();
58
59	let from = sel.from.as_ref().unwrap();
60	parts.push(emit_from_clause(from, cte_names)?);
61
62	for join in &sel.joins {
63		parts.push(emit_join(join, cte_names)?);
64	}
65
66	if let Some(ref where_clause) = sel.where_clause {
67		parts.push(format!("FILTER {{{}}}", emit_expr(where_clause)?));
68	}
69
70	let has_aggregates = has_aggregate_functions(&sel.columns);
71	if has_aggregates || !sel.group_by.is_empty() {
72		let agg_exprs = collect_aggregate_columns(&sel.columns)?;
73		let by_exprs = emit_expr_comma_list(&sel.group_by)?;
74
75		if !agg_exprs.is_empty() {
76			let mut agg_str = format!("AGGREGATE {{{agg_exprs}}}");
77			if !sel.group_by.is_empty() {
78				agg_str.push_str(&format!(" BY {{{by_exprs}}}"));
79			}
80			parts.push(agg_str);
81		}
82
83		if let Some(ref having) = sel.having {
84			parts.push(format!("FILTER {{{}}}", emit_expr(having)?));
85		}
86
87		let map_exprs = collect_non_aggregate_map_columns(sel)?;
88		if !map_exprs.is_empty() {
89			parts.push(format!("MAP {{{map_exprs}}}"));
90		}
91	} else if sel.distinct {
92		if is_all_columns(&sel.columns) {
93			parts.push("DISTINCT {}".into());
94		} else {
95			let cols = emit_select_columns_plain(&sel.columns)?;
96			parts.push(format!("DISTINCT {{{cols}}}"));
97		}
98	} else if !is_all_columns(&sel.columns) {
99		let cols = emit_select_columns(&sel.columns)?;
100		parts.push(format!("MAP {{{cols}}}"));
101	}
102
103	if !sel.order_by.is_empty() {
104		let sort = emit_order_by(&sel.order_by)?;
105		parts.push(format!("SORT {{{sort}}}"));
106	}
107
108	if let Some(limit) = sel.limit {
109		parts.push(format!("TAKE {limit}"));
110	}
111
112	if let Some(offset) = sel.offset {
113		parts.push(format!("OFFSET {offset}"));
114	}
115
116	let base = parts.join(" ");
117
118	if let Some((op, right)) = &sel.set_op {
119		let right_rql = emit_select_inner(right, cte_names)?;
120		let op_str = match op {
121			SetOp::Union => "UNION",
122			SetOp::UnionAll => "UNION ALL",
123			SetOp::Intersect => "INTERSECT",
124			SetOp::Except => "EXCEPT",
125		};
126		return Ok(format!("{op_str} {{{base}}} {{{right_rql}}}"));
127	}
128
129	Ok(base)
130}
131
132fn emit_select_no_from(sel: &SelectStatement) -> Result<String, Error> {
133	let cols = emit_select_columns(&sel.columns)?;
134	Ok(format!("MAP {{{cols}}}"))
135}
136
137fn emit_from_clause(from: &FromClause, cte_names: &HashSet<String>) -> Result<String, Error> {
138	match from {
139		FromClause::Table {
140			name,
141			shape,
142			alias,
143		} => {
144			if shape.is_none() && cte_names.contains(&name.to_ascii_lowercase()) {
145				let base = format!("FROM ${}", name.to_ascii_lowercase());
146				if let Some(alias) = alias {
147					Ok(format!("{base} AS {alias}"))
148				} else {
149					Ok(base)
150				}
151			} else if let Some(shape) = shape {
152				let base = format!("FROM {shape}::{name}");
153				if let Some(alias) = alias {
154					Ok(format!("{base} AS {alias}"))
155				} else {
156					Ok(base)
157				}
158			} else {
159				let base = format!("FROM {name}");
160				if let Some(alias) = alias {
161					Ok(format!("{base} AS {alias}"))
162				} else {
163					Ok(base)
164				}
165			}
166		}
167		FromClause::Subquery(sel) => {
168			let inner = emit_select(sel)?;
169			Ok(format!("FROM {{{inner}}}"))
170		}
171	}
172}
173
174fn emit_join(join: &JoinClause, cte_names: &HashSet<String>) -> Result<String, Error> {
175	let join_kw = match join.join_type {
176		JoinType::Inner => "JOIN",
177		JoinType::Left => "LEFT JOIN",
178		JoinType::Cross => "CROSS JOIN",
179	};
180
181	let table_name = match &join.table {
182		FromClause::Table {
183			name,
184			shape,
185			..
186		} => {
187			if shape.is_none() && cte_names.contains(&name.to_ascii_lowercase()) {
188				format!("${}", name.to_ascii_lowercase())
189			} else if let Some(shape) = shape {
190				format!("{shape}::{name}")
191			} else {
192				name.clone()
193			}
194		}
195		FromClause::Subquery(sel) => emit_select(sel)?,
196	};
197
198	let alias = join
199		.table_alias
200		.as_deref()
201		.or(match &join.table {
202			FromClause::Table {
203				name,
204				..
205			} => Some(name.as_str()),
206			_ => None,
207		})
208		.unwrap_or("_");
209
210	if matches!(join.join_type, JoinType::Cross) {
211		return Ok(format!("{join_kw} {{FROM {table_name}}} AS {alias}"));
212	}
213
214	let using = emit_join_using(&join.on, alias)?;
215
216	Ok(format!("{join_kw} {{FROM {table_name}}} AS {alias} USING ({using})"))
217}
218
219fn emit_join_using(on_expr: &Expr, right_alias: &str) -> Result<String, Error> {
220	match on_expr {
221		Expr::BinaryOp {
222			left,
223			op: BinaryOp::Eq,
224			right,
225		} => {
226			let (left_col, right_col) = extract_join_columns(left, right, right_alias)?;
227			Ok(format!("{left_col}, {right_col}"))
228		}
229		Expr::BinaryOp {
230			left,
231			op: BinaryOp::And,
232			right,
233		} => {
234			let l = emit_join_using(left, right_alias)?;
235			let r = emit_join_using(right, right_alias)?;
236			Ok(format!("{l}, {r}"))
237		}
238		_ => emit_expr(on_expr),
239	}
240}
241
242fn extract_join_columns(left: &Expr, right: &Expr, right_alias: &str) -> Result<(String, String), Error> {
243	let left_col = match left {
244		Expr::QualifiedIdentifier(_table, col) => col.clone(),
245		Expr::Identifier(col) => col.clone(),
246		_ => emit_expr(left)?,
247	};
248	let right_col = match right {
249		Expr::QualifiedIdentifier(table, col) => format!("{table}.{col}"),
250		Expr::Identifier(col) => format!("{right_alias}.{col}"),
251		_ => emit_expr(right)?,
252	};
253	Ok((left_col, right_col))
254}
255
256fn emit_insert(ins: &InsertStatement) -> Result<String, Error> {
257	let table = if let Some(ref shape) = ins.shape {
258		format!("{shape}::{}", ins.table)
259	} else {
260		ins.table.clone()
261	};
262
263	match &ins.source {
264		InsertSource::Values(values) => {
265			let mut rows = Vec::new();
266			for row_values in values {
267				if ins.columns.is_empty() {
268					let vals: Result<Vec<_>, _> = row_values.iter().map(emit_expr).collect();
269					rows.push(format!("({})", vals?.join(", ")));
270				} else {
271					let mut fields = Vec::new();
272					for (i, val) in row_values.iter().enumerate() {
273						let col_name = if i < ins.columns.len() {
274							&ins.columns[i]
275						} else {
276							return Err(Error("more values than columns in INSERT".into()));
277						};
278						fields.push(format!("{}: {}", col_name, emit_expr(val)?));
279					}
280					rows.push(format!("{{{}}}", fields.join(", ")));
281				}
282			}
283			Ok(format!("INSERT {} [{}]", table, rows.join(", ")))
284		}
285		InsertSource::Select(sel) => {
286			let select_rql = emit_select(sel)?;
287			if ins.columns.is_empty() {
288				Ok(format!("INSERT {table} {{{select_rql}}}"))
289			} else {
290				let cols = ins.columns.join(", ");
291				Ok(format!("INSERT {table} ({cols}) {{{select_rql}}}"))
292			}
293		}
294	}
295}
296
297fn emit_update(upd: &UpdateStatement) -> Result<String, Error> {
298	let table = if let Some(ref shape) = upd.shape {
299		format!("{shape}::{}", upd.table)
300	} else {
301		upd.table.clone()
302	};
303
304	let mut assignments = Vec::new();
305	for (col, val) in &upd.assignments {
306		assignments.push(format!("{}: {}", col, emit_expr(val)?));
307	}
308
309	let mut result = format!("UPDATE {} {{{}}}", table, assignments.join(", "));
310
311	if let Some(ref where_clause) = upd.where_clause {
312		result.push_str(&format!(" FILTER {{{}}}", emit_expr(where_clause)?));
313	}
314
315	Ok(result)
316}
317
318fn emit_delete(del: &DeleteStatement) -> Result<String, Error> {
319	let table = if let Some(ref shape) = del.shape {
320		format!("{shape}::{}", del.table)
321	} else {
322		del.table.clone()
323	};
324
325	let mut result = format!("DELETE {table}");
326
327	if let Some(ref where_clause) = del.where_clause {
328		result.push_str(&format!(" FILTER {{{}}}", emit_expr(where_clause)?));
329	}
330
331	Ok(result)
332}
333
334fn emit_create_table(ct: &CreateTableStatement) -> Result<String, Error> {
335	let table = if let Some(ref shape) = ct.shape {
336		format!("{shape}::{}", ct.table)
337	} else {
338		ct.table.clone()
339	};
340
341	let mut cols = Vec::new();
342	for col in &ct.columns {
343		let ty = emit_rql_type(&col.data_type);
344		if col.nullable {
345			cols.push(format!("{}: Option({})", col.name, ty));
346		} else {
347			cols.push(format!("{}: {}", col.name, ty));
348		}
349	}
350
351	let if_ne = if ct.if_not_exists {
352		" IF NOT EXISTS"
353	} else {
354		""
355	};
356	let mut result = format!("CREATE TABLE{if_ne} {} {{{}}}", table, cols.join(", "));
357
358	if !ct.primary_key.is_empty() {
359		result.push_str(&format!("; CREATE PRIMARY KEY ON {} {{{}}}", table, ct.primary_key.join(", ")));
360	}
361
362	Ok(result)
363}
364
365fn emit_create_index(ci: &CreateIndexStatement) -> Result<String, Error> {
366	let unique = if ci.unique {
367		"UNIQUE "
368	} else {
369		""
370	};
371	let table = if let Some(ref shape) = ci.shape {
372		format!("{shape}::{}", ci.table)
373	} else {
374		ci.table.clone()
375	};
376
377	let mut col_parts = Vec::new();
378	for col in &ci.columns {
379		match &col.direction {
380			Some(OrderDirection::Desc) => col_parts.push(format!("{}:desc", col.name)),
381			Some(OrderDirection::Asc) => col_parts.push(format!("{}:asc", col.name)),
382			None => col_parts.push(col.name.clone()),
383		}
384	}
385
386	Ok(format!("CREATE {unique}INDEX {} ON {table} {{{}}}", ci.index_name, col_parts.join(", ")))
387}
388
389fn emit_drop_table(dt: &DropTableStatement) -> Result<String, Error> {
390	let table = if let Some(ref shape) = dt.shape {
391		format!("{shape}::{}", dt.table)
392	} else {
393		dt.table.clone()
394	};
395
396	let if_exists = if dt.if_exists {
397		" IF EXISTS"
398	} else {
399		""
400	};
401	Ok(format!("DROP TABLE{if_exists} {table}"))
402}
403
404fn emit_rql_type(ty: &SqlType) -> &'static str {
405	match ty {
406		SqlType::Int | SqlType::Int4 | SqlType::Integer => "int4",
407		SqlType::Int2 | SqlType::Smallint => "int2",
408		SqlType::Int8 | SqlType::Bigint => "int8",
409		SqlType::Float4 | SqlType::Real => "float4",
410		SqlType::Float8 | SqlType::Double | SqlType::FloatType | SqlType::Numeric => "float8",
411		SqlType::Boolean | SqlType::Bool => "bool",
412		SqlType::Varchar(_) | SqlType::Char(_) | SqlType::Text | SqlType::Utf8 => "utf8",
413		SqlType::Blob => "blob",
414	}
415}
416
417fn emit_expr(expr: &Expr) -> Result<String, Error> {
418	match expr {
419		Expr::Identifier(name) => Ok(name.clone()),
420		Expr::QualifiedIdentifier(table, col) => Ok(format!("{table}.{col}")),
421		Expr::IntegerLiteral(n) => Ok(n.to_string()),
422		Expr::FloatLiteral(f) => Ok(format_float(*f)),
423		Expr::StringLiteral(s) => Ok(format!("'{s}'")),
424		Expr::BoolLiteral(b) => Ok(if *b {
425			"true"
426		} else {
427			"false"
428		}
429		.into()),
430		Expr::Null => Ok("none".into()),
431		Expr::BinaryOp {
432			left,
433			op,
434			right,
435		} => {
436			let l = emit_expr(left)?;
437			let r = emit_expr(right)?;
438			match op {
439				BinaryOp::Concat => Ok(format!("text::concat({l}, {r})")),
440				_ => {
441					let op_str = match op {
442						BinaryOp::Eq => "==",
443						BinaryOp::NotEq => "!=",
444						BinaryOp::Lt => "<",
445						BinaryOp::Gt => ">",
446						BinaryOp::LtEq => "<=",
447						BinaryOp::GtEq => ">=",
448						BinaryOp::And => "and",
449						BinaryOp::Or => "or",
450						BinaryOp::Add => "+",
451						BinaryOp::Sub => "-",
452						BinaryOp::Mul => "*",
453						BinaryOp::Div => "/",
454						BinaryOp::Mod => "%",
455						BinaryOp::Concat => unreachable!(),
456					};
457					Ok(format!("{l} {op_str} {r}"))
458				}
459			}
460		}
461		Expr::UnaryOp {
462			op,
463			expr,
464		} => {
465			let e = emit_expr(expr)?;
466			match op {
467				UnaryOp::Not => Ok(format!("not {e}")),
468				UnaryOp::Neg => Ok(format!("-{e}")),
469			}
470		}
471		Expr::FunctionCall {
472			name,
473			args,
474		} => {
475			let func_name = sql_to_rql_function(name)?;
476			let arg_strs: Result<Vec<_>, _> = args.iter().map(emit_expr).collect();
477			let args_str = arg_strs?.join(", ");
478			Ok(format!("{func_name}({args_str})"))
479		}
480		Expr::Between {
481			expr,
482			low,
483			high,
484			negated,
485		} => {
486			let e = emit_expr(expr)?;
487			let l = emit_expr(low)?;
488			let h = emit_expr(high)?;
489			if *negated {
490				Ok(format!("not ({e} between {l} and {h})"))
491			} else {
492				Ok(format!("{e} between {l} and {h}"))
493			}
494		}
495		Expr::InList {
496			expr,
497			list,
498			negated,
499		} => {
500			let e = emit_expr(expr)?;
501			let items: Result<Vec<_>, _> = list.iter().map(emit_expr).collect();
502			let items_str = items?.join(", ");
503			if *negated {
504				Ok(format!("not ({e} in ({items_str}))"))
505			} else {
506				Ok(format!("{e} in ({items_str})"))
507			}
508		}
509		Expr::InSelect {
510			expr,
511			subquery,
512			negated,
513		} => {
514			let e = emit_expr(expr)?;
515			let sub = emit_select(subquery)?;
516			if *negated {
517				Ok(format!("not ({e} in ({{{sub}}}))"))
518			} else {
519				Ok(format!("{e} in ({{{sub}}})"))
520			}
521		}
522		Expr::IsNull {
523			expr,
524			negated,
525		} => {
526			let e = emit_expr(expr)?;
527			if *negated {
528				Ok(format!("{e} != none"))
529			} else {
530				Ok(format!("{e} == none"))
531			}
532		}
533		Expr::Cast {
534			expr,
535			data_type,
536		} => {
537			let e = emit_expr(expr)?;
538			let ty = emit_rql_type(data_type);
539			Ok(format!("cast({e}, {ty})"))
540		}
541		Expr::Nested(inner) => {
542			let e = emit_expr(inner)?;
543			Ok(format!("({e})"))
544		}
545		Expr::Case {
546			operand,
547			when_clauses,
548			else_clause,
549		} => emit_case(operand, when_clauses, else_clause),
550		Expr::Exists(sel) => {
551			let inner = emit_select(sel)?;
552			Ok(format!("exists({{{inner}}})"))
553		}
554		Expr::Subquery(sel) => {
555			let inner = emit_select(sel)?;
556			Ok(format!("{{{inner}}}"))
557		}
558		Expr::Like {
559			expr,
560			pattern,
561			negated,
562		} => {
563			let e = emit_expr(expr)?;
564			let p = emit_expr(pattern)?;
565			if *negated {
566				Ok(format!("not ({e} like {p})"))
567			} else {
568				Ok(format!("{e} like {p}"))
569			}
570		}
571	}
572}
573
574fn emit_case(
575	operand: &Option<Box<Expr>>,
576	when_clauses: &[(Expr, Expr)],
577	else_clause: &Option<Box<Expr>>,
578) -> Result<String, Error> {
579	let mut parts = Vec::new();
580
581	for (i, (condition, result)) in when_clauses.iter().enumerate() {
582		let cond_str = if let Some(op) = operand {
583			let op_str = emit_expr(op)?;
584			let val_str = emit_expr(condition)?;
585			format!("{op_str} == {val_str}")
586		} else {
587			emit_expr(condition)?
588		};
589		let result_str = emit_expr(result)?;
590
591		if i == 0 {
592			parts.push(format!("if {cond_str} {{ {result_str} }}"));
593		} else {
594			parts.push(format!("else if {cond_str} {{ {result_str} }}"));
595		}
596	}
597
598	if let Some(else_expr) = else_clause {
599		let else_str = emit_expr(else_expr)?;
600		parts.push(format!("else {{ {else_str} }}"));
601	}
602
603	Ok(parts.join(" "))
604}
605
606fn format_float(f: f64) -> String {
607	let s = f.to_string();
608	if s.contains('.') {
609		s
610	} else {
611		format!("{s}.0")
612	}
613}
614
615fn is_all_columns(cols: &[SelectColumn]) -> bool {
616	cols.len() == 1 && matches!(cols[0], SelectColumn::AllColumns)
617}
618
619fn has_aggregate_functions(cols: &[SelectColumn]) -> bool {
620	cols.iter().any(|c| match c {
621		SelectColumn::Expr {
622			expr,
623			..
624		} => expr_has_aggregate(expr),
625		_ => false,
626	})
627}
628
629fn expr_has_aggregate(expr: &Expr) -> bool {
630	match expr {
631		Expr::FunctionCall {
632			name,
633			..
634		} => {
635			let upper = name.to_uppercase();
636
637			let base = upper.strip_suffix("_DISTINCT").unwrap_or(&upper);
638			matches!(
639				sql_to_rql_function(base),
640				Ok("math::count" | "math::sum" | "math::avg" | "math::min" | "math::max")
641			)
642		}
643		Expr::BinaryOp {
644			left,
645			right,
646			..
647		} => expr_has_aggregate(left) || expr_has_aggregate(right),
648		Expr::UnaryOp {
649			expr,
650			..
651		} => expr_has_aggregate(expr),
652		Expr::Nested(inner) => expr_has_aggregate(inner),
653		Expr::Case {
654			when_clauses,
655			else_clause,
656			..
657		} => {
658			when_clauses.iter().any(|(c, r)| expr_has_aggregate(c) || expr_has_aggregate(r))
659				|| else_clause.as_ref().is_some_and(|e| expr_has_aggregate(e))
660		}
661		_ => false,
662	}
663}
664
665fn collect_aggregate_columns(cols: &[SelectColumn]) -> Result<String, Error> {
666	let mut agg_exprs = Vec::new();
667	for col in cols {
668		if let SelectColumn::Expr {
669			expr,
670			alias,
671		} = col && expr_has_aggregate(expr)
672		{
673			let e = emit_expr(expr)?;
674			if let Some(alias) = alias {
675				agg_exprs.push(format!("{alias}: {e}"));
676			} else {
677				agg_exprs.push(e);
678			}
679		}
680	}
681	Ok(agg_exprs.join(", "))
682}
683
684fn collect_non_aggregate_map_columns(sel: &SelectStatement) -> Result<String, Error> {
685	let mut map_exprs = Vec::new();
686	for col in &sel.columns {
687		if let SelectColumn::Expr {
688			expr,
689			alias,
690		} = col && !expr_has_aggregate(expr)
691		{
692			let is_in_group_by = sel.group_by.iter().any(|gb| expr_eq(gb, expr));
693			if !is_in_group_by {
694				let e = emit_expr(expr)?;
695				if let Some(alias) = alias {
696					map_exprs.push(format!("{alias}: {e}"));
697				} else {
698					map_exprs.push(e);
699				}
700			}
701		}
702	}
703	Ok(map_exprs.join(", "))
704}
705
706fn emit_select_columns(cols: &[SelectColumn]) -> Result<String, Error> {
707	let mut parts = Vec::new();
708	for col in cols {
709		match col {
710			SelectColumn::AllColumns => parts.push("*".into()),
711			SelectColumn::Expr {
712				expr,
713				alias,
714			} => {
715				let e = emit_expr(expr)?;
716				if let Some(alias) = alias {
717					parts.push(format!("{alias}: {e}"));
718				} else {
719					parts.push(e);
720				}
721			}
722		}
723	}
724	Ok(parts.join(", "))
725}
726
727fn emit_select_columns_plain(cols: &[SelectColumn]) -> Result<String, Error> {
728	let mut parts = Vec::new();
729	for col in cols {
730		match col {
731			SelectColumn::AllColumns => parts.push("*".into()),
732			SelectColumn::Expr {
733				expr,
734				..
735			} => {
736				parts.push(emit_expr(expr)?);
737			}
738		}
739	}
740	Ok(parts.join(", "))
741}
742
743fn sql_to_rql_function(name: &str) -> Result<&'static str, Error> {
744	match name.to_uppercase().as_str() {
745		"COUNT" | "COUNT_DISTINCT" => Ok("math::count"),
746		"SUM" | "SUM_DISTINCT" => Ok("math::sum"),
747		"AVG" | "AVG_DISTINCT" => Ok("math::avg"),
748		"MIN" | "MIN_DISTINCT" => Ok("math::min"),
749		"MAX" | "MAX_DISTINCT" => Ok("math::max"),
750		"TOTAL" => Ok("math::sum"),
751
752		"ABS" => Ok("math::abs"),
753		"ACOS" => Ok("math::acos"),
754		"ASIN" => Ok("math::asin"),
755		"ATAN" => Ok("math::atan"),
756		"ATAN2" => Ok("math::atan2"),
757		"CEIL" | "CEILING" => Ok("math::ceil"),
758		"COS" => Ok("math::cos"),
759		"EXP" => Ok("math::exp"),
760		"FLOOR" => Ok("math::floor"),
761		"GCD" => Ok("math::gcd"),
762		"LCM" => Ok("math::lcm"),
763		"LOG" => Ok("math::log"),
764		"LOG10" => Ok("math::log10"),
765		"LOG2" => Ok("math::log2"),
766		"MOD" => Ok("math::mod"),
767		"PI" => Ok("math::pi"),
768		"POWER" | "POW" => Ok("math::power"),
769		"ROUND" => Ok("math::round"),
770		"SIGN" => Ok("math::sign"),
771		"SIN" => Ok("math::sin"),
772		"SQRT" => Ok("math::sqrt"),
773		"TAN" => Ok("math::tan"),
774		"TRUNCATE" | "TRUNC" => Ok("math::truncate"),
775		"RANDOM" => Ok("math::random"),
776
777		"ASCII" => Ok("text::ascii"),
778		"CHAR" | "CHR" => Ok("text::char"),
779		"CONCAT" => Ok("text::concat"),
780		"LENGTH" | "LEN" | "CHAR_LENGTH" | "CHARACTER_LENGTH" => Ok("text::length"),
781		"LOWER" | "LCASE" => Ok("text::lower"),
782		"LPAD" => Ok("text::pad_left"),
783		"REPEAT" => Ok("text::repeat"),
784		"REPLACE" => Ok("text::replace"),
785		"REVERSE" => Ok("text::reverse"),
786		"RPAD" => Ok("text::pad_right"),
787		"SUBSTRING" | "SUBSTR" => Ok("text::substring"),
788		"TRIM" => Ok("text::trim"),
789		"LTRIM" => Ok("text::trim_start"),
790		"RTRIM" => Ok("text::trim_end"),
791		"UPPER" | "UCASE" => Ok("text::upper"),
792		"TYPEOF" => Ok("type::of"),
793		"UNICODE" => Ok("text::unicode"),
794		"INSTR" => Ok("text::instr"),
795		"HEX" => Ok("text::hex"),
796		"QUOTE" => Ok("text::quote"),
797		"ZEROBLOB" => Ok("blob::zeroblob"),
798		"GROUP_CONCAT" => Ok("text::group_concat"),
799
800		"COALESCE" => Ok("coalesce"),
801		"NULLIF" => Ok("nullif"),
802		"IIF" => Ok("iif"),
803		"IFNULL" => Ok("ifnull"),
804		"PRINTF" => Ok("text::printf"),
805		_ => Err(Error(format!("no SQL-to-RQL mapping for function: {name}"))),
806	}
807}
808
809fn emit_order_by(items: &[OrderByItem]) -> Result<String, Error> {
810	let mut parts = Vec::new();
811	for item in items {
812		let e = emit_expr(&item.expr)?;
813		match item.direction {
814			OrderDirection::Asc => parts.push(format!("{e}:asc")),
815			OrderDirection::Desc => parts.push(format!("{e}:desc")),
816		};
817	}
818	Ok(parts.join(", "))
819}
820
821fn emit_expr_comma_list(exprs: &[Expr]) -> Result<String, Error> {
822	let parts: Result<Vec<_>, _> = exprs.iter().map(emit_expr).collect();
823	Ok(parts?.join(", "))
824}
825
826fn expr_eq(a: &Expr, b: &Expr) -> bool {
827	match (a, b) {
828		(Expr::Identifier(a), Expr::Identifier(b)) => a == b,
829		(Expr::QualifiedIdentifier(at, ac), Expr::QualifiedIdentifier(bt, bc)) => at == bt && ac == bc,
830		_ => false,
831	}
832}
833
834#[cfg(test)]
835mod tests {
836	use super::*;
837	use crate::{parser::Parser, token::tokenize};
838
839	fn transpile(sql: &str) -> String {
840		let tokens = tokenize(sql).unwrap();
841		let stmt = Parser::new(tokens).parse().unwrap();
842		emit(&stmt).unwrap()
843	}
844
845	#[test]
846	fn test_select_star() {
847		assert_eq!(transpile("SELECT * FROM users"), "FROM users");
848	}
849
850	#[test]
851	fn test_select_columns() {
852		assert_eq!(transpile("SELECT id, name FROM users"), "FROM users MAP {id, name}");
853	}
854
855	#[test]
856	fn test_select_alias() {
857		assert_eq!(transpile("SELECT id, name AS username FROM users"), "FROM users MAP {id, username: name}");
858	}
859
860	#[test]
861	fn test_where() {
862		assert_eq!(transpile("SELECT * FROM users WHERE age > 18"), "FROM users FILTER {age > 18}");
863	}
864
865	#[test]
866	fn test_order_by() {
867		assert_eq!(transpile("SELECT * FROM users ORDER BY name ASC"), "FROM users SORT {name:asc}");
868	}
869
870	#[test]
871	fn test_limit() {
872		assert_eq!(transpile("SELECT * FROM users LIMIT 10"), "FROM users TAKE 10");
873	}
874
875	#[test]
876	fn test_insert() {
877		assert_eq!(
878			transpile("INSERT INTO users (id, name) VALUES (1, 'Alice')"),
879			"INSERT users [{id: 1, name: 'Alice'}]"
880		);
881	}
882
883	#[test]
884	fn test_update() {
885		assert_eq!(
886			transpile("UPDATE users SET name = 'Bob' WHERE id = 1"),
887			"UPDATE users {name: 'Bob'} FILTER {id == 1}"
888		);
889	}
890
891	#[test]
892	fn test_delete() {
893		assert_eq!(transpile("DELETE FROM users WHERE id = 1"), "DELETE users FILTER {id == 1}");
894	}
895
896	#[test]
897	fn test_create_table() {
898		assert_eq!(
899			transpile("CREATE TABLE users (id INT, name TEXT, active BOOLEAN)"),
900			"CREATE TABLE users {id: Option(int4), name: Option(utf8), active: Option(bool)}"
901		);
902	}
903
904	#[test]
905	fn test_create_table_not_null() {
906		assert_eq!(
907			transpile("CREATE TABLE t (id INT NOT NULL, name TEXT)"),
908			"CREATE TABLE t {id: int4, name: Option(utf8)}"
909		);
910	}
911
912	#[test]
913	fn test_null_to_none() {
914		assert_eq!(transpile("SELECT NULL"), "MAP {none}");
915	}
916
917	#[test]
918	fn test_not_equal() {
919		assert_eq!(transpile("SELECT * FROM t WHERE a <> b"), "FROM t FILTER {a != b}");
920	}
921
922	#[test]
923	fn test_and_or() {
924		assert_eq!(transpile("SELECT * FROM t WHERE a = 1 AND b = 2"), "FROM t FILTER {a == 1 and b == 2}");
925	}
926
927	#[test]
928	fn test_distinct() {
929		assert_eq!(transpile("SELECT DISTINCT name FROM users"), "FROM users DISTINCT {name}");
930	}
931
932	#[test]
933	fn test_group_by_aggregate() {
934		assert_eq!(
935			transpile("SELECT COUNT(id) FROM users GROUP BY dept"),
936			"FROM users AGGREGATE {math::count(id)} BY {dept}"
937		);
938	}
939
940	#[test]
941	fn test_cast() {
942		assert_eq!(transpile("SELECT CAST(x AS INT)"), "MAP {cast(x, int4)}");
943	}
944
945	#[test]
946	fn test_between() {
947		assert_eq!(transpile("SELECT * FROM t WHERE x BETWEEN 1 AND 10"), "FROM t FILTER {x between 1 and 10}");
948	}
949
950	#[test]
951	fn test_in_list() {
952		assert_eq!(transpile("SELECT * FROM t WHERE x IN (1, 2, 3)"), "FROM t FILTER {x in (1, 2, 3)}");
953	}
954
955	#[test]
956	fn test_is_null() {
957		assert_eq!(transpile("SELECT * FROM t WHERE x IS NULL"), "FROM t FILTER {x == none}");
958	}
959
960	#[test]
961	fn test_is_not_null() {
962		assert_eq!(transpile("SELECT * FROM t WHERE x IS NOT NULL"), "FROM t FILTER {x != none}");
963	}
964
965	#[test]
966	fn test_join() {
967		assert_eq!(
968			transpile("SELECT * FROM t1 INNER JOIN t2 ON t1.a = t2.b"),
969			"FROM t1 JOIN {FROM t2} AS t2 USING (a, t2.b)"
970		);
971	}
972
973	#[test]
974	fn test_left_join() {
975		assert_eq!(
976			transpile("SELECT * FROM t1 LEFT JOIN t2 ON t1.a = t2.b"),
977			"FROM t1 LEFT JOIN {FROM t2} AS t2 USING (a, t2.b)"
978		);
979	}
980
981	#[test]
982	fn test_select_no_from() {
983		assert_eq!(transpile("SELECT 1 + 2"), "MAP {1 + 2}");
984	}
985
986	#[test]
987	fn test_multiple_insert_rows() {
988		assert_eq!(
989			transpile("INSERT INTO t (a, b) VALUES (1, 2), (3, 4)"),
990			"INSERT t [{a: 1, b: 2}, {a: 3, b: 4}]"
991		);
992	}
993
994	#[test]
995	fn test_shape_qualified_table() {
996		assert_eq!(transpile("SELECT * FROM test.users"), "FROM test::users");
997	}
998
999	#[test]
1000	fn test_simple_cte() {
1001		assert_eq!(
1002			transpile("WITH t AS (SELECT * FROM users WHERE active = true) SELECT * FROM t"),
1003			"LET $t = FROM users FILTER {active == true}; FROM $t"
1004		);
1005	}
1006
1007	#[test]
1008	fn test_insert_without_columns() {
1009		assert_eq!(transpile("INSERT INTO t1 VALUES (1, 'true')"), "INSERT t1 [(1, 'true')]");
1010	}
1011
1012	#[test]
1013	fn test_create_table_primary_key() {
1014		assert_eq!(
1015			transpile("CREATE TABLE t (v1 INT NOT NULL, v2 INT NOT NULL, PRIMARY KEY(v1))"),
1016			"CREATE TABLE t {v1: int4, v2: int4}; CREATE PRIMARY KEY ON t {v1}"
1017		);
1018		assert_eq!(
1019			transpile("CREATE TABLE t (a INT NOT NULL, b INT NOT NULL, PRIMARY KEY(a, b))"),
1020			"CREATE TABLE t {a: int4, b: int4}; CREATE PRIMARY KEY ON t {a, b}"
1021		);
1022	}
1023
1024	#[test]
1025	fn test_multiple_ctes() {
1026		assert_eq!(
1027			transpile("WITH a AS (SELECT * FROM users), b AS (SELECT id FROM a) SELECT * FROM b"),
1028			"LET $a = FROM users; LET $b = FROM $a MAP {id}; FROM $b"
1029		);
1030	}
1031
1032	// CASE expressions
1033	#[test]
1034	fn test_case_when_single() {
1035		assert_eq!(
1036			transpile("SELECT CASE WHEN x > 0 THEN 'pos' END FROM t"),
1037			"FROM t MAP {if x > 0 { 'pos' }}"
1038		);
1039	}
1040
1041	#[test]
1042	fn test_case_when_multiple() {
1043		assert_eq!(
1044			transpile("SELECT CASE WHEN x > 0 THEN 'pos' WHEN x < 0 THEN 'neg' END FROM t"),
1045			"FROM t MAP {if x > 0 { 'pos' } else if x < 0 { 'neg' }}"
1046		);
1047	}
1048
1049	#[test]
1050	fn test_case_when_else() {
1051		assert_eq!(
1052			transpile("SELECT CASE WHEN x > 0 THEN 'pos' ELSE 'non-pos' END FROM t"),
1053			"FROM t MAP {if x > 0 { 'pos' } else { 'non-pos' }}"
1054		);
1055	}
1056
1057	#[test]
1058	fn test_case_simple() {
1059		assert_eq!(
1060			transpile("SELECT CASE x WHEN 1 THEN 'one' WHEN 2 THEN 'two' ELSE 'other' END FROM t"),
1061			"FROM t MAP {if x == 1 { 'one' } else if x == 2 { 'two' } else { 'other' }}"
1062		);
1063	}
1064
1065	#[test]
1066	fn test_case_in_where() {
1067		assert_eq!(
1068			transpile("SELECT * FROM t WHERE CASE WHEN a > 10 THEN 1 ELSE 0 END = 1"),
1069			"FROM t FILTER {if a > 10 { 1 } else { 0 } == 1}"
1070		);
1071	}
1072
1073	#[test]
1074	fn test_case_nested() {
1075		assert_eq!(
1076			transpile(
1077				"SELECT CASE WHEN a > 0 THEN CASE WHEN b > 0 THEN 'pp' ELSE 'pn' END ELSE 'neg' END FROM t"
1078			),
1079			"FROM t MAP {if a > 0 { if b > 0 { 'pp' } else { 'pn' } } else { 'neg' }}"
1080		);
1081	}
1082
1083	#[test]
1084	fn test_case_in_select_projection() {
1085		assert_eq!(
1086			transpile("SELECT id, CASE WHEN active = true THEN 'yes' ELSE 'no' END AS status FROM users"),
1087			"FROM users MAP {id, status: if active == true { 'yes' } else { 'no' }}"
1088		);
1089	}
1090
1091	#[test]
1092	fn test_case_with_aggregate() {
1093		assert_eq!(
1094			transpile("SELECT SUM(CASE WHEN x > 0 THEN 1 ELSE 0 END) FROM t"),
1095			"FROM t AGGREGATE {math::sum(if x > 0 { 1 } else { 0 })}"
1096		);
1097	}
1098
1099	// EXISTS / Subqueries
1100	#[test]
1101	fn test_exists_in_where() {
1102		assert_eq!(
1103			transpile("SELECT * FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.a = t1.a)"),
1104			"FROM t1 FILTER {exists({FROM t2 FILTER {t2.a == t1.a} MAP {1}})}"
1105		);
1106	}
1107
1108	#[test]
1109	fn test_not_exists_in_where() {
1110		assert_eq!(
1111			transpile("SELECT * FROM t1 WHERE NOT EXISTS (SELECT 1 FROM t2 WHERE t2.a = t1.a)"),
1112			"FROM t1 FILTER {not exists({FROM t2 FILTER {t2.a == t1.a} MAP {1}})}"
1113		);
1114	}
1115
1116	#[test]
1117	fn test_scalar_subquery_in_select() {
1118		assert_eq!(
1119			transpile("SELECT (SELECT COUNT(*) FROM t2) FROM t1"),
1120			"FROM t1 MAP {{FROM t2 AGGREGATE {math::count(*)}}}"
1121		);
1122	}
1123
1124	#[test]
1125	fn test_scalar_subquery_in_where() {
1126		assert_eq!(
1127			transpile("SELECT * FROM t1 WHERE a > (SELECT MIN(b) FROM t2)"),
1128			"FROM t1 FILTER {a > {FROM t2 AGGREGATE {math::min(b)}}}"
1129		);
1130	}
1131
1132	#[test]
1133	fn test_in_subquery() {
1134		assert_eq!(
1135			transpile("SELECT * FROM t1 WHERE a IN (SELECT b FROM t2)"),
1136			"FROM t1 FILTER {a in ({FROM t2 MAP {b}})}"
1137		);
1138	}
1139
1140	#[test]
1141	fn test_not_in_subquery() {
1142		assert_eq!(
1143			transpile("SELECT * FROM t1 WHERE a NOT IN (SELECT b FROM t2)"),
1144			"FROM t1 FILTER {not (a in ({FROM t2 MAP {b}}))}"
1145		);
1146	}
1147
1148	// CREATE INDEX
1149	#[test]
1150	fn test_create_index_basic() {
1151		assert_eq!(transpile("CREATE INDEX idx1 ON t1 (a)"), "CREATE INDEX idx1 ON t1 {a}");
1152	}
1153
1154	#[test]
1155	fn test_create_unique_index() {
1156		assert_eq!(transpile("CREATE UNIQUE INDEX idx1 ON t1 (a)"), "CREATE UNIQUE INDEX idx1 ON t1 {a}");
1157	}
1158
1159	#[test]
1160	fn test_create_composite_index() {
1161		assert_eq!(transpile("CREATE INDEX idx1 ON t1 (a, b, c)"), "CREATE INDEX idx1 ON t1 {a, b, c}");
1162	}
1163
1164	#[test]
1165	fn test_create_index_with_direction() {
1166		assert_eq!(
1167			transpile("CREATE INDEX idx1 ON t1 (a DESC, b ASC)"),
1168			"CREATE INDEX idx1 ON t1 {a:desc, b:asc}"
1169		);
1170	}
1171
1172	// DROP TABLE
1173	#[test]
1174	fn test_drop_table() {
1175		assert_eq!(transpile("DROP TABLE t1"), "DROP TABLE t1");
1176	}
1177
1178	#[test]
1179	fn test_drop_table_if_exists() {
1180		assert_eq!(transpile("DROP TABLE IF EXISTS t1"), "DROP TABLE IF EXISTS t1");
1181	}
1182
1183	// INSERT...SELECT
1184	#[test]
1185	fn test_insert_select() {
1186		assert_eq!(transpile("INSERT INTO t1 SELECT * FROM t2"), "INSERT t1 {FROM t2}");
1187	}
1188
1189	#[test]
1190	fn test_insert_select_with_columns() {
1191		assert_eq!(
1192			transpile("INSERT INTO t1 (a, b) SELECT x, y FROM t2"),
1193			"INSERT t1 (a, b) {FROM t2 MAP {x, y}}"
1194		);
1195	}
1196
1197	// LIKE
1198	#[test]
1199	fn test_like_basic() {
1200		assert_eq!(transpile("SELECT * FROM t WHERE name LIKE '%foo%'"), "FROM t FILTER {name like '%foo%'}");
1201	}
1202
1203	#[test]
1204	fn test_not_like() {
1205		assert_eq!(
1206			transpile("SELECT * FROM t WHERE name NOT LIKE '%foo%'"),
1207			"FROM t FILTER {not (name like '%foo%')}"
1208		);
1209	}
1210
1211	#[test]
1212	fn test_like_with_special() {
1213		assert_eq!(transpile("SELECT * FROM t WHERE name LIKE 'a_b%'"), "FROM t FILTER {name like 'a_b%'}");
1214	}
1215
1216	// Column-level PRIMARY KEY
1217	#[test]
1218	fn test_column_primary_key() {
1219		assert_eq!(
1220			transpile("CREATE TABLE t (id INT PRIMARY KEY, name TEXT)"),
1221			"CREATE TABLE t {id: int4, name: Option(utf8)}; CREATE PRIMARY KEY ON t {id}"
1222		);
1223	}
1224
1225	#[test]
1226	fn test_mixed_primary_key() {
1227		assert_eq!(
1228			transpile("CREATE TABLE t (id INT PRIMARY KEY, val INT NOT NULL, PRIMARY KEY(id))"),
1229			"CREATE TABLE t {id: int4, val: int4}; CREATE PRIMARY KEY ON t {id}"
1230		);
1231	}
1232
1233	// Table aliases
1234	#[test]
1235	fn test_from_table_as_alias() {
1236		assert_eq!(transpile("SELECT a.id FROM users AS a"), "FROM users AS a MAP {a.id}");
1237	}
1238
1239	#[test]
1240	fn test_from_table_bare_alias() {
1241		assert_eq!(transpile("SELECT a.id FROM users a"), "FROM users AS a MAP {a.id}");
1242	}
1243
1244	#[test]
1245	fn test_self_join_with_aliases() {
1246		assert_eq!(
1247			transpile("SELECT a.id, b.id FROM t1 AS a INNER JOIN t1 AS b ON a.x = b.y"),
1248			"FROM t1 AS a JOIN {FROM t1} AS b USING (x, b.y) MAP {a.id, b.id}"
1249		);
1250	}
1251
1252	// Multi-table FROM (cross join)
1253	#[test]
1254	fn test_two_table_from() {
1255		assert_eq!(transpile("SELECT * FROM t1, t2"), "FROM t1 CROSS JOIN {FROM t2} AS t2");
1256	}
1257
1258	#[test]
1259	fn test_three_table_from() {
1260		assert_eq!(
1261			transpile("SELECT * FROM t1, t2, t3"),
1262			"FROM t1 CROSS JOIN {FROM t2} AS t2 CROSS JOIN {FROM t3} AS t3"
1263		);
1264	}
1265
1266	// UNION / INTERSECT / EXCEPT
1267	#[test]
1268	fn test_union_all() {
1269		assert_eq!(
1270			transpile("SELECT a FROM t1 UNION ALL SELECT a FROM t2"),
1271			"UNION ALL {FROM t1 MAP {a}} {FROM t2 MAP {a}}"
1272		);
1273	}
1274
1275	#[test]
1276	fn test_union() {
1277		assert_eq!(
1278			transpile("SELECT a FROM t1 UNION SELECT a FROM t2"),
1279			"UNION {FROM t1 MAP {a}} {FROM t2 MAP {a}}"
1280		);
1281	}
1282
1283	#[test]
1284	fn test_intersect() {
1285		assert_eq!(
1286			transpile("SELECT a FROM t1 INTERSECT SELECT a FROM t2"),
1287			"INTERSECT {FROM t1 MAP {a}} {FROM t2 MAP {a}}"
1288		);
1289	}
1290
1291	#[test]
1292	fn test_except() {
1293		assert_eq!(
1294			transpile("SELECT a FROM t1 EXCEPT SELECT a FROM t2"),
1295			"EXCEPT {FROM t1 MAP {a}} {FROM t2 MAP {a}}"
1296		);
1297	}
1298
1299	// String concatenation
1300	#[test]
1301	fn test_concat_operator() {
1302		assert_eq!(transpile("SELECT a || b FROM t"), "FROM t MAP {text::concat(a, b)}");
1303	}
1304
1305	#[test]
1306	fn test_concat_chain() {
1307		assert_eq!(transpile("SELECT a || b || c FROM t"), "FROM t MAP {text::concat(text::concat(a, b), c)}");
1308	}
1309
1310	// COALESCE / NULLIF
1311	#[test]
1312	fn test_coalesce() {
1313		assert_eq!(transpile("SELECT COALESCE(a, b, c) FROM t"), "FROM t MAP {coalesce(a, b, c)}");
1314	}
1315
1316	#[test]
1317	fn test_nullif() {
1318		assert_eq!(transpile("SELECT NULLIF(a, 0) FROM t"), "FROM t MAP {nullif(a, 0)}");
1319	}
1320
1321	// FLOAT type
1322	#[test]
1323	fn test_float_type() {
1324		assert_eq!(transpile("CREATE TABLE t (x FLOAT NOT NULL)"), "CREATE TABLE t {x: float8}");
1325	}
1326
1327	// NUMERIC type
1328	#[test]
1329	fn test_numeric_type() {
1330		assert_eq!(transpile("CREATE TABLE t (x NUMERIC NOT NULL)"), "CREATE TABLE t {x: float8}");
1331	}
1332
1333	// ORDER BY ordinal
1334	#[test]
1335	fn test_order_by_ordinal() {
1336		assert_eq!(transpile("SELECT a, b FROM t ORDER BY 1"), "FROM t MAP {a, b} SORT {1:asc}");
1337	}
1338
1339	#[test]
1340	fn test_order_by_ordinal_desc() {
1341		assert_eq!(
1342			transpile("SELECT a, b FROM t ORDER BY 1, 2 DESC"),
1343			"FROM t MAP {a, b} SORT {1:asc, 2:desc}"
1344		);
1345	}
1346
1347	// IF NOT EXISTS
1348	#[test]
1349	fn test_create_table_if_not_exists() {
1350		assert_eq!(
1351			transpile("CREATE TABLE IF NOT EXISTS t (id INT NOT NULL)"),
1352			"CREATE TABLE IF NOT EXISTS t {id: int4}"
1353		);
1354	}
1355
1356	// Unary plus
1357	#[test]
1358	fn test_unary_plus() {
1359		assert_eq!(transpile("SELECT +1"), "MAP {1}");
1360	}
1361}