From 5572a110c59ea013b719547721c6a2d5ff8c43fc Mon Sep 17 00:00:00 2001 From: Andy Lok Date: Thu, 28 Mar 2024 04:26:10 +0800 Subject: [PATCH] feat(script): add script compiler --- Cargo.lock | 28 + Cargo.toml | 1 + src/common/exception/src/exception_code.rs | 2 + src/query/ast/src/ast/query.rs | 14 +- src/query/ast/src/ast/statements/script.rs | 27 +- src/query/ast/src/parser/expr.rs | 24 +- src/query/ast/src/parser/script.rs | 35 +- src/query/ast/tests/it/parser.rs | 68 +- .../{experimental-expr.txt => dialect.txt} | 494 ++++++ .../ast/tests/it/testdata/expr-error.txt | 16 +- src/query/ast/tests/it/testdata/script.txt | 130 +- src/query/script/Cargo.toml | 39 + src/query/script/src/lib.rs | 1411 +++++++++++++++++ src/query/script/tests/it/compiler.rs | 195 +++ src/query/script/tests/it/main.rs | 15 + .../tests/it/testdata/compiler-error.txt | 142 ++ .../script/tests/it/testdata/compiler.txt | 207 +++ 17 files changed, 2703 insertions(+), 145 deletions(-) rename src/query/ast/tests/it/testdata/{experimental-expr.txt => dialect.txt} (72%) create mode 100644 src/query/script/Cargo.toml create mode 100644 src/query/script/src/lib.rs create mode 100644 src/query/script/tests/it/compiler.rs create mode 100644 src/query/script/tests/it/main.rs create mode 100644 src/query/script/tests/it/testdata/compiler-error.txt create mode 100644 src/query/script/tests/it/testdata/compiler.txt diff --git a/Cargo.lock b/Cargo.lock index 216c9001793b7..d46b6c8dd51e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3618,6 +3618,34 @@ dependencies = [ "tonic-build", ] +[[package]] +name = "databend-common-script" +version = "0.1.0" +dependencies = [ + "databend-common-ast", + "databend-common-exception", + "derive-visitor", + "enum-as-inner 0.5.1", + "ethnum 1.5.0 (git+https://github.com/ariesdevil/ethnum-rs?rev=4cb05f1)", + "fast-float", + "goldenfile", + "indent", + "itertools 0.10.5", + "logos", + "minitrace", + "nom", + "nom-rule", + "ordered-float 4.2.0", + "pratt 0.4.0", + "pretty", + "serde", + "strsim 0.10.0", + "strum 0.24.1", + "strum_macros 0.24.3", + "unindent", + "url", +] + [[package]] name = "databend-common-settings" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index d1b55e044de71..b8b1a8ce41a34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ members = [ "src/query/pipeline/sinks", "src/query/pipeline/sources", "src/query/pipeline/transforms", + "src/query/script", "src/query/settings", "src/query/sql", "src/query/storages/common/blocks", diff --git a/src/common/exception/src/exception_code.rs b/src/common/exception/src/exception_code.rs index 1d8cd6f8503e9..f63f3d0e0b7ba 100644 --- a/src/common/exception/src/exception_code.rs +++ b/src/common/exception/src/exception_code.rs @@ -352,6 +352,8 @@ build_exceptions! { TenantQuotaUnknown(2902), TenantQuotaExceeded(2903), + // Script error codes. + ScriptSematicError(3001), } // Storage errors [3001, 4000]. diff --git a/src/query/ast/src/ast/query.rs b/src/query/ast/src/ast/query.rs index 9821c5ab5a944..d684169e2c2b5 100644 --- a/src/query/ast/src/ast/query.rs +++ b/src/query/ast/src/ast/query.rs @@ -151,10 +151,10 @@ pub enum SetOperator { #[derive(Debug, Clone, PartialEq, Drive, DriveMut)] pub struct OrderByExpr { pub expr: Expr, - // Optional `ASC` or `DESC` + /// `ASC` or `DESC` #[drive(skip)] pub asc: Option, - // Optional `NULLS FIRST` or `NULLS LAST` + /// `NULLS FIRST` or `NULLS LAST` #[drive(skip)] pub nulls_first: Option, } @@ -162,16 +162,16 @@ pub struct OrderByExpr { /// One item of the comma-separated list following `SELECT` #[derive(Debug, Clone, PartialEq, Drive, DriveMut)] pub enum SelectTarget { - // Expression with alias, e.g. `SELECT t.a, b AS a, a+1 AS b FROM t` + /// Expression with alias, e.g. `SELECT t.a, b AS a, a+1 AS b FROM t` AliasedExpr { expr: Box, alias: Option, }, - // Qualified star name, e.g. `SELECT t.* exclude a, columns(expr) FROM t`. - // Columns("pattern_str") - // Columns(lambda expression) - // For simplicity, star wildcard is involved. + /// Qualified star name, e.g. `SELECT t.* exclude a, columns(expr) FROM t`. + /// Columns("pattern_str") + /// Columns(lambda expression) + /// For simplicity, star wildcard is involved. StarColumns { qualified: QualifiedName, column_filter: Option, diff --git a/src/query/ast/src/ast/statements/script.rs b/src/query/ast/src/ast/statements/script.rs index 3e4ca5bae7f03..b6684e3821e6e 100644 --- a/src/query/ast/src/ast/statements/script.rs +++ b/src/query/ast/src/ast/statements/script.rs @@ -19,7 +19,6 @@ use databend_common_exception::Span; use crate::ast::Expr; use crate::ast::Identifier; -use crate::ast::Query; use crate::ast::Statement; use crate::ast::TypeName; @@ -64,15 +63,15 @@ impl Display for VariableDeclare { } #[derive(Debug, Clone, PartialEq)] -pub struct QueryDeclare { +pub struct StatementDeclare { pub name: Identifier, - pub query: Query, + pub stmt: Statement, } -impl Display for QueryDeclare { +impl Display for StatementDeclare { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let QueryDeclare { name, query } = self; - write!(f, "{name} RESULTSET := {query}") + let StatementDeclare { name, stmt } = self; + write!(f, "{name} RESULTSET := {stmt}") } } @@ -82,9 +81,13 @@ pub enum ScriptStatement { span: Span, declare: VariableDeclare, }, - LetQuery { + LetStatement { span: Span, - declare: QueryDeclare, + declare: StatementDeclare, + }, + RunStatement { + span: Span, + stmt: Statement, }, Assign { span: Span, @@ -149,17 +152,14 @@ pub enum ScriptStatement { results: Vec>, else_result: Option>, }, - SQLStatement { - span: Span, - stmt: Statement, - }, } impl Display for ScriptStatement { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { ScriptStatement::LetVar { declare, .. } => write!(f, "LET {declare}"), - ScriptStatement::LetQuery { declare, .. } => write!(f, "LET {declare}"), + ScriptStatement::LetStatement { declare, .. } => write!(f, "LET {declare}"), + ScriptStatement::RunStatement { stmt, .. } => write!(f, "{stmt}"), ScriptStatement::Assign { name, value, .. } => write!(f, "{name} := {value}"), ScriptStatement::Return { value, .. } => { if let Some(value) = value { @@ -352,7 +352,6 @@ impl Display for ScriptStatement { } write!(f, "END IF") } - ScriptStatement::SQLStatement { stmt, .. } => write!(f, "{stmt}"), } } } diff --git a/src/query/ast/src/parser/expr.rs b/src/query/ast/src/parser/expr.rs index 44929a9a71a61..6d2184d5798c9 100644 --- a/src/query/ast/src/parser/expr.rs +++ b/src/query/ast/src/parser/expr.rs @@ -1378,21 +1378,17 @@ pub fn literal_string(i: Input) -> IResult { QuotedString }, |token| { - if token - .text() - .chars() - .next() - .filter(|c| i.dialect.is_string_quote(*c)) - .is_some() - { - let str = &token.text()[1..token.text().len() - 1]; - let unescaped = unescape_string(str, '\'').ok_or(nom::Err::Failure( - ErrorKind::Other("invalid escape or unicode"), - ))?; - Ok(unescaped) - } else { - Err(nom::Err::Error(ErrorKind::ExpectToken(QuotedString))) + if let Some(quote) = token.text().chars().next() { + if i.dialect.is_string_quote(quote) { + let str = &token.text()[1..token.text().len() - 1]; + let unescaped = unescape_string(str, quote).ok_or(nom::Err::Failure( + ErrorKind::Other("invalid escape or unicode"), + ))?; + return Ok(unescaped); + } } + + Err(nom::Err::Error(ErrorKind::ExpectToken(QuotedString))) }, )(i) } diff --git a/src/query/ast/src/parser/script.rs b/src/query/ast/src/parser/script.rs index b3ee7bdbb317d..611cbb477acac 100644 --- a/src/query/ast/src/parser/script.rs +++ b/src/query/ast/src/parser/script.rs @@ -19,19 +19,22 @@ use crate::ast::*; use crate::parser::common::*; use crate::parser::expr::*; use crate::parser::input::Input; -use crate::parser::query::*; use crate::parser::statement::*; use crate::parser::token::*; use crate::rule; +pub fn script_stmts(i: Input) -> IResult> { + semicolon_terminated_list1(script_stmt)(i) +} + pub fn script_stmt(i: Input) -> IResult { - let let_query_stmt = map( + let let_stmt_stmt = map( consumed(rule! { - LET ~^#ident ~ RESULTSET ~ ^":=" ~ ^#query + LET ~^#ident ~ RESULTSET ~ ^":=" ~ ^#statement_body }), - |(span, (_, name, _, _, query))| ScriptStatement::LetQuery { + |(span, (_, name, _, _, stmt))| ScriptStatement::LetStatement { span: transform_span(span.tokens), - declare: QueryDeclare { name, query }, + declare: StatementDeclare { name, stmt }, }, ); let let_var_stmt = map( @@ -47,6 +50,15 @@ pub fn script_stmt(i: Input) -> IResult { }, }, ); + let run_stmt = map( + consumed(rule! { + #statement_body + }), + |(span, stmt)| ScriptStatement::RunStatement { + span: transform_span(span.tokens), + stmt, + }, + ); let assign_stmt = map( consumed(rule! { #ident ~ ":=" ~ ^#expr @@ -199,19 +211,11 @@ pub fn script_stmt(i: Input) -> IResult { } }, ); - let sql_stmt = map( - consumed(rule! { - #statement_body - }), - |(span, stmt)| ScriptStatement::SQLStatement { - span: transform_span(span.tokens), - stmt, - }, - ); rule!( - #let_query_stmt + #let_stmt_stmt | #let_var_stmt + | #run_stmt | #assign_stmt | #return_stmt | #for_loop_stmt @@ -223,6 +227,5 @@ pub fn script_stmt(i: Input) -> IResult { | #continue_stmt | #case_stmt | #if_stmt - | #sql_stmt )(i) } diff --git a/src/query/ast/tests/it/parser.rs b/src/query/ast/tests/it/parser.rs index 48d20d322597a..9f002c2625266 100644 --- a/src/query/ast/tests/it/parser.rs +++ b/src/query/ast/tests/it/parser.rs @@ -963,48 +963,72 @@ fn test_expr() { } #[test] -fn test_experimental_expr() { +fn test_expr_error() { let mut mint = Mint::new("tests/it/testdata"); - let file = &mut mint.new_goldenfile("experimental-expr.txt").unwrap(); + let file = &mut mint.new_goldenfile("expr-error.txt").unwrap(); let cases = &[ - r#"a"#, + r#"5 * (a and ) 1"#, + r#"a + +"#, + r#"CAST(col1 AS foo)"#, + r#"1 a"#, + r#"CAST(col1)"#, r#"a.add(b)"#, - r#"a.sub(b).add(e)"#, - r#"a.sub(b).add(e)"#, - r#"1 + {'k1': 4}.k1"#, - r#"'3'.plus(4)"#, - r#"(3).add({'k1': 4 }.k1)"#, r#"[ x * 100 FOR x in [1,2,3] if x % 2 = 0 ]"#, + r#" + G.E.B IS NOT NULL + AND col1 NOT BETWEEN col2 AND + AND 1 + col3 DIV sum(col4) + "#, ]; for case in cases { - run_parser_with_dialect(file, expr, Dialect::Experimental, ParseMode::Default, case); + run_parser(file, expr, case); } } #[test] -fn test_expr_error() { +fn test_dialect() { let mut mint = Mint::new("tests/it/testdata"); - let file = &mut mint.new_goldenfile("expr-error.txt").unwrap(); + let file = &mut mint.new_goldenfile("dialect.txt").unwrap(); let cases = &[ - r#"5 * (a and ) 1"#, - r#"a + +"#, - r#"CAST(col1 AS foo)"#, - r#"1 a"#, - r#"CAST(col1)"#, + r#"'a'"#, + r#""a""#, + r#"`a`"#, + r#"'a''b'"#, + r#"'a""b'"#, + r#"'a\'b'"#, + r#"'a"b'"#, + r#"'a`b'"#, + r#""a''b""#, + r#""a""b""#, + r#""a'b""#, + r#""a\"b""#, + r#""a`b""#, + ]; + + for case in cases { + run_parser_with_dialect(file, expr, Dialect::PostgreSQL, ParseMode::Default, case); + } + + for case in cases { + run_parser_with_dialect(file, expr, Dialect::MySQL, ParseMode::Default, case); + } + + let cases = &[ + r#"a"#, r#"a.add(b)"#, + r#"a.sub(b).add(e)"#, + r#"a.sub(b).add(e)"#, + r#"1 + {'k1': 4}.k1"#, + r#"'3'.plus(4)"#, + r#"(3).add({'k1': 4 }.k1)"#, r#"[ x * 100 FOR x in [1,2,3] if x % 2 = 0 ]"#, - r#" - G.E.B IS NOT NULL AND - col1 NOT BETWEEN col2 AND - AND 1 + col3 DIV sum(col4) - "#, ]; for case in cases { - run_parser(file, expr, case); + run_parser_with_dialect(file, expr, Dialect::Experimental, ParseMode::Default, case); } } diff --git a/src/query/ast/tests/it/testdata/experimental-expr.txt b/src/query/ast/tests/it/testdata/dialect.txt similarity index 72% rename from src/query/ast/tests/it/testdata/experimental-expr.txt rename to src/query/ast/tests/it/testdata/dialect.txt index eb1cec774e4e0..e199b8557e63d 100644 --- a/src/query/ast/tests/it/testdata/experimental-expr.txt +++ b/src/query/ast/tests/it/testdata/dialect.txt @@ -1,3 +1,497 @@ +---------- Input ---------- +'a' +---------- Output --------- +'a' +---------- AST ------------ +Literal { + span: Some( + 0..3, + ), + lit: String( + "a", + ), +} + + +---------- Input ---------- +"a" +---------- Output --------- +"a" +---------- AST ------------ +ColumnRef { + span: Some( + 0..3, + ), + column: ColumnRef { + database: None, + table: None, + column: Name( + Identifier { + span: Some( + 0..3, + ), + name: "a", + quote: Some( + '"', + ), + is_hole: false, + }, + ), + }, +} + + +---------- Input ---------- +`a` +---------- Output --------- +`a` +---------- AST ------------ +ColumnRef { + span: Some( + 0..3, + ), + column: ColumnRef { + database: None, + table: None, + column: Name( + Identifier { + span: Some( + 0..3, + ), + name: "a", + quote: Some( + '`', + ), + is_hole: false, + }, + ), + }, +} + + +---------- Input ---------- +'a''b' +---------- Output --------- +'a\'b' +---------- AST ------------ +Literal { + span: Some( + 0..6, + ), + lit: String( + "a'b", + ), +} + + +---------- Input ---------- +'a""b' +---------- Output --------- +'a""b' +---------- AST ------------ +Literal { + span: Some( + 0..6, + ), + lit: String( + "a\"\"b", + ), +} + + +---------- Input ---------- +'a\'b' +---------- Output --------- +'a\'b' +---------- AST ------------ +Literal { + span: Some( + 0..6, + ), + lit: String( + "a'b", + ), +} + + +---------- Input ---------- +'a"b' +---------- Output --------- +'a"b' +---------- AST ------------ +Literal { + span: Some( + 0..5, + ), + lit: String( + "a\"b", + ), +} + + +---------- Input ---------- +'a`b' +---------- Output --------- +'a`b' +---------- AST ------------ +Literal { + span: Some( + 0..5, + ), + lit: String( + "a`b", + ), +} + + +---------- Input ---------- +"a''b" +---------- Output --------- +"a''b" +---------- AST ------------ +ColumnRef { + span: Some( + 0..6, + ), + column: ColumnRef { + database: None, + table: None, + column: Name( + Identifier { + span: Some( + 0..6, + ), + name: "a''b", + quote: Some( + '"', + ), + is_hole: false, + }, + ), + }, +} + + +---------- Input ---------- +"a""b" +---------- Output --------- +"a""b" +---------- AST ------------ +ColumnRef { + span: Some( + 0..6, + ), + column: ColumnRef { + database: None, + table: None, + column: Name( + Identifier { + span: Some( + 0..6, + ), + name: "a\"b", + quote: Some( + '"', + ), + is_hole: false, + }, + ), + }, +} + + +---------- Input ---------- +"a'b" +---------- Output --------- +"a'b" +---------- AST ------------ +ColumnRef { + span: Some( + 0..5, + ), + column: ColumnRef { + database: None, + table: None, + column: Name( + Identifier { + span: Some( + 0..5, + ), + name: "a'b", + quote: Some( + '"', + ), + is_hole: false, + }, + ), + }, +} + + +---------- Input ---------- +"a\"b" +---------- Output --------- +"a\""b" +---------- AST ------------ +ColumnRef { + span: Some( + 0..6, + ), + column: ColumnRef { + database: None, + table: None, + column: Name( + Identifier { + span: Some( + 0..6, + ), + name: "a\\\"b", + quote: Some( + '"', + ), + is_hole: false, + }, + ), + }, +} + + +---------- Input ---------- +"a`b" +---------- Output --------- +"a`b" +---------- AST ------------ +ColumnRef { + span: Some( + 0..5, + ), + column: ColumnRef { + database: None, + table: None, + column: Name( + Identifier { + span: Some( + 0..5, + ), + name: "a`b", + quote: Some( + '"', + ), + is_hole: false, + }, + ), + }, +} + + +---------- Input ---------- +'a' +---------- Output --------- +'a' +---------- AST ------------ +Literal { + span: Some( + 0..3, + ), + lit: String( + "a", + ), +} + + +---------- Input ---------- +"a" +---------- Output --------- +'a' +---------- AST ------------ +Literal { + span: Some( + 0..3, + ), + lit: String( + "a", + ), +} + + +---------- Input ---------- +`a` +---------- Output --------- +`a` +---------- AST ------------ +ColumnRef { + span: Some( + 0..3, + ), + column: ColumnRef { + database: None, + table: None, + column: Name( + Identifier { + span: Some( + 0..3, + ), + name: "a", + quote: Some( + '`', + ), + is_hole: false, + }, + ), + }, +} + + +---------- Input ---------- +'a''b' +---------- Output --------- +'a\'b' +---------- AST ------------ +Literal { + span: Some( + 0..6, + ), + lit: String( + "a'b", + ), +} + + +---------- Input ---------- +'a""b' +---------- Output --------- +'a""b' +---------- AST ------------ +Literal { + span: Some( + 0..6, + ), + lit: String( + "a\"\"b", + ), +} + + +---------- Input ---------- +'a\'b' +---------- Output --------- +'a\'b' +---------- AST ------------ +Literal { + span: Some( + 0..6, + ), + lit: String( + "a'b", + ), +} + + +---------- Input ---------- +'a"b' +---------- Output --------- +'a"b' +---------- AST ------------ +Literal { + span: Some( + 0..5, + ), + lit: String( + "a\"b", + ), +} + + +---------- Input ---------- +'a`b' +---------- Output --------- +'a`b' +---------- AST ------------ +Literal { + span: Some( + 0..5, + ), + lit: String( + "a`b", + ), +} + + +---------- Input ---------- +"a''b" +---------- Output --------- +'a\'\'b' +---------- AST ------------ +Literal { + span: Some( + 0..6, + ), + lit: String( + "a''b", + ), +} + + +---------- Input ---------- +"a""b" +---------- Output --------- +'a"b' +---------- AST ------------ +Literal { + span: Some( + 0..6, + ), + lit: String( + "a\"b", + ), +} + + +---------- Input ---------- +"a'b" +---------- Output --------- +'a\'b' +---------- AST ------------ +Literal { + span: Some( + 0..5, + ), + lit: String( + "a'b", + ), +} + + +---------- Input ---------- +"a\"b" +---------- Output --------- +'a\\"b' +---------- AST ------------ +Literal { + span: Some( + 0..6, + ), + lit: String( + "a\\\"b", + ), +} + + +---------- Input ---------- +"a`b" +---------- Output --------- +'a`b' +---------- AST ------------ +Literal { + span: Some( + 0..5, + ), + lit: String( + "a`b", + ), +} + + ---------- Input ---------- a ---------- Output --------- diff --git a/src/query/ast/tests/it/testdata/expr-error.txt b/src/query/ast/tests/it/testdata/expr-error.txt index b79e690679e96..fbb571879b0e5 100644 --- a/src/query/ast/tests/it/testdata/expr-error.txt +++ b/src/query/ast/tests/it/testdata/expr-error.txt @@ -88,18 +88,18 @@ error: ---------- Input ---------- -G.E.B IS NOT NULL AND - col1 NOT BETWEEN col2 AND - AND 1 + col3 DIV sum(col4) +G.E.B IS NOT NULL +AND col1 NOT BETWEEN col2 AND +AND 1 + col3 DIV sum(col4) ---------- Output --------- error: - --> SQL:3:9 + --> SQL:3:1 | -1 | G.E.B IS NOT NULL AND +1 | G.E.B IS NOT NULL | - while parsing expression -2 | col1 NOT BETWEEN col2 AND +2 | AND col1 NOT BETWEEN col2 AND | --- while parsing `[NOT] BETWEEN ... AND ...` -3 | AND 1 + col3 DIV sum(col4) - | ^^^ expected more tokens for expression +3 | AND 1 + col3 DIV sum(col4) + | ^^^ expected more tokens for expression diff --git a/src/query/ast/tests/it/testdata/script.txt b/src/query/ast/tests/it/testdata/script.txt index 91c867e9a46ab..4e66c41cf7887 100644 --- a/src/query/ast/tests/it/testdata/script.txt +++ b/src/query/ast/tests/it/testdata/script.txt @@ -41,11 +41,11 @@ LET t1 RESULTSET := SELECT * FROM numbers(100) ---------- Output --------- LET t1 RESULTSET := SELECT * FROM numbers(100) ---------- AST ------------ -LetQuery { +LetStatement { span: Some( 0..46, ), - declare: QueryDeclare { + declare: StatementDeclare { name: Identifier { span: Some( 4..6, @@ -54,70 +54,72 @@ LetQuery { quote: None, is_hole: false, }, - query: Query { - span: Some( - 20..46, - ), - with: None, - body: Select( - SelectStmt { - span: Some( - 20..46, - ), - hints: None, - distinct: false, - select_list: [ - StarColumns { - qualified: [ - Star( - Some( - 27..28, + stmt: Query( + Query { + span: Some( + 20..46, + ), + with: None, + body: Select( + SelectStmt { + span: Some( + 20..46, + ), + hints: None, + distinct: false, + select_list: [ + StarColumns { + qualified: [ + Star( + Some( + 27..28, + ), ), - ), - ], - column_filter: None, - }, - ], - from: [ - TableFunction { - span: Some( - 34..46, - ), - lateral: false, - name: Identifier { + ], + column_filter: None, + }, + ], + from: [ + TableFunction { span: Some( - 34..41, + 34..46, ), - name: "numbers", - quote: None, - is_hole: false, - }, - params: [ - Literal { + lateral: false, + name: Identifier { span: Some( - 42..45, - ), - lit: UInt64( - 100, + 34..41, ), + name: "numbers", + quote: None, + is_hole: false, }, - ], - named_params: [], - alias: None, - }, - ], - selection: None, - group_by: None, - having: None, - window_list: None, - qualify: None, - }, - ), - order_by: [], - limit: [], - offset: None, - ignore_result: false, - }, + params: [ + Literal { + span: Some( + 42..45, + ), + lit: UInt64( + 100, + ), + }, + ], + named_params: [], + alias: None, + }, + ], + selection: None, + group_by: None, + having: None, + window_list: None, + qualify: None, + }, + ), + order_by: [], + limit: [], + offset: None, + ignore_result: false, + }, + ), }, } @@ -1312,7 +1314,7 @@ Loop { 0..52, ), body: [ - SQLStatement { + RunStatement { span: Some( 9..42, ), @@ -1455,7 +1457,7 @@ select :a + 1 ---------- Output --------- SELECT (:a + 1) ---------- AST ------------ -SQLStatement { +RunStatement { span: Some( 0..13, ), @@ -1519,7 +1521,7 @@ select IDENTIFIER(:b) ---------- Output --------- SELECT IDENTIFIER(:b) ---------- AST ------------ -SQLStatement { +RunStatement { span: Some( 0..21, ), @@ -1582,7 +1584,7 @@ select a.IDENTIFIER(:b).c + minus(:d) ---------- Output --------- SELECT (a.IDENTIFIER(:b).c + minus(:d)) ---------- AST ------------ -SQLStatement { +RunStatement { span: Some( 0..37, ), diff --git a/src/query/script/Cargo.toml b/src/query/script/Cargo.toml new file mode 100644 index 0000000000000..e795429a6e491 --- /dev/null +++ b/src/query/script/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "databend-common-script" +version = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +publish = { workspace = true } +edition = { workspace = true } + +[lib] +doctest = false + +[dependencies] # In alphabetical order +# Workspace dependencies +databend-common-ast = { path = "../ast" } +databend-common-exception = { path = "../../common/exception" } + +# Crates.io dependencies +derive-visitor = { workspace = true } +enum-as-inner = "0.5.1" +ethnum = { workspace = true } +fast-float = "0.2.0" +indent = "0.1.1" +itertools = { workspace = true } +logos = "0.12.1" +minitrace = { workspace = true } +nom = "7.1.1" +nom-rule = "0.3.0" +ordered-float = { workspace = true } +pratt = "0.4.0" +pretty = "0.11.3" +serde = { workspace = true } +strsim = "0.10" +strum = "0.24" +strum_macros = "0.24" +url = "2.3.1" + +[dev-dependencies] +goldenfile = "1.4" +unindent = "0.2.3" diff --git a/src/query/script/src/lib.rs b/src/query/script/src/lib.rs new file mode 100644 index 0000000000000..2a31c98161d48 --- /dev/null +++ b/src/query/script/src/lib.rs @@ -0,0 +1,1411 @@ +// Copyright 2021 Datafuse Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![allow(unreachable_code)] + +use std::collections::HashMap; +use std::fmt; +use std::fmt::Display; +use std::vec; + +use databend_common_ast::ast::ColumnID; +use databend_common_ast::ast::ColumnRef; +use databend_common_ast::ast::Expr; +use databend_common_ast::ast::FunctionCall; +use databend_common_ast::ast::Identifier; +use databend_common_ast::ast::Indirection; +use databend_common_ast::ast::Literal; +use databend_common_ast::ast::Query; +use databend_common_ast::ast::ScriptStatement; +use databend_common_ast::ast::SelectStmt; +use databend_common_ast::ast::SelectTarget; +use databend_common_ast::ast::SetExpr; +use databend_common_ast::ast::Statement; +use databend_common_ast::ast::TableReference; +use databend_common_ast::ast::UnaryOperator; +use databend_common_exception::ErrorCode; +use databend_common_exception::Result; +use databend_common_exception::Span; +use derive_visitor::DriveMut; +use derive_visitor::VisitorMut; + +#[derive(Debug, Clone)] +pub enum ScriptIR { + Query { + stmt: StatementTemplate, + to_set: SetRef, + }, + Iter { + set: SetRef, + to_iter: IterRef, + }, + Read { + iter: IterRef, + column: ColumnAccess, + to_var: VarRef, + }, + Next { + iter: IterRef, + }, + Label { + label: LabelRef, + }, + JumpIfEnded { + iter: IterRef, + to_label: LabelRef, + }, + JumpIfTrue { + condition: VarRef, + to_label: LabelRef, + }, + Goto { + to_label: LabelRef, + }, + Return, + ReturnVar { + var: VarRef, + }, + ReturnSet { + set: SetRef, + }, +} + +impl Display for ScriptIR { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ScriptIR::Query { + stmt: query, + to_set, + } => write!(f, "QUERY {query}, {to_set}")?, + ScriptIR::Iter { set, to_iter } => write!(f, "ITER {set}, {to_iter}")?, + ScriptIR::Read { + iter, + column, + to_var, + } => write!(f, "READ {iter}, {column}, {to_var}")?, + ScriptIR::Next { iter } => { + write!(f, "NEXT {iter}")?; + } + ScriptIR::Label { label } => write!(f, "{label}:")?, + ScriptIR::JumpIfEnded { iter, to_label } => { + write!(f, "JUMP_IF_ENDED {iter}, {to_label}")? + } + ScriptIR::JumpIfTrue { + condition, + to_label, + } => write!(f, "JUMP_IF_TRUE {condition}, {to_label}")?, + ScriptIR::Goto { to_label } => write!(f, "GOTO {to_label}")?, + ScriptIR::Return => write!(f, "RETURN")?, + ScriptIR::ReturnVar { var } => write!(f, "RETURN {var}")?, + ScriptIR::ReturnSet { set } => write!(f, "RETURN {set}")?, + }; + Ok(()) + } +} + +pub type VarRef = Ref<0>; +pub type SetRef = Ref<1>; +pub type IterRef = Ref<2>; +pub type LabelRef = Ref<3>; + +#[derive(Debug, Clone)] +pub struct Ref { + pub span: Span, + pub index: usize, + pub display_name: String, +} + +impl Display for Ref { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}({})", self.display_name, self.index) + } +} + +#[derive(Default)] +struct RefAllocator { + next_index: usize, +} + +impl Ref { + fn new(span: Span, name: &str, allocator: &mut RefAllocator) -> Self { + let index = allocator.next_index; + allocator.next_index += 1; + Ref { + span, + index, + display_name: format!("{name}"), + } + } + + fn new_interal(span: Span, hint: &str, allocator: &mut RefAllocator) -> Self { + let index = allocator.next_index; + allocator.next_index += 1; + Ref { + span, + index, + display_name: format!("__{hint}{index}"), + } + } +} + +#[derive(Debug, Clone)] +pub enum ColumnAccess { + Position(usize), + Name(String), +} + +impl Display for ColumnAccess { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ColumnAccess::Position(index) => write!(f, "${}", index), + ColumnAccess::Name(name) => write!(f, "\"{}\"", name), + } + } +} + +#[derive(Debug, Clone)] +pub struct StatementTemplate { + pub span: Span, + pub stmt: Statement, +} + +impl StatementTemplate { + pub fn build_statement( + span: Span, + stmt: &Statement, + lookup_var: impl Fn(&Identifier) -> Result, + ) -> Result { + #[derive(VisitorMut)] + #[visitor(Expr(enter), Identifier(enter))] + struct TemplateVisitor<'a> { + lookup_var: &'a dyn Fn(&Identifier) -> Result, + error: Option, + } + + impl TemplateVisitor<'_> { + fn enter_expr(&mut self, expr: &mut Expr) { + match expr { + Expr::Hole { span, name } => { + let index = (self.lookup_var)(&Identifier::from_name(*span, name.clone())); + match index { + Ok(index) => { + *expr = Expr::Hole { + span: *span, + name: index.to_string(), + }; + } + Err(e) => { + self.error = Some(e.set_span(*span)); + } + } + } + _ => {} + } + } + + fn enter_identifier(&mut self, ident: &mut Identifier) { + if ident.is_hole { + let index = (self.lookup_var)(&ident); + match index { + Ok(index) => { + *ident = Identifier::from_name(ident.span, index.to_string()); + ident.is_hole = true; + } + Err(e) => { + self.error = Some(e.set_span(ident.span)); + } + } + } + } + } + + let mut stmt = stmt.clone(); + let mut visitor = TemplateVisitor { + lookup_var: &lookup_var, + error: None, + }; + stmt.drive_mut(&mut visitor); + + if let Some(e) = visitor.error { + return Err(e); + } + + Ok(StatementTemplate { span, stmt }) + } + + pub fn build_expr( + expr: &Expr, + common: &mut T, + lookup_var: impl Fn(&mut T, &Identifier) -> Result, + mut read_iter: impl FnMut(&mut T, &Identifier, &Identifier) -> Result, + ) -> Result { + #[derive(VisitorMut)] + #[visitor(Expr(enter), Identifier(enter))] + struct TemplateVisitor<'a, T> { + common: &'a mut T, + lookup_var: &'a dyn Fn(&mut T, &Identifier) -> Result, + read_iter: &'a mut dyn FnMut(&mut T, &Identifier, &Identifier) -> Result, + error: Option, + } + + impl TemplateVisitor<'_, T> { + fn enter_expr(&mut self, expr: &mut Expr) { + match expr { + Expr::ColumnRef { + span, + column: + ColumnRef { + database: None, + table: None, + column: ColumnID::Name(column), + }, + } => { + let index = (self.lookup_var)(self.common, column); + match index { + Ok(index) => { + *expr = Expr::Hole { + span: *span, + name: index.to_string(), + }; + } + Err(e) => { + self.error = Some(e.set_span(*span)); + } + } + } + Expr::ColumnRef { + span, + column: + ColumnRef { + database: None, + table: Some(iter), + column: ColumnID::Name(column), + }, + } => { + let index = (self.read_iter)(self.common, iter, column); + match index { + Ok(index) => { + *expr = Expr::Hole { + span: *span, + name: index.to_string(), + }; + } + Err(e) => { + self.error = Some(e.set_span(*span)); + } + } + } + Expr::Hole { span, .. } => { + self.error = Some( + ErrorCode::ScriptSematicError(format!( + "variable doesn't need to be quoted in this context, try removing the colon" + )) + .set_span(*span), + ); + } + _ => {} + } + } + + fn enter_identifier(&mut self, ident: &mut Identifier) { + if ident.is_hole { + self.error = Some( + ErrorCode::ScriptSematicError(format!( + "variable is not allowed in this context" + )) + .set_span(ident.span), + ); + } + } + } + + let mut expr = expr.clone(); + let mut visitor = TemplateVisitor { + common, + lookup_var: &lookup_var, + read_iter: &mut read_iter, + error: None, + }; + expr.drive_mut(&mut visitor); + + if let Some(e) = visitor.error { + return Err(e); + } + + let select_stmt = Statement::Query(Box::new(Query { + span: expr.span(), + with: None, + body: SetExpr::Select(Box::new(SelectStmt { + span: expr.span(), + hints: None, + distinct: false, + select_list: vec![SelectTarget::AliasedExpr { + expr: Box::new(expr.clone()), + alias: None, + }], + from: vec![], + selection: None, + group_by: None, + having: None, + window_list: None, + qualify: None, + })), + order_by: vec![], + limit: vec![], + offset: None, + ignore_result: false, + })); + + Ok(StatementTemplate { + span: expr.span(), + stmt: select_stmt, + }) + } + + pub fn subst(&self, lookup_var: impl Fn(usize) -> Result) -> Result { + #[derive(VisitorMut)] + #[visitor(Expr(enter), Identifier(enter))] + struct SubstVisitor<'a> { + lookup_var: &'a dyn Fn(usize) -> Result, + error: Option, + } + + impl SubstVisitor<'_> { + fn enter_expr(&mut self, expr: &mut Expr) { + match expr { + Expr::Hole { span, name } => { + let index = name.parse::().unwrap(); + let value = (self.lookup_var)(index); + match value { + Ok(value) => { + *expr = Expr::Literal { + span: *span, + lit: value, + }; + } + Err(e) => { + self.error = Some(e.set_span(*span)); + } + } + } + _ => {} + } + } + + fn enter_identifier(&mut self, ident: &mut Identifier) { + if ident.is_hole { + let index = ident.name.parse::().unwrap(); + let value = (self.lookup_var)(index); + match value { + Ok(Literal::String(name)) => { + *ident = Identifier::from_name(ident.span, name); + } + Ok(value) => { + self.error = Some( + ErrorCode::ScriptSematicError(format!( + "expected string literal, got {value}" + )) + .set_span(ident.span), + ); + } + Err(e) => { + self.error = Some(e.set_span(ident.span)); + } + } + } + } + } + + let mut stmt = self.stmt.clone(); + let mut visitor = SubstVisitor { + lookup_var: &lookup_var, + error: None, + }; + stmt.drive_mut(&mut visitor); + + if let Some(e) = visitor.error { + return Err(e); + } + + Ok(stmt) + } +} + +impl Display for StatementTemplate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.stmt) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +struct RefName(String); + +impl Display for RefName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +#[derive(Default)] +struct Scope { + items: HashMap, + anonymous_items: Vec, +} + +#[derive(Debug, Clone)] +enum RefItem { + Var(VarRef), + Set(SetRef), + Iter(IterRef), + Loop { + continue_label: LabelRef, + break_label: LabelRef, + }, +} + +impl RefItem { + pub fn is_same_kind(&self, other: &RefItem) -> bool { + match (self, other) { + (RefItem::Var(_), RefItem::Var(_)) => true, + (RefItem::Set(_), RefItem::Set(_)) => true, + (RefItem::Loop { .. }, RefItem::Loop { .. }) => true, + _ => false, + } + } +} + +pub fn compile(code: &[ScriptStatement]) -> Result> { + let mut compiler = Compiler::new(); + compiler.compile(code) +} + +struct Compiler { + ref_allocator: RefAllocator, + scopes: Vec, +} + +impl Compiler { + pub fn new() -> Compiler { + Compiler { + ref_allocator: RefAllocator::default(), + scopes: vec![Scope::default()], + } + } + + pub fn compile(&mut self, code: &[ScriptStatement]) -> Result> { + let mut output = vec![]; + + for line in code { + match line { + ScriptStatement::LetVar { declare, .. } => { + let to_var = self.declare_var(&declare.name)?; + output.append(&mut self.compile_expr(&declare.default, to_var)?); + } + ScriptStatement::LetStatement { span, declare } => { + // QUERY , to_set + let stmt = self.build_sql_statement(*span, &declare.stmt)?; + let to_set = self.declare_set(&declare.name)?; + output.push(ScriptIR::Query { stmt, to_set }); + } + ScriptStatement::RunStatement { span, stmt } => { + // QUERY , unused_result + let stmt = self.build_sql_statement(*span, stmt)?; + let to_set = self.declare_anonymous_set(*span, "unused_result")?; + output.push(ScriptIR::Query { stmt, to_set }); + } + ScriptStatement::Assign { name, value, .. } => { + let to_var = self.lookup_var(&name)?; + output.append(&mut self.compile_expr(value, to_var)?); + } + ScriptStatement::Return { value: None, .. } => { + output.push(ScriptIR::Return); + } + ScriptStatement::Return { + value: Some(value), .. + } => { + // TODO: support returning table + let to_var = self.declare_anonymous_var(value.span(), "return_val")?; + output.append(&mut self.compile_expr(value, to_var.clone())?); + output.push(ScriptIR::ReturnVar { var: to_var }); + } + ScriptStatement::ForLoop { + span, + variable, + is_reverse, + lower_bound, + upper_bound, + body, + label, + } => { + output.append(&mut self.compile_for_loop( + *span, + variable, + *is_reverse, + lower_bound, + upper_bound, + body, + label, + )?); + } + ScriptStatement::ForIn { + span, + variable, + resultset, + body, + label, + } => { + output + .append(&mut self.compile_for_in(*span, variable, resultset, body, label)?); + } + ScriptStatement::WhileLoop { + span, + condition, + body, + label, + } => { + output.append(&mut self.compile_while_loop(*span, condition, body, label)?); + } + ScriptStatement::RepeatLoop { + span, + body, + until_condition, + label, + } => { + output.append(&mut self.compile_repeat_loop( + *span, + until_condition, + body, + label, + )?); + } + ScriptStatement::Loop { span, body, label } => { + output.append(&mut self.compile_loop(*span, body, label)?); + } + ScriptStatement::Break { + label: Some(label), .. + } => { + let (_, break_label) = self.lookup_loop(label)?; + output.push(ScriptIR::Goto { + to_label: break_label, + }); + } + ScriptStatement::Break { span, label: None } => { + let (_, break_label) = self.current_loop(*span)?; + output.push(ScriptIR::Goto { + to_label: break_label, + }); + } + ScriptStatement::Continue { + label: Some(label), .. + } => { + let (continue_label, _) = self.lookup_loop(label)?; + output.push(ScriptIR::Goto { + to_label: continue_label, + }); + } + ScriptStatement::Continue { span, label: None } => { + let (continue_label, _) = self.current_loop(*span)?; + output.push(ScriptIR::Goto { + to_label: continue_label, + }); + } + ScriptStatement::If { + span, + conditions, + results, + else_result, + } => { + output.append(&mut self.compile_if(*span, conditions, results, else_result)?); + } + ScriptStatement::Case { span, operand, conditions, results, else_result } => todo!(), + } + } + + Ok(output) + } + + fn compile_expr(&mut self, expr: &Expr, to_var: VarRef) -> Result> { + let mut output = vec![]; + + let stmt = StatementTemplate::build_expr( + expr, + self, + |this, hole| { + let var = this.lookup_var(hole)?; + Ok(var.index) + }, + |this, iter, column| { + // READ , , to_var + let to_var = + this.declare_anonymous_var(column.span, &format!("{iter}.{column}"))?; + let iter = this.lookup_iter(iter)?; + let column = ColumnAccess::Name(this.normalize_ident(column).0); + output.push(ScriptIR::Read { + iter, + column, + to_var: to_var.clone(), + }); + + Ok(to_var) + }, + )?; + + // QUERY 'SELECT ', expr_result + let set_ref = self.declare_anonymous_set(expr.span(), "expr_result")?; + output.push(ScriptIR::Query { + stmt, + to_set: set_ref.clone(), + }); + + // ITER expr_result, expr_result_iter + let iter_ref = self.declare_anonymous_iter(expr.span(), "expr_result_iter")?; + output.push(ScriptIR::Iter { + set: set_ref, + to_iter: iter_ref.clone(), + }); + + // READ expr_result_iter, $0, to_var + output.push(ScriptIR::Read { + iter: iter_ref, + column: ColumnAccess::Position(0), + to_var, + }); + + Ok(output) + } + + fn compile_for_loop( + &mut self, + span: Span, + variable: &Identifier, + is_reverse: bool, + lower_bound: &Expr, + upper_bound: &Expr, + body: &[ScriptStatement], + label: &Option, + ) -> Result> { + let mut output = vec![]; + + self.push_scope(); + + let (continue_label, break_label) = match label { + Some(label) => self.declare_loop(label)?, + None => self.declare_anonymous_loop(span)?, + }; + + // QUERY 'SELECT * FROM generate_series(, , )', for_index_set + let (start, end, step) = if is_reverse { + (upper_bound, lower_bound, -1) + } else { + (lower_bound, upper_bound, 1) + }; + let select_stmt = Statement::Query(Box::new(Query { + span, + with: None, + body: SetExpr::Select(Box::new(SelectStmt { + span, + hints: None, + distinct: false, + select_list: vec![SelectTarget::StarColumns { + qualified: vec![ + Indirection::Star(None), + ], + column_filter: None, + }], + from: vec![TableReference::TableFunction { + span, + lateral: false, + name: Identifier::from_name(span, "generate_series"), + params: vec![start.clone(), end.clone(), Expr::Literal { + span, + lit: Literal::Decimal256 { + value: step.into(), + precision: 1, + scale: 0, + }, + }], + named_params: vec![], + alias: None, + }], + selection: None, + group_by: None, + having: None, + window_list: None, + qualify: None, + })), + order_by: vec![], + limit: vec![], + offset: None, + ignore_result: false, + })); + let stmt = self.build_sql_statement(span, &select_stmt)?; + let to_set = self.declare_anonymous_set(span, "for_index_set")?; + output.push(ScriptIR::Query { + stmt, + to_set: to_set.clone(), + }); + + // ITER for_index_set, for_index_iter + let iter = self.declare_anonymous_iter(span, "for_index_iter")?; + output.push(ScriptIR::Iter { + set: to_set, + to_iter: iter.clone(), + }); + + // Label LOOP + output.push(ScriptIR::Label { + label: continue_label.clone(), + }); + + // JUMP_IF_ENDED for_index_iter, LOOP_END + output.push(ScriptIR::JumpIfEnded { + iter: iter.clone(), + to_label: break_label.clone(), + }); + + // READ for_index_iter, $0, variable + let variable = self.declare_var(variable)?; + output.push(ScriptIR::Read { + iter: iter.clone(), + column: ColumnAccess::Position(0), + to_var: variable.clone(), + }); + + // + output.append(&mut self.compile(body)?); + + // NEXT for_index_iter + output.push(ScriptIR::Next { iter: iter.clone() }); + + // GOTO LOOP + output.push(ScriptIR::Goto { + to_label: continue_label.clone(), + }); + + // Label LOOP_END + output.push(ScriptIR::Label { label: break_label }); + + self.pop_scope(); + + Ok(output) + } + + fn compile_for_in( + &mut self, + span: Span, + variable: &Identifier, + resultset: &Identifier, + body: &[ScriptStatement], + label: &Option, + ) -> Result> { + let mut output = vec![]; + + self.push_scope(); + + let (continue_label, break_label) = match label { + Some(label) => self.declare_loop(label)?, + None => self.declare_anonymous_loop(span)?, + }; + + // ITER resultset, for_iter + let set = self.lookup_set(resultset)?; + let iter = self.declare_iter(span, variable)?; + output.push(ScriptIR::Iter { + set, + to_iter: iter.clone(), + }); + + // Label LOOP + output.push(ScriptIR::Label { + label: continue_label.clone(), + }); + + // JUMP_IF_ENDED for_iter, LOOP_END + output.push(ScriptIR::JumpIfEnded { + iter: iter.clone(), + to_label: break_label.clone(), + }); + + // + output.append(&mut self.compile(body)?); + + // NEXT for_iter + output.push(ScriptIR::Next { iter: iter.clone() }); + + // GOTO LOOP + output.push(ScriptIR::Goto { + to_label: continue_label.clone(), + }); + + // Label LOOP_END + output.push(ScriptIR::Label { label: break_label }); + + self.pop_scope(); + + Ok(output) + } + + fn compile_while_loop( + &mut self, + span: Span, + condition: &Expr, + body: &[ScriptStatement], + label: &Option, + ) -> Result> { + let mut output = vec![]; + + self.push_scope(); + + let (continue_label, break_label) = match label { + Some(label) => self.declare_loop(label)?, + None => self.declare_anonymous_loop(span)?, + }; + + // Label LOOP + output.push(ScriptIR::Label { + label: continue_label.clone(), + }); + + // )> + // JUMP_IF_TRUE break_condition, LOOP_END + let break_condition = wrap_not(wrap_is_true(condition.clone())); + let break_condition_var = + self.declare_anonymous_var(break_condition.span(), "break_condition")?; + output.append(&mut self.compile_expr(&break_condition, break_condition_var.clone())?); + output.push(ScriptIR::JumpIfTrue { + condition: break_condition_var, + to_label: break_label.clone(), + }); + + // + output.append(&mut self.compile(&body)?); + + // GOTO LOOP + output.push(ScriptIR::Goto { + to_label: continue_label.clone(), + }); + + // Label LOOP_END + output.push(ScriptIR::Label { label: break_label }); + + self.pop_scope(); + + Ok(output) + } + + fn compile_repeat_loop( + &mut self, + span: Span, + until_condition: &Expr, + body: &[ScriptStatement], + label: &Option, + ) -> Result> { + let mut output = vec![]; + + self.push_scope(); + + let (continue_label, break_label) = match label { + Some(label) => self.declare_loop(label)?, + None => self.declare_anonymous_loop(span)?, + }; + + // Label LOOP + output.push(ScriptIR::Label { + label: continue_label.clone(), + }); + + // + output.append(&mut self.compile(&body)?); + + // + // JUMP_IF_TRUE break_condition, LOOP_END + let break_condition = wrap_is_true(until_condition.clone()); + let break_condition_var = + self.declare_anonymous_var(break_condition.span(), "break_condition")?; + output.append(&mut self.compile_expr(&break_condition, break_condition_var.clone())?); + output.push(ScriptIR::JumpIfTrue { + condition: break_condition_var, + to_label: break_label.clone(), + }); + + // GOTO LOOP + output.push(ScriptIR::Goto { + to_label: continue_label.clone(), + }); + + // Label LOOP_END + output.push(ScriptIR::Label { label: break_label }); + + self.pop_scope(); + + Ok(output) + } + + fn compile_loop( + &mut self, + span: Span, + body: &[ScriptStatement], + label: &Option, + ) -> Result> { + let mut output = vec![]; + + self.push_scope(); + + let (continue_label, break_label) = match label { + Some(label) => self.declare_loop(label)?, + None => self.declare_anonymous_loop(span)?, + }; + + // Label LOOP + output.push(ScriptIR::Label { + label: continue_label.clone(), + }); + + // + output.append(&mut self.compile(&body)?); + + // GOTO LOOP + output.push(ScriptIR::Goto { + to_label: continue_label.clone(), + }); + + // Label LOOP_END + output.push(ScriptIR::Label { label: break_label }); + + self.pop_scope(); + + Ok(output) + } + + fn compile_if( + &mut self, + span: Span, + conditions: &[Expr], + results: &Vec>, + else_result: &Option>, + ) -> Result> { + let mut output = vec![]; + + let then_labels = conditions + .iter() + .map(|condition| { + LabelRef::new_interal(condition.span(), "IF_THEN", &mut self.ref_allocator) + }) + .collect::>(); + let end_label = LabelRef::new_interal(span, "IF_END", &mut self.ref_allocator); + + for (condition, then_label) in conditions.iter().zip(&then_labels) { + // + // JUMP_IF_TRUE condition, IF_THEN + let condition = wrap_is_true(condition.clone()); + let condition_var = self.declare_anonymous_var(condition.span(), "condition")?; + output.append(&mut self.compile_expr(&condition, condition_var.clone())?); + output.push(ScriptIR::JumpIfTrue { + condition: condition_var, + to_label: then_label.clone(), + }); + } + + if let Some(else_result) = else_result { + // + self.push_scope(); + output.append(&mut self.compile(&else_result)?); + self.pop_scope(); + } + + // GOTO IF_END + output.push(ScriptIR::Goto { + to_label: end_label.clone(), + }); + + for (result, then_label) in results.iter().zip(&then_labels) { + // Label IF_THEN + output.push(ScriptIR::Label { + label: then_label.clone(), + }); + + // + self.push_scope(); + output.append(&mut self.compile(&result)?); + self.pop_scope(); + + // GOTO IF_END + output.push(ScriptIR::Goto { + to_label: end_label.clone(), + }); + } + + // Label IF_END + output.push(ScriptIR::Label { + label: end_label.clone(), + }); + + Ok(output) + } + + fn build_sql_statement(&self, span: Span, stmt: &Statement) -> Result { + StatementTemplate::build_statement(span, stmt, |hole| { + let var = self.lookup_var(hole)?; + Ok(var.index) + }) + } + + fn push_scope(&mut self) { + self.scopes.push(Scope::default()); + } + + fn pop_scope(&mut self) { + self.scopes.pop().unwrap(); + } + + fn normalize_ident(&self, ident: &Identifier) -> RefName { + // todo!() + RefName(ident.name.clone()) + } + + fn declare_ref(&mut self, ident: &Identifier, item: RefItem) -> Result<()> { + let name = self.normalize_ident(ident); + for scope in self.scopes.iter().rev() { + if let Some(shadowed) = scope.items.get(&name) { + if !shadowed.is_same_kind(&item) { + return Err(ErrorCode::ScriptSematicError(format!( + "`{name}` is already defined as a different kind of variable" + )) + .set_span(ident.span)); + } + break; + } + } + self.scopes.last_mut().unwrap().items.insert(name, item); + Ok(()) + } + + fn declare_anonymous_ref(&mut self, item: RefItem) -> Result<()> { + self.scopes.last_mut().unwrap().anonymous_items.push(item); + Ok(()) + } + + fn declare_var(&mut self, ident: &Identifier) -> Result { + let name = self.normalize_ident(ident); + let var = VarRef::new(ident.span, &name.0, &mut self.ref_allocator); + self.declare_ref(ident, RefItem::Var(var.clone()))?; + Ok(var) + } + + fn declare_anonymous_var(&mut self, span: Span, hint: &str) -> Result { + let var = VarRef::new_interal(span, hint, &mut self.ref_allocator); + self.declare_anonymous_ref(RefItem::Var(var.clone()))?; + Ok(var) + } + + fn declare_set(&mut self, ident: &Identifier) -> Result { + let name = self.normalize_ident(ident); + let set = SetRef::new(ident.span, &name.0, &mut self.ref_allocator); + self.declare_ref(ident, RefItem::Set(set.clone()))?; + Ok(set) + } + + fn declare_anonymous_set(&mut self, span: Span, hint: &str) -> Result { + let set = SetRef::new_interal(span, hint, &mut self.ref_allocator); + self.declare_anonymous_ref(RefItem::Set(set.clone()))?; + Ok(set) + } + + fn declare_iter(&mut self, span: Span, ident: &Identifier) -> Result { + let name = self.normalize_ident(ident); + let iter = IterRef::new(span, &name.0, &mut self.ref_allocator); + self.declare_ref(ident, RefItem::Iter(iter.clone()))?; + Ok(iter) + } + + fn declare_anonymous_iter(&mut self, span: Span, hint: &str) -> Result { + let iter = IterRef::new_interal(span, hint, &mut self.ref_allocator); + self.declare_anonymous_ref(RefItem::Iter(iter.clone()))?; + Ok(iter) + } + + fn declare_loop(&mut self, ident: &Identifier) -> Result<(LabelRef, LabelRef)> { + let name = self.normalize_ident(ident); + let continue_label = LabelRef::new( + ident.span, + &format!("{}_LOOP", &name.0), + &mut self.ref_allocator, + ); + let break_label = LabelRef::new( + ident.span, + &format!("{}_LOOP_END", &name.0), + &mut self.ref_allocator, + ); + self.declare_ref(ident, RefItem::Loop { + continue_label: continue_label.clone(), + break_label: break_label.clone(), + })?; + Ok((continue_label, break_label)) + } + + fn declare_anonymous_loop(&mut self, span: Span) -> Result<(LabelRef, LabelRef)> { + let continue_label = LabelRef::new_interal(span, "LOOP", &mut self.ref_allocator); + let break_label = LabelRef::new_interal(span, "LOOP_END", &mut self.ref_allocator); + self.declare_anonymous_ref(RefItem::Loop { + continue_label: continue_label.clone(), + break_label: break_label.clone(), + })?; + Ok((continue_label, break_label)) + } + + fn lookup_ref(&self, ident: &Identifier) -> Result { + let name = self.normalize_ident(ident); + for scope in self.scopes.iter().rev() { + if let Some(item) = scope.items.get(&name) { + return Ok(item.clone()); + } + } + Err( + ErrorCode::ScriptSematicError(format!("`{name}` is not defined")) + .set_span(ident.span), + ) + } + + fn lookup_var(&self, ident: &Identifier) -> Result { + let RefItem::Var(var) = self.lookup_ref(ident)? else { + let name = self.normalize_ident(ident); + return Err( + ErrorCode::ScriptSematicError(format!("`{name}` is not a scalar variable")) + .set_span(ident.span), + ); + }; + Ok(var) + } + + fn lookup_set(&self, ident: &Identifier) -> Result { + let RefItem::Set(set) = self.lookup_ref(ident)? else { + let name = self.normalize_ident(ident); + return Err( + ErrorCode::ScriptSematicError(format!("`{name}` is not a set")) + .set_span(ident.span), + ); + }; + Ok(set) + } + + fn lookup_iter(&self, ident: &Identifier) -> Result { + let RefItem::Iter(iter) = self.lookup_ref(ident)? else { + let name = self.normalize_ident(ident); + return Err( + ErrorCode::ScriptSematicError(format!("`{name}` is not a row variable")) + .set_span(ident.span), + ); + }; + Ok(iter) + } + + fn lookup_loop(&self, ident: &Identifier) -> Result<(LabelRef, LabelRef)> { + let RefItem::Loop { + continue_label, + break_label, + } = self.lookup_ref(ident)? + else { + let name = self.normalize_ident(ident); + return Err( + ErrorCode::ScriptSematicError(format!("`{name}` is not a loop")) + .set_span(ident.span), + ); + }; + Ok((continue_label, break_label)) + } + + fn current_loop(&self, span: Span) -> Result<(LabelRef, LabelRef)> { + for scope in self.scopes.iter().rev() { + for item in scope.anonymous_items.iter().chain(scope.items.values()) { + if let RefItem::Loop { + continue_label, + break_label, + } = item + { + return Ok((continue_label.clone(), break_label.clone())); + } + } + } + Err(ErrorCode::ScriptSematicError(format!("not in a loop")).set_span(span)) + } +} + +fn wrap_not(expr: Expr) -> Expr { + Expr::UnaryOp { + span: expr.span(), + op: UnaryOperator::Not, + expr: Box::new(expr), + } +} + +fn wrap_is_true(expr: Expr) -> Expr { + Expr::FunctionCall { + span: expr.span(), + func: FunctionCall { + distinct: false, + name: Identifier::from_name(expr.span(), "is_true"), + args: vec![expr], + params: vec![], + window: None, + lambda: None, + }, + } +} + +// /// Replace column references with holes. +// /// +// /// For example, `a + 1 = b` will be transformed to `:a + 1 = :b`. +// fn quote_expr(expr: &Expr) -> Result { +// #[derive(VisitorMut)] +// #[visitor(Expr(enter), Identifier(enter))] +// struct QuoteVisitor { +// error: Option, +// } + +// impl QuoteVisitor { +// fn enter_expr(&mut self, expr: &mut Expr) { +// match expr { +// Expr::ColumnRef { +// span, +// column: +// ColumnRef { +// database: None, +// table: None, +// column: ColumnID::Name(column), +// }, +// } => { +// *expr = Expr::Hole { +// span: *span, +// name: column.name.clone(), +// } +// } +// Expr::Hole { span, .. } => { +// self.error = Some( +// ErrorCode::ScriptSematicError(format!( +// "variable doesn't need to be quoted in this context, try removing the colon" +// )) +// .set_span(*span), +// ); +// } +// _ => {} +// } +// } + +// fn enter_identifier(&mut self, ident: &mut Identifier) { +// if ident.is_hole { +// self.error = Some( +// ErrorCode::ScriptSematicError(format!( +// "variable is not allowed in this context" +// )) +// .set_span(ident.span), +// ); +// } +// } +// } + +// let mut expr = expr.clone(); +// let mut visitor = QuoteVisitor { error: None }; +// expr.drive_mut(&mut visitor); + +// match visitor.error { +// Some(e) => Err(e), +// None => Ok(expr), +// } +// } + +// pub struct Executor { +// pub code: Vec, +// pub func_ctx: FunctionContext, +// pub variables: DataBlock, +// pub result_sets: HashMap, +// pub label_to_pc: HashMap, +// pub return_value: ReturnValue, +// pub pc: usize, +// } + +// impl Executor { +// fn load(code: Vec, func_ctx: FunctionContext) -> Executor { +// let mut max_variable_index = 0; +// let mut labels = HashMap::new(); + +// for (pc, ir) in code.iter().enumerate() { +// match ir { +// ScriptLIR::Evaluate { to, .. } => { +// max_variable_index = max_variable_index.max(to.index); +// } +// ScriptLIR::GetResult { to, .. } => { +// max_variable_index = max_variable_index.max(to.index); +// } +// ScriptLIR::Label { label } => { +// labels.insert(label.index, pc); +// } +// _ => {} +// } +// } + +// Executor { +// code, +// func_ctx, +// variables: vec![Scalar::Null; max_variable_index + 1], +// result_sets: HashMap::new(), +// label_to_pc: labels, +// return_value: ReturnValue::None, +// pc: 0, +// } +// } + +// pub fn run(mut self, code: Vec, func_ctx: FunctionContext) -> Result { +// let mut executor = Executor::load(code, func_ctx); +// while self.pc < self.code.len() { +// self.step()?; +// } +// Ok(self.return_value) +// } + +// fn step(&mut self) -> Result<()> { +// let ir = &self.code.get(self.pc).ok_or_else(|| { +// ErrorCode::ScriptMemoryViolation(format!("pc: {} is out of range", self.pc)) +// })?; + +// // match ir { +// // ScriptLIR::Eval { expr, to } => { +// // let expr = expr.as_expr(); +// // let mut evaluator = Evaluator::new(todo!(), &self.func_ctx, &BUILTIN_FUNCTIONS); +// // evaluator.run(&expr)?; +// // } +// // ScriptLIR::RunQuery { query, to } => todo!(), +// // ScriptLIR::DropQuery { result_set } => { +// // self.result_sets.remove(&result_set.index); +// // } +// // ScriptLIR::GetResult { +// // result_set, +// // row, +// // col, +// // to, +// // } => todo!(), +// // ScriptLIR::Label { label } => {} +// // ScriptLIR::Goto { label } => { +// // self.pc = self.labels.get(&label.index).ok_or_else(|| { +// // ErrorCode::ScriptMemoryViolation(format!("label: {} is not found", label)) +// // })?; +// // } +// // ScriptLIR::JumpIf { condition, label } => { + +// // } +// // _ => todo!(), +// // } + +// Ok(()) +// } +// } diff --git a/src/query/script/tests/it/compiler.rs b/src/query/script/tests/it/compiler.rs new file mode 100644 index 0000000000000..e44b8e53d56c5 --- /dev/null +++ b/src/query/script/tests/it/compiler.rs @@ -0,0 +1,195 @@ +// Copyright 2022 Datafuse Labs. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::io::Write; + +use databend_common_ast::parser::run_parser; +use databend_common_ast::parser::script::script_stmts; +use databend_common_ast::parser::tokenize_sql; +use databend_common_ast::parser::Dialect; +use databend_common_ast::parser::ParseMode; +use databend_common_script::compile; +use goldenfile::Mint; + +fn run_compiler(file: &mut dyn Write, src: &str) { + let src = unindent::unindent(src); + let src = src.trim(); + let tokens = tokenize_sql(src).unwrap(); + let ast = run_parser( + &tokens, + Dialect::PostgreSQL, + ParseMode::Template, + false, + script_stmts, + ) + .unwrap(); + match compile(&ast) { + Ok(ir) => { + writeln!(file, "---------- Input ----------").unwrap(); + writeln!(file, "{}", src).unwrap(); + writeln!(file, "---------- Output ---------").unwrap(); + for line in ir { + writeln!(file, "{}", line).unwrap(); + } + writeln!(file, "\n").unwrap(); + } + Err(err) => { + let report = err.display_with_sql(src).message().trim().to_string(); + writeln!(file, "---------- Input ----------").unwrap(); + writeln!(file, "{}", src).unwrap(); + writeln!(file, "---------- Output ----------").unwrap(); + writeln!(file, "{}", report).unwrap(); + writeln!(file, "\n").unwrap(); + } + } +} + +#[test] +fn test_compile() { + let mut mint = Mint::new("tests/it/testdata"); + let file = &mut mint.new_goldenfile("compiler.txt").unwrap(); + let cases = &[ + r#" + CREATE TABLE t1 (a INT, b INT, c INT); + INSERT INTO t1 VALUES (1, 2, 3); + DROP TABLE t1; + "#, + r#" + LET x := 1; + LET y := x + 1; + LET z RESULTSET := SELECT :y + 1; + "#, + r#" + RETURN; + "#, + r#" + LET x := 1; + IF x < 0 THEN + RETURN 'LESS THAN 0'; + ELSEIF x = 0 THEN + RETURN 'EQUAL TO 0'; + ELSE + RETURN 'GREATER THAN 0'; + END IF; + "#, + r#" + LET x := 1; + LET sum := 0; + FOR x IN x TO x + 10 DO + sum := sum + x; + END FOR; + "#, + r#" + FOR x IN REVERSE -100 TO 100 DO + CONTINUE; + END FOR; + "#, + r#" + LET x RESULTSET := SELECT * FROM numbers(10); + FOR row IN x DO + LET y := row.number; + END FOR; + "#, + r#" + LET x := 1; + WHILE x < 5 DO + x := x + 1; + END WHILE; + RETURN x; + "#, + r#" + LET x := 1; + REPEAT + x := x + 1; + UNTIL x = 5 + END REPEAT; + "#, + r#" + LOOP + LOOP + IF rand() < 0.5 THEN BREAK; + ELSE CONTINUE loop_label; + END IF; + END LOOP; + END LOOP loop_label; + "#, + // case + // if + ]; + + for case in cases { + run_compiler(file, case); + } +} + +#[test] +fn test_compile_error() { + let mut mint = Mint::new("tests/it/testdata"); + let file = &mut mint.new_goldenfile("compiler-error.txt").unwrap(); + let cases = &[ + r#" + LET x := y + 1; + "#, + r#" + LET x := 1; + LET x RESULTSET := SELECT 1; + "#, + r#" + LET x RESULTSET := SELECT 1; + LET x := 1; + "#, + r#" + LET x RESULTSET := SELECT 1; + LET y := x; + "#, + r#" + LET x := 1; + LET y := x.a; + "#, + r#" + LET x := 'min'; + LET y := IDENTIFIER(:x)([1,2]); + "#, + r#" + LET x := 1; + LET y := :x + 1; + "#, + r#" + LET x := 1; + FOR row IN x DO + BREAK; + END FOR; + "#, + r#" + BREAK; + "#, + r#" + CONTINUE; + "#, + r#" + LOOP + BREAK foo; + END LOOP bar; + "#, + r#" + LOOP + CONTINUE foo; + END LOOP bar; + "#, + ]; + + for case in cases { + run_compiler(file, case); + } +} diff --git a/src/query/script/tests/it/main.rs b/src/query/script/tests/it/main.rs new file mode 100644 index 0000000000000..666397c8c4451 --- /dev/null +++ b/src/query/script/tests/it/main.rs @@ -0,0 +1,15 @@ +// Copyright 2021 Datafuse Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod compiler; diff --git a/src/query/script/tests/it/testdata/compiler-error.txt b/src/query/script/tests/it/testdata/compiler-error.txt new file mode 100644 index 0000000000000..5b42e8da78a66 --- /dev/null +++ b/src/query/script/tests/it/testdata/compiler-error.txt @@ -0,0 +1,142 @@ +---------- Input ---------- +LET x := y + 1; +---------- Output ---------- +error: + --> SQL:1:10 + | +1 | LET x := y + 1; + | ^ `y` is not defined + + +---------- Input ---------- +LET x := 1; +LET x RESULTSET := SELECT 1; +---------- Output ---------- +error: + --> SQL:2:5 + | +1 | LET x := 1; +2 | LET x RESULTSET := SELECT 1; + | ^ `x` is already defined as a different kind of variable + + +---------- Input ---------- +LET x RESULTSET := SELECT 1; +LET x := 1; +---------- Output ---------- +error: + --> SQL:2:5 + | +1 | LET x RESULTSET := SELECT 1; +2 | LET x := 1; + | ^ `x` is already defined as a different kind of variable + + +---------- Input ---------- +LET x RESULTSET := SELECT 1; +LET y := x; +---------- Output ---------- +error: + --> SQL:2:10 + | +1 | LET x RESULTSET := SELECT 1; +2 | LET y := x; + | ^ `x` is not a scalar variable + + +---------- Input ---------- +LET x := 1; +LET y := x.a; +---------- Output ---------- +error: + --> SQL:2:10 + | +1 | LET x := 1; +2 | LET y := x.a; + | ^ `x` is not a row variable + + +---------- Input ---------- +LET x := 'min'; +LET y := IDENTIFIER(:x)([1,2]); +---------- Output ---------- +error: + --> SQL:2:10 + | +1 | LET x := 'min'; +2 | LET y := IDENTIFIER(:x)([1,2]); + | ^^^^^^^^^^^^^^ variable is not allowed in this context + + +---------- Input ---------- +LET x := 1; +LET y := :x + 1; +---------- Output ---------- +error: + --> SQL:2:10 + | +1 | LET x := 1; +2 | LET y := :x + 1; + | ^^ variable doesn't need to be quoted in this context, try removing the colon + + +---------- Input ---------- +LET x := 1; +FOR row IN x DO + BREAK; +END FOR; +---------- Output ---------- +error: + --> SQL:2:12 + | +1 | LET x := 1; +2 | FOR row IN x DO + | ^ `x` is not a set + + +---------- Input ---------- +BREAK; +---------- Output ---------- +error: + --> SQL:1:1 + | +1 | BREAK; + | ^^^^^ not in a loop + + +---------- Input ---------- +CONTINUE; +---------- Output ---------- +error: + --> SQL:1:1 + | +1 | CONTINUE; + | ^^^^^^^^ not in a loop + + +---------- Input ---------- +LOOP + BREAK foo; +END LOOP bar; +---------- Output ---------- +error: + --> SQL:2:11 + | +1 | LOOP +2 | BREAK foo; + | ^^^ `foo` is not defined + + +---------- Input ---------- +LOOP + CONTINUE foo; +END LOOP bar; +---------- Output ---------- +error: + --> SQL:2:14 + | +1 | LOOP +2 | CONTINUE foo; + | ^^^ `foo` is not defined + + diff --git a/src/query/script/tests/it/testdata/compiler.txt b/src/query/script/tests/it/testdata/compiler.txt new file mode 100644 index 0000000000000..bcb9d26981c42 --- /dev/null +++ b/src/query/script/tests/it/testdata/compiler.txt @@ -0,0 +1,207 @@ +---------- Input ---------- +CREATE TABLE t1 (a INT, b INT, c INT); +INSERT INTO t1 VALUES (1, 2, 3); +DROP TABLE t1; +---------- Output --------- +QUERY CREATE TABLE t1 (a Int32, b Int32, c Int32), __unused_result0(0) +QUERY INSERT INTO t1 VALUES (1, 2, 3), __unused_result1(1) +QUERY DROP TABLE t1, __unused_result2(2) + + +---------- Input ---------- +LET x := 1; +LET y := x + 1; +LET z RESULTSET := SELECT :y + 1; +---------- Output --------- +QUERY SELECT 1, __expr_result1(1) +ITER __expr_result1(1), __expr_result_iter2(2) +READ __expr_result_iter2(2), $0, x(0) +QUERY SELECT (:0 + 1), __expr_result4(4) +ITER __expr_result4(4), __expr_result_iter5(5) +READ __expr_result_iter5(5), $0, y(3) +QUERY SELECT (:3 + 1), z(6) + + +---------- Input ---------- +RETURN; +---------- Output --------- +RETURN + + +---------- Input ---------- +LET x := 1; +IF x < 0 THEN + RETURN 'LESS THAN 0'; +ELSEIF x = 0 THEN + RETURN 'EQUAL TO 0'; +ELSE + RETURN 'GREATER THAN 0'; +END IF; +---------- Output --------- +QUERY SELECT 1, __expr_result1(1) +ITER __expr_result1(1), __expr_result_iter2(2) +READ __expr_result_iter2(2), $0, x(0) +QUERY SELECT is_true((:0 < 0)), __expr_result7(7) +ITER __expr_result7(7), __expr_result_iter8(8) +READ __expr_result_iter8(8), $0, __condition6(6) +JUMP_IF_TRUE __condition6(6), __IF_THEN3(3) +QUERY SELECT is_true((:0 = 0)), __expr_result10(10) +ITER __expr_result10(10), __expr_result_iter11(11) +READ __expr_result_iter11(11), $0, __condition9(9) +JUMP_IF_TRUE __condition9(9), __IF_THEN4(4) +QUERY SELECT 'GREATER THAN 0', __expr_result13(13) +ITER __expr_result13(13), __expr_result_iter14(14) +READ __expr_result_iter14(14), $0, __return_val12(12) +RETURN __return_val12(12) +GOTO __IF_END5(5) +__IF_THEN3(3): +QUERY SELECT 'LESS THAN 0', __expr_result16(16) +ITER __expr_result16(16), __expr_result_iter17(17) +READ __expr_result_iter17(17), $0, __return_val15(15) +RETURN __return_val15(15) +GOTO __IF_END5(5) +__IF_THEN4(4): +QUERY SELECT 'EQUAL TO 0', __expr_result19(19) +ITER __expr_result19(19), __expr_result_iter20(20) +READ __expr_result_iter20(20), $0, __return_val18(18) +RETURN __return_val18(18) +GOTO __IF_END5(5) +__IF_END5(5): + + +---------- Input ---------- +LET x := 1; +LET sum := 0; +FOR x IN x TO x + 10 DO + sum := sum + x; +END FOR; +---------- Output --------- +QUERY SELECT 1, __expr_result1(1) +ITER __expr_result1(1), __expr_result_iter2(2) +READ __expr_result_iter2(2), $0, x(0) +QUERY SELECT 0, __expr_result4(4) +ITER __expr_result4(4), __expr_result_iter5(5) +READ __expr_result_iter5(5), $0, sum(3) +QUERY SELECT * FROM generate_series(x, (x + 10), 1), __for_index_set8(8) +ITER __for_index_set8(8), __for_index_iter9(9) +__LOOP6(6): +JUMP_IF_ENDED __for_index_iter9(9), __LOOP_END7(7) +READ __for_index_iter9(9), $0, x(10) +QUERY SELECT (:3 + :10), __expr_result11(11) +ITER __expr_result11(11), __expr_result_iter12(12) +READ __expr_result_iter12(12), $0, sum(3) +NEXT __for_index_iter9(9) +GOTO __LOOP6(6) +__LOOP_END7(7): + + +---------- Input ---------- +FOR x IN REVERSE -100 TO 100 DO + CONTINUE; +END FOR; +---------- Output --------- +QUERY SELECT * FROM generate_series(100, (- 100), -1), __for_index_set2(2) +ITER __for_index_set2(2), __for_index_iter3(3) +__LOOP0(0): +JUMP_IF_ENDED __for_index_iter3(3), __LOOP_END1(1) +READ __for_index_iter3(3), $0, x(4) +GOTO __LOOP0(0) +NEXT __for_index_iter3(3) +GOTO __LOOP0(0) +__LOOP_END1(1): + + +---------- Input ---------- +LET x RESULTSET := SELECT * FROM numbers(10); +FOR row IN x DO + LET y := row.number; +END FOR; +---------- Output --------- +QUERY SELECT * FROM numbers(10), x(0) +ITER x(0), row(3) +__LOOP1(1): +JUMP_IF_ENDED row(3), __LOOP_END2(2) +READ row(3), "number", __row.number5(5) +QUERY SELECT :__row.number5(5), __expr_result6(6) +ITER __expr_result6(6), __expr_result_iter7(7) +READ __expr_result_iter7(7), $0, y(4) +NEXT row(3) +GOTO __LOOP1(1) +__LOOP_END2(2): + + +---------- Input ---------- +LET x := 1; +WHILE x < 5 DO + x := x + 1; +END WHILE; +RETURN x; +---------- Output --------- +QUERY SELECT 1, __expr_result1(1) +ITER __expr_result1(1), __expr_result_iter2(2) +READ __expr_result_iter2(2), $0, x(0) +__LOOP3(3): +QUERY SELECT (NOT is_true((:0 < 5))), __expr_result6(6) +ITER __expr_result6(6), __expr_result_iter7(7) +READ __expr_result_iter7(7), $0, __break_condition5(5) +JUMP_IF_TRUE __break_condition5(5), __LOOP_END4(4) +QUERY SELECT (:0 + 1), __expr_result8(8) +ITER __expr_result8(8), __expr_result_iter9(9) +READ __expr_result_iter9(9), $0, x(0) +GOTO __LOOP3(3) +__LOOP_END4(4): +QUERY SELECT :0, __expr_result11(11) +ITER __expr_result11(11), __expr_result_iter12(12) +READ __expr_result_iter12(12), $0, __return_val10(10) +RETURN __return_val10(10) + + +---------- Input ---------- +LET x := 1; +REPEAT + x := x + 1; +UNTIL x = 5 +END REPEAT; +---------- Output --------- +QUERY SELECT 1, __expr_result1(1) +ITER __expr_result1(1), __expr_result_iter2(2) +READ __expr_result_iter2(2), $0, x(0) +__LOOP3(3): +QUERY SELECT (:0 + 1), __expr_result5(5) +ITER __expr_result5(5), __expr_result_iter6(6) +READ __expr_result_iter6(6), $0, x(0) +QUERY SELECT is_true((:0 = 5)), __expr_result8(8) +ITER __expr_result8(8), __expr_result_iter9(9) +READ __expr_result_iter9(9), $0, __break_condition7(7) +JUMP_IF_TRUE __break_condition7(7), __LOOP_END4(4) +GOTO __LOOP3(3) +__LOOP_END4(4): + + +---------- Input ---------- +LOOP + LOOP + IF rand() < 0.5 THEN BREAK; + ELSE CONTINUE loop_label; + END IF; + END LOOP; +END LOOP loop_label; +---------- Output --------- +loop_label_LOOP(0): +__LOOP2(2): +QUERY SELECT is_true((rand() < 0.5)), __expr_result7(7) +ITER __expr_result7(7), __expr_result_iter8(8) +READ __expr_result_iter8(8), $0, __condition6(6) +JUMP_IF_TRUE __condition6(6), __IF_THEN4(4) +GOTO loop_label_LOOP(0) +GOTO __IF_END5(5) +__IF_THEN4(4): +GOTO __LOOP_END3(3) +GOTO __IF_END5(5) +__IF_END5(5): +GOTO __LOOP2(2) +__LOOP_END3(3): +GOTO loop_label_LOOP(0) +loop_label_LOOP_END(1): + +