Skip to content

Commit e3fbfd9

Browse files
authored
MySQL: Support CROSS JOIN constraint (#2025)
1 parent 280f518 commit e3fbfd9

File tree

6 files changed

+62
-6
lines changed

6 files changed

+62
-6
lines changed

src/ast/query.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2333,7 +2333,11 @@ impl fmt::Display for Join {
23332333
self.relation,
23342334
suffix(constraint)
23352335
)),
2336-
JoinOperator::CrossJoin => f.write_fmt(format_args!("CROSS JOIN {}", self.relation)),
2336+
JoinOperator::CrossJoin(constraint) => f.write_fmt(format_args!(
2337+
"CROSS JOIN {}{}",
2338+
self.relation,
2339+
suffix(constraint)
2340+
)),
23372341
JoinOperator::Semi(constraint) => f.write_fmt(format_args!(
23382342
"{}SEMI JOIN {}{}",
23392343
prefix(constraint),
@@ -2400,7 +2404,8 @@ pub enum JoinOperator {
24002404
Right(JoinConstraint),
24012405
RightOuter(JoinConstraint),
24022406
FullOuter(JoinConstraint),
2403-
CrossJoin,
2407+
/// CROSS (constraint is non-standard)
2408+
CrossJoin(JoinConstraint),
24042409
/// SEMI (non-standard)
24052410
Semi(JoinConstraint),
24062411
/// LEFT SEMI (non-standard)

src/ast/spans.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2226,7 +2226,7 @@ impl Spanned for JoinOperator {
22262226
JoinOperator::Right(join_constraint) => join_constraint.span(),
22272227
JoinOperator::RightOuter(join_constraint) => join_constraint.span(),
22282228
JoinOperator::FullOuter(join_constraint) => join_constraint.span(),
2229-
JoinOperator::CrossJoin => Span::empty(),
2229+
JoinOperator::CrossJoin(join_constraint) => join_constraint.span(),
22302230
JoinOperator::LeftSemi(join_constraint) => join_constraint.span(),
22312231
JoinOperator::RightSemi(join_constraint) => join_constraint.span(),
22322232
JoinOperator::LeftAnti(join_constraint) => join_constraint.span(),

src/dialect/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,11 @@ pub trait Dialect: Debug + Any {
311311
false
312312
}
313313

314+
/// Returns true if the dialect supports a join specification on CROSS JOIN.
315+
fn supports_cross_join_constraint(&self) -> bool {
316+
false
317+
}
318+
314319
/// Returns true if the dialect supports CONNECT BY.
315320
fn supports_connect_by(&self) -> bool {
316321
false

src/dialect/mysql.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,10 @@ impl Dialect for MySqlDialect {
163163
fn supports_data_type_signed_suffix(&self) -> bool {
164164
true
165165
}
166+
167+
fn supports_cross_join_constraint(&self) -> bool {
168+
true
169+
}
166170
}
167171

168172
/// `LOCK TABLES`

src/parser/mod.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13317,15 +13317,24 @@ impl<'a> Parser<'a> {
1331713317
let global = self.parse_keyword(Keyword::GLOBAL);
1331813318
let join = if self.parse_keyword(Keyword::CROSS) {
1331913319
let join_operator = if self.parse_keyword(Keyword::JOIN) {
13320-
JoinOperator::CrossJoin
13320+
JoinOperator::CrossJoin(JoinConstraint::None)
1332113321
} else if self.parse_keyword(Keyword::APPLY) {
1332213322
// MSSQL extension, similar to CROSS JOIN LATERAL
1332313323
JoinOperator::CrossApply
1332413324
} else {
1332513325
return self.expected("JOIN or APPLY after CROSS", self.peek_token());
1332613326
};
13327+
let relation = self.parse_table_factor()?;
13328+
let join_operator = if matches!(join_operator, JoinOperator::CrossJoin(_))
13329+
&& self.dialect.supports_cross_join_constraint()
13330+
{
13331+
let constraint = self.parse_join_constraint(false)?;
13332+
JoinOperator::CrossJoin(constraint)
13333+
} else {
13334+
join_operator
13335+
};
1332713336
Join {
13328-
relation: self.parse_table_factor()?,
13337+
relation,
1332913338
global,
1333013339
join_operator,
1333113340
}

tests/sqlparser_common.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7131,12 +7131,45 @@ fn parse_cross_join() {
71317131
Join {
71327132
relation: table_from_name(ObjectName::from(vec![Ident::new("t2")])),
71337133
global: false,
7134-
join_operator: JoinOperator::CrossJoin,
7134+
join_operator: JoinOperator::CrossJoin(JoinConstraint::None),
71357135
},
71367136
only(only(select.from).joins),
71377137
);
71387138
}
71397139

7140+
#[test]
7141+
fn parse_cross_join_constraint() {
7142+
fn join_with_constraint(constraint: JoinConstraint) -> Join {
7143+
Join {
7144+
relation: table_from_name(ObjectName::from(vec![Ident::new("t2")])),
7145+
global: false,
7146+
join_operator: JoinOperator::CrossJoin(constraint),
7147+
}
7148+
}
7149+
7150+
fn test_constraint(sql: &str, constraint: JoinConstraint) {
7151+
let dialect = all_dialects_where(|d| d.supports_cross_join_constraint());
7152+
let select = dialect.verified_only_select(sql);
7153+
assert_eq!(
7154+
join_with_constraint(constraint),
7155+
only(only(select.from).joins),
7156+
);
7157+
}
7158+
7159+
test_constraint(
7160+
"SELECT * FROM t1 CROSS JOIN t2 ON a = b",
7161+
JoinConstraint::On(Expr::BinaryOp {
7162+
left: Box::new(Expr::Identifier(Ident::new("a"))),
7163+
op: BinaryOperator::Eq,
7164+
right: Box::new(Expr::Identifier(Ident::new("b"))),
7165+
}),
7166+
);
7167+
test_constraint(
7168+
"SELECT * FROM t1 CROSS JOIN t2 USING(a)",
7169+
JoinConstraint::Using(vec![ObjectName::from(vec![Ident::new("a")])]),
7170+
);
7171+
}
7172+
71407173
#[test]
71417174
fn parse_joins_on() {
71427175
fn join_with_constraint(

0 commit comments

Comments
 (0)