diff --git a/src/ast/query.rs b/src/ast/query.rs index 978604266..b318f686a 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -1537,6 +1537,9 @@ impl Display for TableVersion { #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub struct Join { pub relation: TableFactor, + /// ClickHouse supports the optional `GLOBAL` keyword before the join operator. + /// See [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/select/join) + pub global: bool, pub join_operator: JoinOperator, } @@ -1563,6 +1566,10 @@ impl fmt::Display for Join { } Suffix(constraint) } + if self.global { + write!(f, " GLOBAL")?; + } + match &self.join_operator { JoinOperator::Inner(constraint) => write!( f, diff --git a/src/keywords.rs b/src/keywords.rs index 4b599f12a..ee2bd6173 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -850,6 +850,7 @@ pub const RESERVED_FOR_TABLE_ALIAS: &[Keyword] = &[ Keyword::USING, Keyword::CLUSTER, Keyword::DISTRIBUTE, + Keyword::GLOBAL, // for MSSQL-specific OUTER APPLY (seems reserved in most dialects) Keyword::OUTER, Keyword::SET, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index f8267a7cb..fe0a3e6b6 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -9003,6 +9003,7 @@ impl<'a> Parser<'a> { // a table alias. let mut joins = vec![]; loop { + let global = self.parse_keyword(Keyword::GLOBAL); let join = if self.parse_keyword(Keyword::CROSS) { let join_operator = if self.parse_keyword(Keyword::JOIN) { JoinOperator::CrossJoin @@ -9014,6 +9015,7 @@ impl<'a> Parser<'a> { }; Join { relation: self.parse_table_factor()?, + global, join_operator, } } else if self.parse_keyword(Keyword::OUTER) { @@ -9021,6 +9023,7 @@ impl<'a> Parser<'a> { self.expect_keyword(Keyword::APPLY)?; Join { relation: self.parse_table_factor()?, + global, join_operator: JoinOperator::OuterApply, } } else if self.parse_keyword(Keyword::ASOF) { @@ -9030,6 +9033,7 @@ impl<'a> Parser<'a> { let match_condition = self.parse_parenthesized(Self::parse_expr)?; Join { relation, + global, join_operator: JoinOperator::AsOf { match_condition, constraint: self.parse_join_constraint(false)?, @@ -9115,6 +9119,7 @@ impl<'a> Parser<'a> { let join_constraint = self.parse_join_constraint(natural)?; Join { relation, + global, join_operator: join_operator_type(join_constraint), } }; diff --git a/src/test_utils.rs b/src/test_utils.rs index 5ed6339bd..b8e9ecee4 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -331,6 +331,7 @@ pub fn table_with_alias(name: impl Into, alias: impl Into) -> Ta pub fn join(relation: TableFactor) -> Join { Join { relation, + global: false, join_operator: JoinOperator::Inner(JoinConstraint::Natural), } } diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs index 089a41889..a0dd5a662 100644 --- a/tests/sqlparser_bigquery.rs +++ b/tests/sqlparser_bigquery.rs @@ -1557,6 +1557,7 @@ fn parse_join_constraint_unnest_alias() { with_offset_alias: None, with_ordinality: false, }, + global: false, join_operator: JoinOperator::Inner(JoinConstraint::On(Expr::BinaryOp { left: Box::new(Expr::Identifier("c1".into())), op: BinaryOperator::Eq, diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index dd3ed0515..c8e46fe8d 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -5582,6 +5582,7 @@ fn parse_implicit_join() { partitions: vec![], with_ordinality: false, }, + global: false, join_operator: JoinOperator::Inner(JoinConstraint::Natural), }], }, @@ -5605,6 +5606,7 @@ fn parse_implicit_join() { partitions: vec![], with_ordinality: false, }, + global: false, join_operator: JoinOperator::Inner(JoinConstraint::Natural), }], }, @@ -5628,6 +5630,7 @@ fn parse_cross_join() { partitions: vec![], with_ordinality: false, }, + global: false, join_operator: JoinOperator::CrossJoin, }, only(only(select.from).joins), @@ -5639,6 +5642,7 @@ fn parse_joins_on() { fn join_with_constraint( relation: impl Into, alias: Option, + global: bool, f: impl Fn(JoinConstraint) -> JoinOperator, ) -> Join { Join { @@ -5651,6 +5655,7 @@ fn parse_joins_on() { partitions: vec![], with_ordinality: false, }, + global, join_operator: f(JoinConstraint::On(Expr::BinaryOp { left: Box::new(Expr::Identifier("c1".into())), op: BinaryOperator::Eq, @@ -5664,6 +5669,7 @@ fn parse_joins_on() { vec![join_with_constraint( "t2", table_alias("foo"), + false, JoinOperator::Inner, )] ); @@ -5674,35 +5680,80 @@ fn parse_joins_on() { // Test parsing of different join operators assert_eq!( only(&verified_only_select("SELECT * FROM t1 JOIN t2 ON c1 = c2").from).joins, - vec![join_with_constraint("t2", None, JoinOperator::Inner)] + vec![join_with_constraint("t2", None, false, JoinOperator::Inner)] ); assert_eq!( only(&verified_only_select("SELECT * FROM t1 LEFT JOIN t2 ON c1 = c2").from).joins, - vec![join_with_constraint("t2", None, JoinOperator::LeftOuter)] + vec![join_with_constraint( + "t2", + None, + false, + JoinOperator::LeftOuter + )] ); assert_eq!( only(&verified_only_select("SELECT * FROM t1 RIGHT JOIN t2 ON c1 = c2").from).joins, - vec![join_with_constraint("t2", None, JoinOperator::RightOuter)] + vec![join_with_constraint( + "t2", + None, + false, + JoinOperator::RightOuter + )] ); assert_eq!( only(&verified_only_select("SELECT * FROM t1 LEFT SEMI JOIN t2 ON c1 = c2").from).joins, - vec![join_with_constraint("t2", None, JoinOperator::LeftSemi)] + vec![join_with_constraint( + "t2", + None, + false, + JoinOperator::LeftSemi + )] ); assert_eq!( only(&verified_only_select("SELECT * FROM t1 RIGHT SEMI JOIN t2 ON c1 = c2").from).joins, - vec![join_with_constraint("t2", None, JoinOperator::RightSemi)] + vec![join_with_constraint( + "t2", + None, + false, + JoinOperator::RightSemi + )] ); assert_eq!( only(&verified_only_select("SELECT * FROM t1 LEFT ANTI JOIN t2 ON c1 = c2").from).joins, - vec![join_with_constraint("t2", None, JoinOperator::LeftAnti)] + vec![join_with_constraint( + "t2", + None, + false, + JoinOperator::LeftAnti + )] ); assert_eq!( only(&verified_only_select("SELECT * FROM t1 RIGHT ANTI JOIN t2 ON c1 = c2").from).joins, - vec![join_with_constraint("t2", None, JoinOperator::RightAnti)] + vec![join_with_constraint( + "t2", + None, + false, + JoinOperator::RightAnti + )] ); assert_eq!( only(&verified_only_select("SELECT * FROM t1 FULL JOIN t2 ON c1 = c2").from).joins, - vec![join_with_constraint("t2", None, JoinOperator::FullOuter)] + vec![join_with_constraint( + "t2", + None, + false, + JoinOperator::FullOuter + )] + ); + + assert_eq!( + only(&verified_only_select("SELECT * FROM t1 GLOBAL FULL JOIN t2 ON c1 = c2").from).joins, + vec![join_with_constraint( + "t2", + None, + true, + JoinOperator::FullOuter + )] ); } @@ -5723,6 +5774,7 @@ fn parse_joins_using() { partitions: vec![], with_ordinality: false, }, + global: false, join_operator: f(JoinConstraint::Using(vec!["c1".into()])), } } @@ -5787,6 +5839,7 @@ fn parse_natural_join() { partitions: vec![], with_ordinality: false, }, + global: false, join_operator: f(JoinConstraint::Natural), } } @@ -6055,6 +6108,7 @@ fn parse_derived_tables() { partitions: vec![], with_ordinality: false, }, + global: false, join_operator: JoinOperator::Inner(JoinConstraint::Natural), }], }), @@ -6965,6 +7019,7 @@ fn lateral_function() { ], alias: None, }, + global: false, join_operator: JoinOperator::LeftOuter(JoinConstraint::None), }], }], diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index b0b29f347..1c9c009d9 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -1891,6 +1891,7 @@ fn parse_update_with_joins() { partitions: vec![], with_ordinality: false, }, + global: false, join_operator: JoinOperator::Inner(JoinConstraint::On(Expr::BinaryOp { left: Box::new(Expr::CompoundIdentifier(vec![ Ident::new("o"), diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 5ac421da0..d1953f9ae 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4102,6 +4102,7 @@ fn parse_join_constraint_unnest_alias() { with_offset_alias: None, with_ordinality: false, }, + global: false, join_operator: JoinOperator::Inner(JoinConstraint::On(Expr::BinaryOp { left: Box::new(Expr::Identifier("c1".into())), op: BinaryOperator::Eq, diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index 7a2288cbb..26245af1e 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -2206,6 +2206,7 @@ fn asof_joins() { relation: table_with_alias("trades_unixtime", "tu"), joins: vec![Join { relation: table_with_alias("quotes_unixtime", "qu"), + global: false, join_operator: JoinOperator::AsOf { match_condition: Expr::BinaryOp { left: Box::new(Expr::CompoundIdentifier(vec![