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(
+ '"',
+ ),
+ },
+}
+
+