diff --git a/src/ast/query.rs b/src/ast/query.rs index 151b94e8..3b414cc6 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -2333,7 +2333,11 @@ impl fmt::Display for Join { self.relation, suffix(constraint) )), - JoinOperator::CrossJoin => f.write_fmt(format_args!("CROSS JOIN {}", self.relation)), + JoinOperator::CrossJoin(constraint) => f.write_fmt(format_args!( + "CROSS JOIN {}{}", + self.relation, + suffix(constraint) + )), JoinOperator::Semi(constraint) => f.write_fmt(format_args!( "{}SEMI JOIN {}{}", prefix(constraint), @@ -2400,7 +2404,8 @@ pub enum JoinOperator { Right(JoinConstraint), RightOuter(JoinConstraint), FullOuter(JoinConstraint), - CrossJoin, + /// CROSS (constraint is non-standard) + CrossJoin(JoinConstraint), /// SEMI (non-standard) Semi(JoinConstraint), /// LEFT SEMI (non-standard) diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 5d3694be..4e15edd8 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -2226,7 +2226,7 @@ impl Spanned for JoinOperator { JoinOperator::Right(join_constraint) => join_constraint.span(), JoinOperator::RightOuter(join_constraint) => join_constraint.span(), JoinOperator::FullOuter(join_constraint) => join_constraint.span(), - JoinOperator::CrossJoin => Span::empty(), + JoinOperator::CrossJoin(join_constraint) => join_constraint.span(), JoinOperator::LeftSemi(join_constraint) => join_constraint.span(), JoinOperator::RightSemi(join_constraint) => join_constraint.span(), JoinOperator::LeftAnti(join_constraint) => join_constraint.span(), diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 3c7be8f7..ef4e1cdd 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -311,6 +311,11 @@ pub trait Dialect: Debug + Any { false } + /// Returns true if the dialect supports a join specification on CROSS JOIN. + fn supports_cross_join_constraint(&self) -> bool { + false + } + /// Returns true if the dialect supports CONNECT BY. fn supports_connect_by(&self) -> bool { false diff --git a/src/dialect/mysql.rs b/src/dialect/mysql.rs index 8c63bfda..8d2a5ad4 100644 --- a/src/dialect/mysql.rs +++ b/src/dialect/mysql.rs @@ -163,6 +163,10 @@ impl Dialect for MySqlDialect { fn supports_data_type_signed_suffix(&self) -> bool { true } + + fn supports_cross_join_constraint(&self) -> bool { + true + } } /// `LOCK TABLES` diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 1bc2a3d6..819819f9 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -13317,15 +13317,24 @@ impl<'a> Parser<'a> { 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 + JoinOperator::CrossJoin(JoinConstraint::None) } else if self.parse_keyword(Keyword::APPLY) { // MSSQL extension, similar to CROSS JOIN LATERAL JoinOperator::CrossApply } else { return self.expected("JOIN or APPLY after CROSS", self.peek_token()); }; + let relation = self.parse_table_factor()?; + let join_operator = if matches!(join_operator, JoinOperator::CrossJoin(_)) + && self.dialect.supports_cross_join_constraint() + { + let constraint = self.parse_join_constraint(false)?; + JoinOperator::CrossJoin(constraint) + } else { + join_operator + }; Join { - relation: self.parse_table_factor()?, + relation, global, join_operator, } diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index aa47c0f7..720c1e49 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -7131,12 +7131,45 @@ fn parse_cross_join() { Join { relation: table_from_name(ObjectName::from(vec![Ident::new("t2")])), global: false, - join_operator: JoinOperator::CrossJoin, + join_operator: JoinOperator::CrossJoin(JoinConstraint::None), }, only(only(select.from).joins), ); } +#[test] +fn parse_cross_join_constraint() { + fn join_with_constraint(constraint: JoinConstraint) -> Join { + Join { + relation: table_from_name(ObjectName::from(vec![Ident::new("t2")])), + global: false, + join_operator: JoinOperator::CrossJoin(constraint), + } + } + + fn test_constraint(sql: &str, constraint: JoinConstraint) { + let dialect = all_dialects_where(|d| d.supports_cross_join_constraint()); + let select = dialect.verified_only_select(sql); + assert_eq!( + join_with_constraint(constraint), + only(only(select.from).joins), + ); + } + + test_constraint( + "SELECT * FROM t1 CROSS JOIN t2 ON a = b", + JoinConstraint::On(Expr::BinaryOp { + left: Box::new(Expr::Identifier(Ident::new("a"))), + op: BinaryOperator::Eq, + right: Box::new(Expr::Identifier(Ident::new("b"))), + }), + ); + test_constraint( + "SELECT * FROM t1 CROSS JOIN t2 USING(a)", + JoinConstraint::Using(vec![ObjectName::from(vec![Ident::new("a")])]), + ); +} + #[test] fn parse_joins_on() { fn join_with_constraint(