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