diff --git a/Cargo.lock b/Cargo.lock index 6a1c63ce4f9f..beb492e5e28a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1433,7 +1433,7 @@ dependencies = [ "common-base", "common-exception", "common-functions", - "indoc", + "goldenfile", "logos", "nom 7.1.0", "nom-rule", @@ -2582,6 +2582,12 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" +[[package]] +name = "difference" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" + [[package]] name = "difflib" version = "0.4.0" @@ -3309,6 +3315,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "goldenfile" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f46e6a4d70c06f0b9a70d36dd8eef4fdeaa1ab657e4f1eaff290f69e48145f2" +dependencies = [ + "difference", + "tempfile", +] + [[package]] name = "griddle" version = "0.5.2" @@ -3714,15 +3730,6 @@ dependencies = [ "regex", ] -[[package]] -name = "indoc" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a75aeaaef0ce18b58056d306c27b07436fbb34b8816c53094b76dd81803136" -dependencies = [ - "unindent", -] - [[package]] name = "inferno" version = "0.10.10" @@ -7465,12 +7472,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" -[[package]] -name = "unindent" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f14ee04d9415b52b3aeab06258a3f07093182b88ba0f9b8d203f211a7a7d41c7" - [[package]] name = "untrusted" version = "0.7.1" diff --git a/common/ast/Cargo.toml b/common/ast/Cargo.toml index 00a5e23352ba..e4abe1caef1a 100644 --- a/common/ast/Cargo.toml +++ b/common/ast/Cargo.toml @@ -31,6 +31,6 @@ thiserror = "1.0.30" pratt = "0.3" [dev-dependencies] -indoc = "1" +goldenfile = "1" pretty_assertions = "1.0.0" common-base = { path = "../base" } diff --git a/common/ast/src/parser/ast/expression.rs b/common/ast/src/parser/ast/expression.rs index 17506c741264..5023aefc35d9 100644 --- a/common/ast/src/parser/ast/expression.rs +++ b/common/ast/src/parser/ast/expression.rs @@ -322,7 +322,7 @@ impl Display for Literal { write!(f, "{}", val) } Literal::String(val) => { - write!(f, "\"{}\"", val) + write!(f, "\'{}\'", val) } Literal::Boolean(val) => { if *val { diff --git a/common/ast/src/parser/rule/expr.rs b/common/ast/src/parser/rule/expr.rs index 8e3c24f29d99..117112073309 100644 --- a/common/ast/src/parser/rule/expr.rs +++ b/common/ast/src/parser/rule/expr.rs @@ -54,7 +54,7 @@ pub fn expr<'a>(i: Input<'a>) -> IResult, Expr, Error> { pub fn subexpr<'a>( min_precedence: u32, ) -> impl FnMut(Input<'a>) -> IResult, Expr, Error> { - cut(move |i| { + move |i| { let expr_element_limited = verify( expr_element, @@ -94,7 +94,7 @@ pub fn subexpr<'a>( } Ok((i, expr)) - }) + } } fn map_pratt_error<'a>( @@ -371,7 +371,7 @@ pub fn expr_element<'a>(i: Input<'a>) -> IResult, WithSpan<'a>, Error> ); let in_list = map( rule! { - NOT? ~ IN ~ "(" ~ #subexpr(0) ~ ("," ~ #subexpr(0))* ~ ")" + NOT? ~ IN ~ "(" ~ #cut(subexpr(0)) ~ ("," ~ #cut(subexpr(0)))* ~ ")" }, |(not, _, _, head, tail, _)| { let mut list = vec![head]; @@ -393,7 +393,7 @@ pub fn expr_element<'a>(i: Input<'a>) -> IResult, WithSpan<'a>, Error> ); let between = map( rule! { - NOT? ~ BETWEEN ~ #subexpr(BETWEEN_PREC) ~ AND ~ #subexpr(BETWEEN_PREC) + NOT? ~ BETWEEN ~ #cut(subexpr(BETWEEN_PREC)) ~ AND ~ #cut(subexpr(BETWEEN_PREC)) }, |(not, _, low, _, high)| ExprElement::Between { low, @@ -403,7 +403,7 @@ pub fn expr_element<'a>(i: Input<'a>) -> IResult, WithSpan<'a>, Error> ); let cast = map( rule! { - CAST ~ "(" ~ #subexpr(0) ~ AS ~ #type_name ~ ")" + CAST ~ "(" ~ #cut(subexpr(0)) ~ AS ~ #cut(type_name) ~ ")" }, |(_, _, expr, _, target_type, _)| ExprElement::Cast { expr, target_type }, ); @@ -412,7 +412,7 @@ pub fn expr_element<'a>(i: Input<'a>) -> IResult, WithSpan<'a>, Error> }); let function_call = map( rule! { - #function_name ~ "(" ~ (DISTINCT? ~ #subexpr(0) ~ ("," ~ #subexpr(0))*)? ~ ")" + #function_name ~ "(" ~ (DISTINCT? ~ #cut(subexpr(0)) ~ ("," ~ #cut(subexpr(0)))*)? ~ ")" }, |(name, _, args, _)| { let (distinct, args) = args @@ -433,7 +433,7 @@ pub fn expr_element<'a>(i: Input<'a>) -> IResult, WithSpan<'a>, Error> ); let function_call_with_param = map( rule! { - #function_name ~ "(" ~ (#literal ~ ("," ~ #literal)*)? ~ ")" ~ "(" ~ (DISTINCT? ~ #subexpr(0) ~ ("," ~ #subexpr(0))*)? ~ ")" + #function_name ~ "(" ~ (#literal ~ ("," ~ #literal)*)? ~ ")" ~ "(" ~ (DISTINCT? ~ #cut(subexpr(0)) ~ ("," ~ #cut(subexpr(0)))*)? ~ ")" }, |(name, _, params, _, _, args, _)| { let params = params @@ -462,7 +462,7 @@ pub fn expr_element<'a>(i: Input<'a>) -> IResult, WithSpan<'a>, Error> ); let case = map( rule! { - CASE ~ #subexpr(0)? ~ (WHEN ~ #subexpr(0) ~ THEN ~ #subexpr(0))+ ~ (ELSE ~ #subexpr(0))? ~ END + CASE ~ #subexpr(0)? ~ (WHEN ~ #cut(subexpr(0)) ~ THEN ~ #cut(subexpr(0)))+ ~ (ELSE ~ #cut(subexpr(0)))? ~ END }, |(_, operand, branches, else_result, _)| { let (conditions, results) = branches @@ -485,7 +485,7 @@ pub fn expr_element<'a>(i: Input<'a>) -> IResult, WithSpan<'a>, Error> let subquery = map(rule! { "(" ~ #query ~ ")" }, |(_, subquery, _)| { ExprElement::Subquery(subquery) }); - let group = map(rule! { "(" ~ #subexpr(0) ~ ")" }, |(_, expr, _)| { + let group = map(rule! { "(" ~ #cut(subexpr(0)) ~ ")" }, |(_, expr, _)| { ExprElement::Group(expr) }); let binary_op = map(binary_op, |op| ExprElement::BinaryOp { op }); @@ -493,22 +493,22 @@ pub fn expr_element<'a>(i: Input<'a>) -> IResult, WithSpan<'a>, Error> let literal = map(literal, ExprElement::Literal); let (rest, elem) = rule! ( - #column_ref - | #is_null - | #in_list - | #in_subquery - | #between - | #binary_op - | #unary_op - | #cast - | #count_all - | #literal - | #function_call_with_param - | #function_call - | #case - | #exists - | #subquery - | #group + #column_ref : "" + | #is_null : "... IS NULL" + | #in_list : "IN [...]" + | #in_subquery : "IN (SELECT ...)" + | #between : "BETWEEN ..." + | #binary_op : "" + | #unary_op : "" + | #cast : "CAST(... AS ...)" + | #count_all : "COUNT(*)" + | #literal : "" + | #function_call_with_param : "" + | #function_call : "" + | #case : "CASE ... END" + | #exists : "EXISTS (SELECT ...)" + | #subquery : "(SELECT ...)" + | #group : "()" )(i)?; let input_ptr = i.as_ptr(); diff --git a/common/ast/tests/it/rule.rs b/common/ast/tests/it/rule.rs index 999c5f67d71a..b03d328d7a38 100644 --- a/common/ast/tests/it/rule.rs +++ b/common/ast/tests/it/rule.rs @@ -12,133 +12,100 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::io::Write; + use common_ast::parser::rule::error::pretty_print_error; -use common_ast::parser::rule::error::Error; use common_ast::parser::rule::expr::expr; use common_ast::parser::rule::statement::statement; use common_ast::parser::token::*; -use indoc::indoc; +use goldenfile::Mint; use nom::Parser; use pretty_assertions::assert_eq; -macro_rules! assert_parse { - ($parser:expr, $source:literal, $expected:expr $(,)*) => { +macro_rules! test_parse { + ($file:expr, $parser:expr, $source:literal $(,)*) => { let tokens = tokenise($source).unwrap(); - let res: nom::IResult<_, _, Error> = $parser.parse(&(tokens)); - let (i, output) = res.unwrap(); - - assert_eq!(&format!("{}", output), $expected); - assert_eq!(i[0].kind, TokenKind::EOI); - }; -} - -macro_rules! assert_parse_error { - ($parser:expr, $source:literal, $expected:expr $(,)*) => { - let tokens = tokenise($source).unwrap(); - let res: nom::IResult<_, _, Error> = $parser.parse(&(tokens)); - let err = res.unwrap_err(); - - let output = pretty_print_error($source, err).trim_end().to_string(); - if output != $expected.trim() { - panic!("assertion failed: error message mismatched\noutput:\n{output}"); + match $parser.parse(&(tokens)) { + Ok((i, output)) => { + writeln!($file, "---------- Input ----------").unwrap(); + writeln!($file, "{}", $source).unwrap(); + writeln!($file, "---------- Output ---------").unwrap(); + writeln!($file, "{}", output).unwrap(); + writeln!($file, "---------- AST ------------").unwrap(); + writeln!($file, "{:#?}", output).unwrap(); + writeln!($file, "\n").unwrap(); + assert_eq!(i[0].kind, TokenKind::EOI); + } + Err(err) => { + let report = pretty_print_error($source, err).trim_end().to_string(); + writeln!($file, "---------- Input ----------").unwrap(); + writeln!($file, "{}", $source).unwrap(); + writeln!($file, "---------- Output ---------").unwrap(); + writeln!($file, "{}", report).unwrap(); + writeln!($file, "\n").unwrap(); + } } }; } #[test] fn test_statement() { - assert_parse!(statement, "truncate table a;", "TRUNCATE TABLE a"); - assert_parse!( - statement, - r#"truncate table "a".b;"#, - r#"TRUNCATE TABLE "a".b"#, - ); - assert_parse!(statement, "drop table a;", "DROP TABLE a"); - assert_parse!( - statement, - r#"drop table if exists a."b";"#, - r#"DROP TABLE IF EXISTS a."b""#, - ); + let mut mint = Mint::new("tests/it/testdata"); + let mut file = mint.new_goldenfile("statement.txt").unwrap(); + test_parse!(file, statement, "truncate table a;"); + test_parse!(file, statement, r#"truncate table "a".b;"#,); + test_parse!(file, statement, "drop table a;"); + test_parse!(file, statement, r#"drop table if exists a."b";"#,); } -// TODO (andylokandy): test tree structure, maybe add parentheses? #[test] fn test_expr() { - assert_parse!(expr, "a", "a"); - assert_parse!(expr, "1 + a * c.d", "1 + a * c.d"); - assert_parse!(expr, "col1 not between 1 and 2", "col1 NOT BETWEEN 1 AND 2"); - assert_parse!(expr, "sum(col1)", "sum(col1)"); - assert_parse!( + let mut mint = Mint::new("tests/it/testdata"); + let mut file = mint.new_goldenfile("expr.txt").unwrap(); + test_parse!(file, expr, "a"); + test_parse!(file, expr, "1 + a * c.d"); + test_parse!(file, expr, "col1 not between 1 and 2"); + test_parse!(file, expr, "sum(col1)"); + test_parse!( + file, expr, "G.E.B IS NOT NULL AND col1 not between col2 and (1 + col3) DIV sum(col4)", - "G.E.B IS NOT NULL AND col1 NOT BETWEEN col2 AND 1 + col3 DIV sum(col4)" + ); + test_parse!( + file, + expr, + "sum(CASE WHEN n2.n_name = 'GERMANY' THEN ol_amount ELSE 0 END) / CASE WHEN sum(ol_amount) = 0 THEN 1 ELSE sum(ol_amount) END", + ); + test_parse!( + file, + expr, + "p_partkey = l_partkey + AND p_brand = 'Brand#12' + AND p_container IN ('SM CASE', 'SM BOX', 'SM PACK', 'SM PKG') + AND l_quantity >= CAST (1 AS smallint) AND l_quantity <= CAST (1 + 10 AS smallint) + AND p_size BETWEEN CAST (1 AS smallint) AND CAST (5 AS smallint) + AND l_shipmode IN ('AIR', 'AIR REG') + AND l_shipinstruct = 'DELIVER IN PERSON'", ); } #[test] fn test_statement_error() { - assert_parse_error!(statement, "drop table if a.b;", indoc! { - r#" - error: - --> SQL:1:12 - | - 1 | drop table if a.b; - | ---- ^^ expected token - | | - | while parsing DROP TABLE statement - "# - }); - assert_parse_error!(statement, "truncate table a", indoc! { - r#" - error: - --> SQL:1:17 - | - 1 | truncate table a - | -------- ^ expected token ";" - | | - | while parsing TRUNCATE TABLE statement - "# - }); + let mut mint = Mint::new("tests/it/testdata"); + let mut file = mint.new_goldenfile("statement-error.txt").unwrap(); + test_parse!(file, statement, "drop table if a.b;"); + test_parse!(file, statement, "truncate table a"); } #[test] fn test_expr_error() { - assert_parse_error!(expr, "(a and ) 1", indoc! { - r#" - error: - --> SQL:1:8 - | - 1 | (a and ) 1 - | - ^ unexpected end of expression - | | - | while parsing expression - "# - }); - assert_parse_error!(expr, "a + +", indoc! { - r#" - error: - --> SQL:1:5 - | - 1 | a + + - | - ^ unable to parse the binary operator - | | - | while parsing expression - "# - }); - assert_parse_error!( + let mut mint = Mint::new("tests/it/testdata"); + let mut file = mint.new_goldenfile("expr-error.txt").unwrap(); + test_parse!(file, expr, "(a and ) 1"); + test_parse!(file, expr, "a + +"); + test_parse!( + file, expr, - "G.E.B IS NOT NULL AND\n\tcol1 NOT BETWEEN col2 AND\n\t\tAND 1 + col3 DIV sum(col4)", - indoc! { - r#" - error: - --> SQL:3:3 - | - 1 | G.E.B IS NOT NULL AND - | - while parsing expression - 2 | col1 NOT BETWEEN col2 AND - 3 | AND 1 + col3 DIV sum(col4) - | ^^^ unexpected end of expression - "# - } + "G.E.B IS NOT NULL AND\n\tcol1 NOT BETWEEN col2 AND\n\t\tAND 1 + col3 DIV sum(col4)" ); } diff --git a/common/ast/tests/it/testdata/expr-error.txt b/common/ast/tests/it/testdata/expr-error.txt new file mode 100644 index 000000000000..1a84dc46879e --- /dev/null +++ b/common/ast/tests/it/testdata/expr-error.txt @@ -0,0 +1,41 @@ +---------- Input ---------- +(a and ) 1 +---------- Output --------- +error: + --> SQL:1:8 + | +1 | (a and ) 1 + | - ^ unexpected end of expression + | | + | while parsing () + | while parsing expression + + +---------- Input ---------- +a + + +---------- Output --------- +error: + --> SQL:1:5 + | +1 | a + + + | - ^ unable to parse the binary operator + | | + | while parsing expression + + +---------- Input ---------- +G.E.B IS NOT NULL AND + col1 NOT BETWEEN col2 AND + AND 1 + col3 DIV sum(col4) +---------- Output --------- +error: + --> SQL:3:3 + | +1 | G.E.B IS NOT NULL AND + | - while parsing expression +2 | col1 NOT BETWEEN col2 AND + | --- while parsing BETWEEN ... +3 | AND 1 + col3 DIV sum(col4) + | ^^^ unexpected end of expression + + diff --git a/common/ast/tests/it/testdata/expr.txt b/common/ast/tests/it/testdata/expr.txt new file mode 100644 index 000000000000..490bb8789507 --- /dev/null +++ b/common/ast/tests/it/testdata/expr.txt @@ -0,0 +1,522 @@ +---------- Input ---------- +a +---------- Output --------- +a +---------- AST ------------ +ColumnRef { + database: None, + table: None, + column: Identifier { + name: "a", + quote: None, + }, +} + + +---------- Input ---------- +1 + a * c.d +---------- Output --------- +1 + a * c.d +---------- AST ------------ +BinaryOp { + op: Plus, + left: Literal( + Number( + "1", + ), + ), + right: BinaryOp { + op: Multiply, + left: ColumnRef { + database: None, + table: None, + column: Identifier { + name: "a", + quote: None, + }, + }, + right: ColumnRef { + database: None, + table: Some( + Identifier { + name: "c", + quote: None, + }, + ), + column: Identifier { + name: "d", + quote: None, + }, + }, + }, +} + + +---------- Input ---------- +col1 not between 1 and 2 +---------- Output --------- +col1 NOT BETWEEN 1 AND 2 +---------- AST ------------ +Between { + expr: ColumnRef { + database: None, + table: None, + column: Identifier { + name: "col1", + quote: None, + }, + }, + low: Literal( + Number( + "1", + ), + ), + high: Literal( + Number( + "2", + ), + ), + not: true, +} + + +---------- Input ---------- +sum(col1) +---------- Output --------- +sum(col1) +---------- AST ------------ +FunctionCall { + distinct: false, + name: "sum", + args: [ + ColumnRef { + database: None, + table: None, + column: Identifier { + name: "col1", + quote: None, + }, + }, + ], + params: [], +} + + +---------- Input ---------- +G.E.B IS NOT NULL AND col1 not between col2 and (1 + col3) DIV sum(col4) +---------- Output --------- +G.E.B IS NOT NULL AND col1 NOT BETWEEN col2 AND 1 + col3 DIV sum(col4) +---------- AST ------------ +BinaryOp { + op: And, + left: IsNull { + expr: ColumnRef { + database: Some( + Identifier { + name: "G", + quote: None, + }, + ), + table: Some( + Identifier { + name: "E", + quote: None, + }, + ), + column: Identifier { + name: "B", + quote: None, + }, + }, + not: true, + }, + right: Between { + expr: ColumnRef { + database: None, + table: None, + column: Identifier { + name: "col1", + quote: None, + }, + }, + low: ColumnRef { + database: None, + table: None, + column: Identifier { + name: "col2", + quote: None, + }, + }, + high: BinaryOp { + op: Div, + left: BinaryOp { + op: Plus, + left: Literal( + Number( + "1", + ), + ), + right: ColumnRef { + database: None, + table: None, + column: Identifier { + name: "col3", + quote: None, + }, + }, + }, + right: FunctionCall { + distinct: false, + name: "sum", + args: [ + ColumnRef { + database: None, + table: None, + column: Identifier { + name: "col4", + quote: None, + }, + }, + ], + params: [], + }, + }, + not: true, + }, +} + + +---------- Input ---------- +sum(CASE WHEN n2.n_name = 'GERMANY' THEN ol_amount ELSE 0 END) / CASE WHEN sum(ol_amount) = 0 THEN 1 ELSE sum(ol_amount) END +---------- Output --------- +sum(CASE WHEN n2.n_name = 'GERMANY' THEN ol_amount ELSE 0 END) / CASE WHEN sum(ol_amount) = 0 THEN 1 ELSE sum(ol_amount) END +---------- AST ------------ +BinaryOp { + op: Divide, + left: FunctionCall { + distinct: false, + name: "sum", + args: [ + Case { + operand: None, + conditions: [ + BinaryOp { + op: Eq, + left: ColumnRef { + database: None, + table: Some( + Identifier { + name: "n2", + quote: None, + }, + ), + column: Identifier { + name: "n_name", + quote: None, + }, + }, + right: Literal( + String( + "GERMANY", + ), + ), + }, + ], + results: [ + ColumnRef { + database: None, + table: None, + column: Identifier { + name: "ol_amount", + quote: None, + }, + }, + ], + else_result: Some( + Literal( + Number( + "0", + ), + ), + ), + }, + ], + params: [], + }, + right: Case { + operand: None, + conditions: [ + BinaryOp { + op: Eq, + left: FunctionCall { + distinct: false, + name: "sum", + args: [ + ColumnRef { + database: None, + table: None, + column: Identifier { + name: "ol_amount", + quote: None, + }, + }, + ], + params: [], + }, + right: Literal( + Number( + "0", + ), + ), + }, + ], + results: [ + Literal( + Number( + "1", + ), + ), + ], + else_result: Some( + FunctionCall { + distinct: false, + name: "sum", + args: [ + ColumnRef { + database: None, + table: None, + column: Identifier { + name: "ol_amount", + quote: None, + }, + }, + ], + params: [], + }, + ), + }, +} + + +---------- Input ---------- +p_partkey = l_partkey + AND p_brand = 'Brand#12' + AND p_container IN ('SM CASE', 'SM BOX', 'SM PACK', 'SM PKG') + AND l_quantity >= CAST (1 AS smallint) AND l_quantity <= CAST (1 + 10 AS smallint) + AND p_size BETWEEN CAST (1 AS smallint) AND CAST (5 AS smallint) + AND l_shipmode IN ('AIR', 'AIR REG') + AND l_shipinstruct = 'DELIVER IN PERSON' +---------- Output --------- +p_partkey = l_partkey AND p_brand = 'Brand#12' AND p_container IN('SM CASE', 'SM BOX', 'SM PACK', 'SM PKG') AND l_quantity >= CAST(1 AS SMALLINT) AND l_quantity <= CAST(1 + 10 AS SMALLINT) AND p_size BETWEEN CAST(1 AS SMALLINT) AND CAST(5 AS SMALLINT) AND l_shipmode IN('AIR', 'AIR REG') AND l_shipinstruct = 'DELIVER IN PERSON' +---------- AST ------------ +BinaryOp { + op: And, + left: BinaryOp { + op: And, + left: BinaryOp { + op: And, + left: BinaryOp { + op: And, + left: BinaryOp { + op: And, + left: BinaryOp { + op: And, + left: BinaryOp { + op: And, + left: BinaryOp { + op: Eq, + left: ColumnRef { + database: None, + table: None, + column: Identifier { + name: "p_partkey", + quote: None, + }, + }, + right: ColumnRef { + database: None, + table: None, + column: Identifier { + name: "l_partkey", + quote: None, + }, + }, + }, + right: BinaryOp { + op: Eq, + left: ColumnRef { + database: None, + table: None, + column: Identifier { + name: "p_brand", + quote: None, + }, + }, + right: Literal( + String( + "Brand#12", + ), + ), + }, + }, + right: InList { + expr: ColumnRef { + database: None, + table: None, + column: Identifier { + name: "p_container", + quote: None, + }, + }, + list: [ + Literal( + String( + "SM CASE", + ), + ), + Literal( + String( + "SM BOX", + ), + ), + Literal( + String( + "SM PACK", + ), + ), + Literal( + String( + "SM PKG", + ), + ), + ], + not: false, + }, + }, + right: BinaryOp { + op: Gte, + left: ColumnRef { + database: None, + table: None, + column: Identifier { + name: "l_quantity", + quote: None, + }, + }, + right: Cast { + expr: Literal( + Number( + "1", + ), + ), + target_type: SmallInt( + None, + ), + }, + }, + }, + right: BinaryOp { + op: Lte, + left: ColumnRef { + database: None, + table: None, + column: Identifier { + name: "l_quantity", + quote: None, + }, + }, + right: Cast { + expr: BinaryOp { + op: Plus, + left: Literal( + Number( + "1", + ), + ), + right: Literal( + Number( + "10", + ), + ), + }, + target_type: SmallInt( + None, + ), + }, + }, + }, + right: Between { + expr: ColumnRef { + database: None, + table: None, + column: Identifier { + name: "p_size", + quote: None, + }, + }, + low: Cast { + expr: Literal( + Number( + "1", + ), + ), + target_type: SmallInt( + None, + ), + }, + high: Cast { + expr: Literal( + Number( + "5", + ), + ), + target_type: SmallInt( + None, + ), + }, + not: false, + }, + }, + right: InList { + expr: ColumnRef { + database: None, + table: None, + column: Identifier { + name: "l_shipmode", + quote: None, + }, + }, + list: [ + Literal( + String( + "AIR", + ), + ), + Literal( + String( + "AIR REG", + ), + ), + ], + not: false, + }, + }, + right: BinaryOp { + op: Eq, + left: ColumnRef { + database: None, + table: None, + column: Identifier { + name: "l_shipinstruct", + quote: None, + }, + }, + right: Literal( + String( + "DELIVER IN PERSON", + ), + ), + }, +} + + diff --git a/common/ast/tests/it/testdata/statement-error.txt b/common/ast/tests/it/testdata/statement-error.txt new file mode 100644 index 000000000000..8e16704f98e4 --- /dev/null +++ b/common/ast/tests/it/testdata/statement-error.txt @@ -0,0 +1,24 @@ +---------- Input ---------- +drop table if a.b; +---------- Output --------- +error: + --> SQL:1:12 + | +1 | drop table if a.b; + | ---- ^^ expected token + | | + | while parsing DROP TABLE statement + + +---------- Input ---------- +truncate table a +---------- Output --------- +error: + --> SQL:1:17 + | +1 | truncate table a + | -------- ^ expected token ";" + | | + | while parsing TRUNCATE TABLE statement + + diff --git a/common/ast/tests/it/testdata/statement.txt b/common/ast/tests/it/testdata/statement.txt new file mode 100644 index 000000000000..05fc13a272df --- /dev/null +++ b/common/ast/tests/it/testdata/statement.txt @@ -0,0 +1,72 @@ +---------- Input ---------- +truncate table a; +---------- Output --------- +TRUNCATE TABLE a +---------- AST ------------ +TruncateTable { + database: None, + table: Identifier { + name: "a", + quote: None, + }, +} + + +---------- Input ---------- +truncate table "a".b; +---------- Output --------- +TRUNCATE TABLE "a".b +---------- AST ------------ +TruncateTable { + database: Some( + Identifier { + name: "a", + quote: Some( + '"', + ), + }, + ), + table: Identifier { + name: "b", + quote: None, + }, +} + + +---------- Input ---------- +drop table a; +---------- Output --------- +DROP TABLE a +---------- AST ------------ +DropTable { + if_exists: false, + database: None, + table: Identifier { + name: "a", + quote: None, + }, +} + + +---------- Input ---------- +drop table if exists a."b"; +---------- Output --------- +DROP TABLE IF EXISTS a."b" +---------- AST ------------ +DropTable { + if_exists: true, + database: Some( + Identifier { + name: "a", + quote: None, + }, + ), + table: Identifier { + name: "b", + quote: Some( + '"', + ), + }, +} + +