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 {
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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[test]
1358 fn test_unary_plus() {
1359 assert_eq!(transpile("SELECT +1"), "MAP {1}");
1360 }
1361}