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