From 4b2ad47071047a30ebfe3873f8a566108e86928e Mon Sep 17 00:00:00 2001 From: git-hulk Date: Sun, 30 Jun 2024 22:20:29 +0800 Subject: [PATCH 1/2] Add the support of PREWHERE condition for ClickHouse dialect For ClickHouse PREWHERE clause, please refer to: https://clickhouse.com/docs/en/sql-reference/statements/select/prewhere --- src/ast/query.rs | 8 ++++++ src/keywords.rs | 3 +++ src/parser/mod.rs | 9 +++++++ tests/sqlparser_clickhouse.rs | 51 +++++++++++++++++++++++++++++++++++ tests/sqlparser_common.rs | 8 ++++++ tests/sqlparser_duckdb.rs | 2 ++ tests/sqlparser_mssql.rs | 2 ++ tests/sqlparser_mysql.rs | 8 ++++++ tests/sqlparser_postgres.rs | 3 +++ 9 files changed, 94 insertions(+) diff --git a/src/ast/query.rs b/src/ast/query.rs index d00a0dfcc..82250ae4b 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -240,6 +240,11 @@ pub struct Select { pub from: Vec, /// LATERAL VIEWs pub lateral_views: Vec, + /// ClickHouse syntax: `PREWHERE a = 1 WHERE b = 2`, + /// and it can be used together with WHERE selection. + /// + /// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/select/prewhere) + pub prewhere: Option, /// WHERE pub selection: Option, /// GROUP BY @@ -295,6 +300,9 @@ impl fmt::Display for Select { write!(f, "{lv}")?; } } + if let Some(ref prewhere) = self.prewhere { + write!(f, " PREWHERE {prewhere}")?; + } if let Some(ref selection) = self.selection { write!(f, " WHERE {selection}")?; } diff --git a/src/keywords.rs b/src/keywords.rs index 5db55e9da..757669dae 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -558,6 +558,7 @@ define_keywords!( PRECISION, PREPARE, PRESERVE, + PREWHERE, PRIMARY, PRIOR, PRIVILEGES, @@ -850,6 +851,8 @@ pub const RESERVED_FOR_TABLE_ALIAS: &[Keyword] = &[ Keyword::FOR, // for MYSQL PARTITION SELECTION Keyword::PARTITION, + // for Clickhouse PREWHERE + Keyword::PREWHERE, // for Snowflake START WITH .. CONNECT BY Keyword::START, Keyword::CONNECT, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 4e9c3836b..0554cbcd1 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -8312,6 +8312,14 @@ impl<'a> Parser<'a> { } } + let prewhere = if dialect_of!(self is ClickHouseDialect|GenericDialect) + && self.parse_keyword(Keyword::PREWHERE) + { + Some(self.parse_expr()?) + } else { + None + }; + let selection = if self.parse_keyword(Keyword::WHERE) { Some(self.parse_expr()?) } else { @@ -8423,6 +8431,7 @@ impl<'a> Parser<'a> { into, from, lateral_views, + prewhere, selection, group_by, cluster_by, diff --git a/tests/sqlparser_clickhouse.rs b/tests/sqlparser_clickhouse.rs index 0c188a24b..61aa011bb 100644 --- a/tests/sqlparser_clickhouse.rs +++ b/tests/sqlparser_clickhouse.rs @@ -63,6 +63,7 @@ fn parse_map_access_expr() { joins: vec![], }], lateral_views: vec![], + prewhere: None, selection: Some(BinaryOp { left: Box::new(BinaryOp { left: Box::new(Identifier(Ident::new("id"))), @@ -681,6 +682,56 @@ fn parse_group_by_with_modifier() { } } +#[test] +fn test_prewhere() { + match clickhouse().verified_stmt("SELECT * FROM t PREWHERE x = 1 WHERE y = 2") { + Statement::Query(query) => { + let prewhere = query.body.as_select().unwrap().prewhere.as_ref(); + assert_eq!( + prewhere, + Some(&BinaryOp { + left: Box::new(Identifier(Ident::new("x"))), + op: BinaryOperator::Eq, + right: Box::new(Expr::Value(Value::Number("1".parse().unwrap(), false))), + }) + ); + let selection = query.as_ref().body.as_select().unwrap().selection.as_ref(); + assert_eq!( + selection, + Some(&BinaryOp { + left: Box::new(Identifier(Ident::new("y"))), + op: BinaryOperator::Eq, + right: Box::new(Expr::Value(Value::Number("2".parse().unwrap(), false))), + }) + ); + } + _ => unreachable!(), + } + + match clickhouse().verified_stmt("SELECT * FROM t PREWHERE x = 1 AND y = 2") { + Statement::Query(query) => { + let prewhere = query.body.as_select().unwrap().prewhere.as_ref(); + assert_eq!( + prewhere, + Some(&BinaryOp { + left: Box::new(BinaryOp { + left: Box::new(Identifier(Ident::new("x"))), + op: BinaryOperator::Eq, + right: Box::new(Expr::Value(Value::Number("1".parse().unwrap(), false))), + }), + op: BinaryOperator::And, + right: Box::new(BinaryOp { + left: Box::new(Identifier(Ident::new("y"))), + op: BinaryOperator::Eq, + right: Box::new(Expr::Value(Value::Number("2".parse().unwrap(), false))), + }), + }) + ); + } + _ => unreachable!(), + } +} + fn clickhouse() -> TestedDialects { TestedDialects { dialects: vec![Box::new(ClickHouseDialect {})], diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index ac2133946..188dc7844 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -391,6 +391,7 @@ fn parse_update_set_from() { joins: vec![], }], lateral_views: vec![], + prewhere: None, selection: None, group_by: GroupByExpr::Expressions( vec![Expr::Identifier(Ident::new("id"))], @@ -4548,6 +4549,7 @@ fn test_parse_named_window() { joins: vec![], }], lateral_views: vec![], + prewhere: None, selection: None, group_by: GroupByExpr::Expressions(vec![], vec![]), cluster_by: vec![], @@ -4929,6 +4931,7 @@ fn parse_interval_and_or_xor() { joins: vec![], }], lateral_views: vec![], + prewhere: None, selection: Some(Expr::BinaryOp { left: Box::new(Expr::BinaryOp { left: Box::new(Expr::Identifier(Ident { @@ -6911,6 +6914,7 @@ fn lateral_function() { }], }], lateral_views: vec![], + prewhere: None, selection: None, group_by: GroupByExpr::Expressions(vec![], vec![]), cluster_by: vec![], @@ -7630,6 +7634,7 @@ fn parse_merge() { joins: vec![], }], lateral_views: vec![], + prewhere: None, selection: None, group_by: GroupByExpr::Expressions(vec![], vec![]), cluster_by: vec![], @@ -9136,6 +9141,7 @@ fn parse_unload() { joins: vec![], }], lateral_views: vec![], + prewhere: None, selection: None, group_by: GroupByExpr::Expressions(vec![], vec![]), cluster_by: vec![], @@ -9279,6 +9285,7 @@ fn parse_connect_by() { }], into: None, lateral_views: vec![], + prewhere: None, selection: None, group_by: GroupByExpr::Expressions(vec![], vec![]), cluster_by: vec![], @@ -9363,6 +9370,7 @@ fn parse_connect_by() { }], into: None, lateral_views: vec![], + prewhere: None, selection: Some(Expr::BinaryOp { left: Box::new(Expr::Identifier(Ident::new("employee_id"))), op: BinaryOperator::NotEq, diff --git a/tests/sqlparser_duckdb.rs b/tests/sqlparser_duckdb.rs index 948e150c9..400daa8a8 100644 --- a/tests/sqlparser_duckdb.rs +++ b/tests/sqlparser_duckdb.rs @@ -170,6 +170,7 @@ fn test_select_union_by_name() { joins: vec![], }], lateral_views: vec![], + prewhere: None, selection: None, group_by: GroupByExpr::Expressions(vec![], vec![]), cluster_by: vec![], @@ -208,6 +209,7 @@ fn test_select_union_by_name() { joins: vec![], }], lateral_views: vec![], + prewhere: None, selection: None, group_by: GroupByExpr::Expressions(vec![], vec![]), cluster_by: vec![], diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 993850299..91f736e60 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -110,6 +110,7 @@ fn parse_create_procedure() { into: None, from: vec![], lateral_views: vec![], + prewhere: None, selection: None, group_by: GroupByExpr::Expressions(vec![], vec![]), cluster_by: vec![], @@ -527,6 +528,7 @@ fn parse_substring_in_select() { joins: vec![] }], lateral_views: vec![], + prewhere: None, selection: None, group_by: GroupByExpr::Expressions(vec![], vec![]), cluster_by: vec![], diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 4c18d4a75..f0cda30c6 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -906,6 +906,7 @@ fn parse_escaped_quote_identifiers_with_escape() { into: None, from: vec![], lateral_views: vec![], + prewhere: None, selection: None, group_by: GroupByExpr::Expressions(vec![], vec![]), cluster_by: vec![], @@ -953,6 +954,7 @@ fn parse_escaped_quote_identifiers_with_no_escape() { into: None, from: vec![], lateral_views: vec![], + prewhere: None, selection: None, group_by: GroupByExpr::Expressions(vec![], vec![]), cluster_by: vec![], @@ -997,6 +999,7 @@ fn parse_escaped_backticks_with_escape() { into: None, from: vec![], lateral_views: vec![], + prewhere: None, selection: None, group_by: GroupByExpr::Expressions(vec![], vec![]), cluster_by: vec![], @@ -1041,6 +1044,7 @@ fn parse_escaped_backticks_with_no_escape() { into: None, from: vec![], lateral_views: vec![], + prewhere: None, selection: None, group_by: GroupByExpr::Expressions(vec![], vec![]), cluster_by: vec![], @@ -1702,6 +1706,7 @@ fn parse_select_with_numeric_prefix_column_name() { joins: vec![] }], lateral_views: vec![], + prewhere: None, selection: None, group_by: GroupByExpr::Expressions(vec![], vec![]), cluster_by: vec![], @@ -1755,6 +1760,7 @@ fn parse_select_with_concatenation_of_exp_number_and_numeric_prefix_column() { joins: vec![] }], lateral_views: vec![], + prewhere: None, selection: None, group_by: GroupByExpr::Expressions(vec![], vec![]), cluster_by: vec![], @@ -2254,6 +2260,7 @@ fn parse_substring_in_select() { joins: vec![] }], lateral_views: vec![], + prewhere: None, selection: None, group_by: GroupByExpr::Expressions(vec![], vec![]), cluster_by: vec![], @@ -2558,6 +2565,7 @@ fn parse_hex_string_introducer() { })], from: vec![], lateral_views: vec![], + prewhere: None, selection: None, group_by: GroupByExpr::Expressions(vec![], vec![]), cluster_by: vec![], diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 2606fb96e..b8bb54b96 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -1074,6 +1074,7 @@ fn parse_copy_to() { into: None, from: vec![], lateral_views: vec![], + prewhere: None, selection: None, group_by: GroupByExpr::Expressions(vec![], vec![]), having: None, @@ -2382,6 +2383,7 @@ fn parse_array_subquery_expr() { into: None, from: vec![], lateral_views: vec![], + prewhere: None, selection: None, group_by: GroupByExpr::Expressions(vec![], vec![]), cluster_by: vec![], @@ -2401,6 +2403,7 @@ fn parse_array_subquery_expr() { into: None, from: vec![], lateral_views: vec![], + prewhere: None, selection: None, group_by: GroupByExpr::Expressions(vec![], vec![]), cluster_by: vec![], From 6ef816468210c8bc1183bb2a4ce916cd000051ba Mon Sep 17 00:00:00 2001 From: git-hulk Date: Fri, 5 Jul 2024 18:29:30 +0800 Subject: [PATCH 2/2] Fix review comment --- tests/sqlparser_clickhouse.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/sqlparser_clickhouse.rs b/tests/sqlparser_clickhouse.rs index 61aa011bb..b808860d4 100644 --- a/tests/sqlparser_clickhouse.rs +++ b/tests/sqlparser_clickhouse.rs @@ -684,7 +684,7 @@ fn parse_group_by_with_modifier() { #[test] fn test_prewhere() { - match clickhouse().verified_stmt("SELECT * FROM t PREWHERE x = 1 WHERE y = 2") { + match clickhouse_and_generic().verified_stmt("SELECT * FROM t PREWHERE x = 1 WHERE y = 2") { Statement::Query(query) => { let prewhere = query.body.as_select().unwrap().prewhere.as_ref(); assert_eq!( @@ -708,7 +708,7 @@ fn test_prewhere() { _ => unreachable!(), } - match clickhouse().verified_stmt("SELECT * FROM t PREWHERE x = 1 AND y = 2") { + match clickhouse_and_generic().verified_stmt("SELECT * FROM t PREWHERE x = 1 AND y = 2") { Statement::Query(query) => { let prewhere = query.body.as_select().unwrap().prewhere.as_ref(); assert_eq!(