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 }
16}
17
18fn 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 return emit_select_no_from(sel);
46 }
47
48 let mut parts = Vec::new();
49
50 parts.push(emit_from_clause(sel.from.as_ref().unwrap(), cte_names)?);
52
53 for join in &sel.joins {
55 parts.push(emit_join(join, cte_names)?);
56 }
57
58 if let Some(ref where_clause) = sel.where_clause {
60 parts.push(format!("FILTER {{{}}}", emit_expr(where_clause)?));
61 }
62
63 let has_aggregates = has_aggregate_functions(&sel.columns);
65 if has_aggregates || !sel.group_by.is_empty() {
66 let agg_exprs = collect_aggregate_columns(&sel.columns)?;
67 let by_exprs = emit_expr_comma_list(&sel.group_by)?;
68
69 if !agg_exprs.is_empty() {
70 let mut agg_str = format!("AGGREGATE {{{agg_exprs}}}");
71 if !sel.group_by.is_empty() {
72 agg_str.push_str(&format!(" BY {{{by_exprs}}}"));
73 }
74 parts.push(agg_str);
75 }
76
77 if let Some(ref having) = sel.having {
79 parts.push(format!("FILTER {{{}}}", emit_expr(having)?));
80 }
81
82 let map_exprs = collect_non_aggregate_map_columns(sel)?;
84 if !map_exprs.is_empty() {
85 parts.push(format!("MAP {{{map_exprs}}}"));
86 }
87 } else {
88 if sel.distinct {
90 if is_all_columns(&sel.columns) {
91 parts.push("DISTINCT {}".into());
92 } else {
93 let cols = emit_select_columns_plain(&sel.columns)?;
94 parts.push(format!("DISTINCT {{{cols}}}"));
95 }
96 }
97 else if !is_all_columns(&sel.columns) {
99 let cols = emit_select_columns(&sel.columns)?;
100 parts.push(format!("MAP {{{cols}}}"));
101 }
102 }
103
104 if !sel.order_by.is_empty() {
106 let sort = emit_order_by(&sel.order_by)?;
107 parts.push(format!("SORT {{{sort}}}"));
108 }
109
110 if let Some(limit) = sel.limit {
112 parts.push(format!("TAKE {limit}"));
113 }
114
115 if let Some(offset) = sel.offset {
117 parts.push(format!("OFFSET {offset}"));
118 }
119
120 Ok(parts.join(" "))
121}
122
123fn emit_select_no_from(sel: &SelectStatement) -> Result<String, Error> {
124 let cols = emit_select_columns(&sel.columns)?;
125 Ok(format!("MAP {{{cols}}}"))
126}
127
128fn emit_from_clause(from: &FromClause, cte_names: &HashSet<String>) -> Result<String, Error> {
129 match from {
130 FromClause::Table {
131 name,
132 schema,
133 } => {
134 if schema.is_none() && cte_names.contains(&name.to_ascii_lowercase()) {
135 return Ok(format!("FROM ${}", name.to_ascii_lowercase()));
136 }
137 if let Some(schema) = schema {
138 Ok(format!("FROM {schema}.{name}"))
139 } else {
140 Ok(format!("FROM {name}"))
141 }
142 }
143 FromClause::Subquery(sel) => {
144 let inner = emit_select(sel)?;
145 Ok(format!("FROM {{{inner}}}"))
146 }
147 }
148}
149
150fn emit_join(join: &JoinClause, cte_names: &HashSet<String>) -> Result<String, Error> {
151 let join_kw = match join.join_type {
152 JoinType::Inner => "JOIN",
153 JoinType::Left => "LEFT JOIN",
154 };
155
156 let table_name = match &join.table {
157 FromClause::Table {
158 name,
159 schema,
160 } => {
161 if schema.is_none() && cte_names.contains(&name.to_ascii_lowercase()) {
162 format!("${}", name.to_ascii_lowercase())
163 } else if let Some(schema) = schema {
164 format!("{schema}.{name}")
165 } else {
166 name.clone()
167 }
168 }
169 FromClause::Subquery(sel) => emit_select(sel)?,
170 };
171
172 let alias = join
173 .table_alias
174 .as_deref()
175 .or(match &join.table {
176 FromClause::Table {
177 name,
178 ..
179 } => Some(name.as_str()),
180 _ => None,
181 })
182 .unwrap_or("_");
183
184 let using = emit_join_using(&join.on, alias)?;
186
187 Ok(format!("{join_kw} {{FROM {table_name}}} AS {alias} USING ({using})"))
188}
189
190fn emit_join_using(on_expr: &Expr, right_alias: &str) -> Result<String, Error> {
192 match on_expr {
193 Expr::BinaryOp {
194 left,
195 op: BinaryOp::Eq,
196 right,
197 } => {
198 let (left_col, right_col) = extract_join_columns(left, right, right_alias)?;
199 Ok(format!("{left_col}, {right_col}"))
200 }
201 Expr::BinaryOp {
202 left,
203 op: BinaryOp::And,
204 right,
205 } => {
206 let l = emit_join_using(left, right_alias)?;
207 let r = emit_join_using(right, right_alias)?;
208 Ok(format!("{l}, {r}"))
209 }
210 _ => {
211 emit_expr(on_expr)
213 }
214 }
215}
216
217fn extract_join_columns(left: &Expr, right: &Expr, right_alias: &str) -> Result<(String, String), Error> {
218 let left_col = match left {
219 Expr::QualifiedIdentifier(_table, col) => col.clone(),
220 Expr::Identifier(col) => col.clone(),
221 _ => emit_expr(left)?,
222 };
223 let right_col = match right {
224 Expr::QualifiedIdentifier(table, col) => format!("{table}.{col}"),
225 Expr::Identifier(col) => format!("{right_alias}.{col}"),
226 _ => emit_expr(right)?,
227 };
228 Ok((left_col, right_col))
229}
230
231fn emit_insert(ins: &InsertStatement) -> Result<String, Error> {
234 let table = if let Some(ref schema) = ins.schema {
235 format!("{schema}.{}", ins.table)
236 } else {
237 ins.table.clone()
238 };
239
240 let mut rows = Vec::new();
241 for row_values in &ins.values {
242 if ins.columns.is_empty() {
243 let vals: Result<Vec<_>, _> = row_values.iter().map(emit_expr).collect();
245 rows.push(format!("({})", vals?.join(", ")));
246 } else {
247 let mut fields = Vec::new();
249 for (i, val) in row_values.iter().enumerate() {
250 let col_name = if i < ins.columns.len() {
251 &ins.columns[i]
252 } else {
253 return Err(Error("more values than columns in INSERT".into()));
254 };
255 fields.push(format!("{}: {}", col_name, emit_expr(val)?));
256 }
257 rows.push(format!("{{{}}}", fields.join(", ")));
258 }
259 }
260
261 Ok(format!("INSERT {} [{}]", table, rows.join(", ")))
262}
263
264fn emit_update(upd: &UpdateStatement) -> Result<String, Error> {
267 let table = if let Some(ref schema) = upd.schema {
268 format!("{schema}.{}", upd.table)
269 } else {
270 upd.table.clone()
271 };
272
273 let mut assignments = Vec::new();
274 for (col, val) in &upd.assignments {
275 assignments.push(format!("{}: {}", col, emit_expr(val)?));
276 }
277
278 let mut result = format!("UPDATE {} {{{}}}", table, assignments.join(", "));
279
280 if let Some(ref where_clause) = upd.where_clause {
281 result.push_str(&format!(" FILTER {{{}}}", emit_expr(where_clause)?));
282 }
283
284 Ok(result)
285}
286
287fn emit_delete(del: &DeleteStatement) -> Result<String, Error> {
290 let table = if let Some(ref schema) = del.schema {
291 format!("{schema}.{}", del.table)
292 } else {
293 del.table.clone()
294 };
295
296 let mut result = format!("DELETE {table}");
297
298 if let Some(ref where_clause) = del.where_clause {
299 result.push_str(&format!(" FILTER {{{}}}", emit_expr(where_clause)?));
300 }
301
302 Ok(result)
303}
304
305fn emit_create_table(ct: &CreateTableStatement) -> Result<String, Error> {
308 let table = if let Some(ref schema) = ct.schema {
309 format!("{schema}.{}", ct.table)
310 } else {
311 ct.table.clone()
312 };
313
314 let mut cols = Vec::new();
315 for col in &ct.columns {
316 let ty = emit_rql_type(&col.data_type);
317 if col.nullable {
318 cols.push(format!("{}: Option({})", col.name, ty));
319 } else {
320 cols.push(format!("{}: {}", col.name, ty));
321 }
322 }
323
324 let mut result = format!("CREATE TABLE {} {{{}}}", table, cols.join(", "));
325
326 if !ct.primary_key.is_empty() {
327 result.push_str(&format!(" WITH {{primary_key: {{{}}}}}", ct.primary_key.join(", ")));
328 }
329
330 Ok(result)
331}
332
333fn emit_rql_type(ty: &SqlType) -> &'static str {
334 match ty {
335 SqlType::Int | SqlType::Int4 | SqlType::Integer => "int4",
336 SqlType::Int2 | SqlType::Smallint => "int2",
337 SqlType::Int8 | SqlType::Bigint => "int8",
338 SqlType::Float4 | SqlType::Real => "float4",
339 SqlType::Float8 | SqlType::Double => "float8",
340 SqlType::Boolean | SqlType::Bool => "bool",
341 SqlType::Varchar(_) | SqlType::Char(_) | SqlType::Text | SqlType::Utf8 => "utf8",
342 SqlType::Blob => "blob",
343 }
344}
345
346fn emit_expr(expr: &Expr) -> Result<String, Error> {
349 match expr {
350 Expr::Identifier(name) => Ok(name.clone()),
351 Expr::QualifiedIdentifier(table, col) => Ok(format!("{table}.{col}")),
352 Expr::IntegerLiteral(n) => Ok(n.to_string()),
353 Expr::FloatLiteral(f) => Ok(format_float(*f)),
354 Expr::StringLiteral(s) => Ok(format!("'{s}'")),
355 Expr::BoolLiteral(b) => Ok(if *b {
356 "true"
357 } else {
358 "false"
359 }
360 .into()),
361 Expr::Null => Ok("none".into()),
362 Expr::BinaryOp {
363 left,
364 op,
365 right,
366 } => {
367 let l = emit_expr(left)?;
368 let r = emit_expr(right)?;
369 let op_str = match op {
370 BinaryOp::Eq => "==",
371 BinaryOp::NotEq => "!=",
372 BinaryOp::Lt => "<",
373 BinaryOp::Gt => ">",
374 BinaryOp::LtEq => "<=",
375 BinaryOp::GtEq => ">=",
376 BinaryOp::And => "and",
377 BinaryOp::Or => "or",
378 BinaryOp::Add => "+",
379 BinaryOp::Sub => "-",
380 BinaryOp::Mul => "*",
381 BinaryOp::Div => "/",
382 BinaryOp::Mod => "%",
383 };
384 Ok(format!("{l} {op_str} {r}"))
385 }
386 Expr::UnaryOp {
387 op,
388 expr,
389 } => {
390 let e = emit_expr(expr)?;
391 match op {
392 UnaryOp::Not => Ok(format!("not {e}")),
393 UnaryOp::Neg => Ok(format!("-{e}")),
394 }
395 }
396 Expr::FunctionCall {
397 name,
398 args,
399 } => {
400 let func_name = sql_to_rql_function(name)?;
401 let arg_strs: Result<Vec<_>, _> = args.iter().map(emit_expr).collect();
402 let args_str = arg_strs?.join(", ");
403 Ok(format!("{func_name}({args_str})"))
404 }
405 Expr::Between {
406 expr,
407 low,
408 high,
409 negated,
410 } => {
411 let e = emit_expr(expr)?;
412 let l = emit_expr(low)?;
413 let h = emit_expr(high)?;
414 if *negated {
415 Ok(format!("not ({e} between {l} and {h})"))
416 } else {
417 Ok(format!("{e} between {l} and {h}"))
418 }
419 }
420 Expr::InList {
421 expr,
422 list,
423 negated,
424 } => {
425 let e = emit_expr(expr)?;
426 let items: Result<Vec<_>, _> = list.iter().map(emit_expr).collect();
427 let items_str = items?.join(", ");
428 if *negated {
429 Ok(format!("not ({e} in ({items_str}))"))
430 } else {
431 Ok(format!("{e} in ({items_str})"))
432 }
433 }
434 Expr::IsNull {
435 expr,
436 negated,
437 } => {
438 let e = emit_expr(expr)?;
439 if *negated {
440 Ok(format!("{e} != none"))
441 } else {
442 Ok(format!("{e} == none"))
443 }
444 }
445 Expr::Cast {
446 expr,
447 data_type,
448 } => {
449 let e = emit_expr(expr)?;
450 let ty = emit_rql_type(data_type);
451 Ok(format!("cast({e}, {ty})"))
452 }
453 Expr::Nested(inner) => {
454 let e = emit_expr(inner)?;
455 Ok(format!("({e})"))
456 }
457 }
458}
459
460fn format_float(f: f64) -> String {
461 let s = f.to_string();
462 if s.contains('.') {
463 s
464 } else {
465 format!("{s}.0")
466 }
467}
468
469fn is_all_columns(cols: &[SelectColumn]) -> bool {
472 cols.len() == 1 && matches!(cols[0], SelectColumn::AllColumns)
473}
474
475fn has_aggregate_functions(cols: &[SelectColumn]) -> bool {
476 cols.iter().any(|c| match c {
477 SelectColumn::Expr {
478 expr,
479 ..
480 } => expr_has_aggregate(expr),
481 _ => false,
482 })
483}
484
485fn expr_has_aggregate(expr: &Expr) -> bool {
486 match expr {
487 Expr::FunctionCall {
488 name,
489 ..
490 } => {
491 matches!(
492 sql_to_rql_function(name),
493 Ok("math::count" | "math::sum" | "math::avg" | "math::min" | "math::max")
494 )
495 }
496 Expr::BinaryOp {
497 left,
498 right,
499 ..
500 } => expr_has_aggregate(left) || expr_has_aggregate(right),
501 Expr::UnaryOp {
502 expr,
503 ..
504 } => expr_has_aggregate(expr),
505 Expr::Nested(inner) => expr_has_aggregate(inner),
506 _ => false,
507 }
508}
509
510fn collect_aggregate_columns(cols: &[SelectColumn]) -> Result<String, Error> {
511 let mut agg_exprs = Vec::new();
512 for col in cols {
513 if let SelectColumn::Expr {
514 expr,
515 alias,
516 } = col
517 {
518 if expr_has_aggregate(expr) {
519 let e = emit_expr(expr)?;
520 if let Some(alias) = alias {
521 agg_exprs.push(format!("{alias}: {e}"));
522 } else {
523 agg_exprs.push(e);
524 }
525 }
526 }
527 }
528 Ok(agg_exprs.join(", "))
529}
530
531fn collect_non_aggregate_map_columns(sel: &SelectStatement) -> Result<String, Error> {
532 let mut map_exprs = Vec::new();
533 for col in &sel.columns {
534 if let SelectColumn::Expr {
535 expr,
536 alias,
537 } = col
538 {
539 if !expr_has_aggregate(expr) {
540 let is_in_group_by = sel.group_by.iter().any(|gb| expr_eq(gb, expr));
542 if !is_in_group_by {
543 let e = emit_expr(expr)?;
544 if let Some(alias) = alias {
545 map_exprs.push(format!("{alias}: {e}"));
546 } else {
547 map_exprs.push(e);
548 }
549 }
550 }
551 }
552 }
553 Ok(map_exprs.join(", "))
554}
555
556fn emit_select_columns(cols: &[SelectColumn]) -> Result<String, Error> {
557 let mut parts = Vec::new();
558 for col in cols {
559 match col {
560 SelectColumn::AllColumns => parts.push("*".into()),
561 SelectColumn::Expr {
562 expr,
563 alias,
564 } => {
565 let e = emit_expr(expr)?;
566 if let Some(alias) = alias {
567 parts.push(format!("{alias}: {e}"));
568 } else {
569 parts.push(e);
570 }
571 }
572 }
573 }
574 Ok(parts.join(", "))
575}
576
577fn emit_select_columns_plain(cols: &[SelectColumn]) -> Result<String, Error> {
578 let mut parts = Vec::new();
579 for col in cols {
580 match col {
581 SelectColumn::AllColumns => parts.push("*".into()),
582 SelectColumn::Expr {
583 expr,
584 ..
585 } => {
586 parts.push(emit_expr(expr)?);
587 }
588 }
589 }
590 Ok(parts.join(", "))
591}
592
593fn sql_to_rql_function(name: &str) -> Result<&'static str, Error> {
594 match name.to_uppercase().as_str() {
595 "COUNT" => Ok("math::count"),
597 "SUM" => Ok("math::sum"),
598 "AVG" => Ok("math::avg"),
599 "MIN" => Ok("math::min"),
600 "MAX" => Ok("math::max"),
601 "ABS" => Ok("math::abs"),
603 "ACOS" => Ok("math::acos"),
604 "ASIN" => Ok("math::asin"),
605 "ATAN" => Ok("math::atan"),
606 "ATAN2" => Ok("math::atan2"),
607 "CEIL" | "CEILING" => Ok("math::ceil"),
608 "COS" => Ok("math::cos"),
609 "EXP" => Ok("math::exp"),
610 "FLOOR" => Ok("math::floor"),
611 "GCD" => Ok("math::gcd"),
612 "LCM" => Ok("math::lcm"),
613 "LOG" => Ok("math::log"),
614 "LOG10" => Ok("math::log10"),
615 "LOG2" => Ok("math::log2"),
616 "MOD" => Ok("math::mod"),
617 "PI" => Ok("math::pi"),
618 "POWER" | "POW" => Ok("math::power"),
619 "ROUND" => Ok("math::round"),
620 "SIGN" => Ok("math::sign"),
621 "SIN" => Ok("math::sin"),
622 "SQRT" => Ok("math::sqrt"),
623 "TAN" => Ok("math::tan"),
624 "TRUNCATE" | "TRUNC" => Ok("math::truncate"),
625 "ASCII" => Ok("text::ascii"),
627 "CHAR" | "CHR" => Ok("text::char"),
628 "CONCAT" => Ok("text::concat"),
629 "LENGTH" | "LEN" | "CHAR_LENGTH" | "CHARACTER_LENGTH" => Ok("text::length"),
630 "LOWER" | "LCASE" => Ok("text::lower"),
631 "LPAD" => Ok("text::pad_left"),
632 "REPEAT" => Ok("text::repeat"),
633 "REPLACE" => Ok("text::replace"),
634 "REVERSE" => Ok("text::reverse"),
635 "RPAD" => Ok("text::pad_right"),
636 "SUBSTRING" | "SUBSTR" => Ok("text::substring"),
637 "TRIM" => Ok("text::trim"),
638 "LTRIM" => Ok("text::trim_start"),
639 "RTRIM" => Ok("text::trim_end"),
640 "UPPER" | "UCASE" => Ok("text::upper"),
641 _ => Err(Error(format!("no SQL-to-RQL mapping for function: {name}"))),
642 }
643}
644
645fn emit_order_by(items: &[OrderByItem]) -> Result<String, Error> {
646 let mut parts = Vec::new();
647 for item in items {
648 let e = emit_expr(&item.expr)?;
649 match item.direction {
650 OrderDirection::Asc => parts.push(format!("{e}:asc")),
651 OrderDirection::Desc => parts.push(e),
652 };
653 }
654 Ok(parts.join(", "))
655}
656
657fn emit_expr_comma_list(exprs: &[Expr]) -> Result<String, Error> {
658 let parts: Result<Vec<_>, _> = exprs.iter().map(emit_expr).collect();
659 Ok(parts?.join(", "))
660}
661
662fn expr_eq(a: &Expr, b: &Expr) -> bool {
664 match (a, b) {
665 (Expr::Identifier(a), Expr::Identifier(b)) => a == b,
666 (Expr::QualifiedIdentifier(at, ac), Expr::QualifiedIdentifier(bt, bc)) => at == bt && ac == bc,
667 _ => false,
668 }
669}
670
671#[cfg(test)]
672mod tests {
673 use super::*;
674 use crate::{parser::Parser, token::tokenize};
675
676 fn transpile(sql: &str) -> String {
677 let tokens = tokenize(sql).unwrap();
678 let stmt = Parser::new(tokens).parse().unwrap();
679 emit(&stmt).unwrap()
680 }
681
682 #[test]
683 fn test_select_star() {
684 assert_eq!(transpile("SELECT * FROM users"), "FROM users");
685 }
686
687 #[test]
688 fn test_select_columns() {
689 assert_eq!(transpile("SELECT id, name FROM users"), "FROM users MAP {id, name}");
690 }
691
692 #[test]
693 fn test_select_alias() {
694 assert_eq!(transpile("SELECT id, name AS username FROM users"), "FROM users MAP {id, username: name}");
695 }
696
697 #[test]
698 fn test_where() {
699 assert_eq!(transpile("SELECT * FROM users WHERE age > 18"), "FROM users FILTER {age > 18}");
700 }
701
702 #[test]
703 fn test_order_by() {
704 assert_eq!(transpile("SELECT * FROM users ORDER BY name ASC"), "FROM users SORT {name:asc}");
705 }
706
707 #[test]
708 fn test_limit() {
709 assert_eq!(transpile("SELECT * FROM users LIMIT 10"), "FROM users TAKE 10");
710 }
711
712 #[test]
713 fn test_insert() {
714 assert_eq!(
715 transpile("INSERT INTO users (id, name) VALUES (1, 'Alice')"),
716 "INSERT users [{id: 1, name: 'Alice'}]"
717 );
718 }
719
720 #[test]
721 fn test_update() {
722 assert_eq!(
723 transpile("UPDATE users SET name = 'Bob' WHERE id = 1"),
724 "UPDATE users {name: 'Bob'} FILTER {id == 1}"
725 );
726 }
727
728 #[test]
729 fn test_delete() {
730 assert_eq!(transpile("DELETE FROM users WHERE id = 1"), "DELETE users FILTER {id == 1}");
731 }
732
733 #[test]
734 fn test_create_table() {
735 assert_eq!(
736 transpile("CREATE TABLE users (id INT, name TEXT, active BOOLEAN)"),
737 "CREATE TABLE users {id: Option(int4), name: Option(utf8), active: Option(bool)}"
738 );
739 }
740
741 #[test]
742 fn test_create_table_not_null() {
743 assert_eq!(
744 transpile("CREATE TABLE t (id INT NOT NULL, name TEXT)"),
745 "CREATE TABLE t {id: int4, name: Option(utf8)}"
746 );
747 }
748
749 #[test]
750 fn test_null_to_none() {
751 assert_eq!(transpile("SELECT NULL"), "MAP {none}");
752 }
753
754 #[test]
755 fn test_not_equal() {
756 assert_eq!(transpile("SELECT * FROM t WHERE a <> b"), "FROM t FILTER {a != b}");
757 }
758
759 #[test]
760 fn test_and_or() {
761 assert_eq!(transpile("SELECT * FROM t WHERE a = 1 AND b = 2"), "FROM t FILTER {a == 1 and b == 2}");
762 }
763
764 #[test]
765 fn test_distinct() {
766 assert_eq!(transpile("SELECT DISTINCT name FROM users"), "FROM users DISTINCT {name}");
767 }
768
769 #[test]
770 fn test_group_by_aggregate() {
771 assert_eq!(
772 transpile("SELECT COUNT(id) FROM users GROUP BY dept"),
773 "FROM users AGGREGATE {math::count(id)} BY {dept}"
774 );
775 }
776
777 #[test]
778 fn test_cast() {
779 assert_eq!(transpile("SELECT CAST(x AS INT)"), "MAP {cast(x, int4)}");
780 }
781
782 #[test]
783 fn test_between() {
784 assert_eq!(transpile("SELECT * FROM t WHERE x BETWEEN 1 AND 10"), "FROM t FILTER {x between 1 and 10}");
785 }
786
787 #[test]
788 fn test_in_list() {
789 assert_eq!(transpile("SELECT * FROM t WHERE x IN (1, 2, 3)"), "FROM t FILTER {x in (1, 2, 3)}");
790 }
791
792 #[test]
793 fn test_is_null() {
794 assert_eq!(transpile("SELECT * FROM t WHERE x IS NULL"), "FROM t FILTER {x == none}");
795 }
796
797 #[test]
798 fn test_is_not_null() {
799 assert_eq!(transpile("SELECT * FROM t WHERE x IS NOT NULL"), "FROM t FILTER {x != none}");
800 }
801
802 #[test]
803 fn test_join() {
804 assert_eq!(
805 transpile("SELECT * FROM t1 INNER JOIN t2 ON t1.a = t2.b"),
806 "FROM t1 JOIN {FROM t2} AS t2 USING (a, t2.b)"
807 );
808 }
809
810 #[test]
811 fn test_left_join() {
812 assert_eq!(
813 transpile("SELECT * FROM t1 LEFT JOIN t2 ON t1.a = t2.b"),
814 "FROM t1 LEFT JOIN {FROM t2} AS t2 USING (a, t2.b)"
815 );
816 }
817
818 #[test]
819 fn test_select_no_from() {
820 assert_eq!(transpile("SELECT 1 + 2"), "MAP {1 + 2}");
821 }
822
823 #[test]
824 fn test_multiple_insert_rows() {
825 assert_eq!(
826 transpile("INSERT INTO t (a, b) VALUES (1, 2), (3, 4)"),
827 "INSERT t [{a: 1, b: 2}, {a: 3, b: 4}]"
828 );
829 }
830
831 #[test]
832 fn test_schema_qualified_table() {
833 assert_eq!(transpile("SELECT * FROM test.users"), "FROM test.users");
834 }
835
836 #[test]
837 fn test_simple_cte() {
838 assert_eq!(
839 transpile("WITH t AS (SELECT * FROM users WHERE active = true) SELECT * FROM t"),
840 "LET $t = FROM users FILTER {active == true}; FROM $t"
841 );
842 }
843
844 #[test]
845 fn test_insert_without_columns() {
846 assert_eq!(transpile("INSERT INTO t1 VALUES (1, 'true')"), "INSERT t1 [(1, 'true')]");
847 }
848
849 #[test]
850 fn test_create_table_primary_key() {
851 assert_eq!(
852 transpile("CREATE TABLE t (v1 INT NOT NULL, v2 INT NOT NULL, PRIMARY KEY(v1))"),
853 "CREATE TABLE t {v1: int4, v2: int4} WITH {primary_key: {v1}}"
854 );
855 assert_eq!(
856 transpile("CREATE TABLE t (a INT NOT NULL, b INT NOT NULL, PRIMARY KEY(a, b))"),
857 "CREATE TABLE t {a: int4, b: int4} WITH {primary_key: {a, b}}"
858 );
859 }
860
861 #[test]
862 fn test_multiple_ctes() {
863 assert_eq!(
864 transpile("WITH a AS (SELECT * FROM users), b AS (SELECT id FROM a) SELECT * FROM b"),
865 "LET $a = FROM users; LET $b = FROM $a MAP {id}; FROM $b"
866 );
867 }
868}