Skip to content

Commit 3bc9423

Browse files
authored
Fix join precedence for non-snowflake queries (apache#1905)
1 parent 50c605a commit 3bc9423

File tree

5 files changed

+64
-1
lines changed

5 files changed

+64
-1
lines changed

src/dialect/generic.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ impl Dialect for GenericDialect {
5252
true
5353
}
5454

55+
fn supports_left_associative_joins_without_parens(&self) -> bool {
56+
true
57+
}
58+
5559
fn supports_connect_by(&self) -> bool {
5660
true
5761
}

src/dialect/mod.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,34 @@ pub trait Dialect: Debug + Any {
278278
false
279279
}
280280

281+
/// Indicates whether the dialect supports left-associative join parsing
282+
/// by default when parentheses are omitted in nested joins.
283+
///
284+
/// Most dialects (like MySQL or Postgres) assume **left-associative** precedence,
285+
/// so a query like:
286+
///
287+
/// ```sql
288+
/// SELECT * FROM t1 NATURAL JOIN t5 INNER JOIN t0 ON ...
289+
/// ```
290+
/// is interpreted as:
291+
/// ```sql
292+
/// ((t1 NATURAL JOIN t5) INNER JOIN t0 ON ...)
293+
/// ```
294+
/// and internally represented as a **flat list** of joins.
295+
///
296+
/// In contrast, some dialects (e.g. **Snowflake**) assume **right-associative**
297+
/// precedence and interpret the same query as:
298+
/// ```sql
299+
/// (t1 NATURAL JOIN (t5 INNER JOIN t0 ON ...))
300+
/// ```
301+
/// which results in a **nested join** structure in the AST.
302+
///
303+
/// If this method returns `false`, the parser must build nested join trees
304+
/// even in the absence of parentheses to reflect the correct associativity
305+
fn supports_left_associative_joins_without_parens(&self) -> bool {
306+
true
307+
}
308+
281309
/// Returns true if the dialect supports the `(+)` syntax for OUTER JOIN.
282310
fn supports_outer_join_operator(&self) -> bool {
283311
false

src/dialect/snowflake.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,10 @@ impl Dialect for SnowflakeDialect {
283283
true
284284
}
285285

286+
fn supports_left_associative_joins_without_parens(&self) -> bool {
287+
false
288+
}
289+
286290
fn is_reserved_for_identifier(&self, kw: Keyword) -> bool {
287291
// Unreserve some keywords that Snowflake accepts as identifiers
288292
// See: https://docs.snowflake.com/en/sql-reference/reserved-keywords

src/parser/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12495,7 +12495,11 @@ impl<'a> Parser<'a> {
1249512495
};
1249612496
let mut relation = self.parse_table_factor()?;
1249712497

12498-
if self.peek_parens_less_nested_join() {
12498+
if !self
12499+
.dialect
12500+
.supports_left_associative_joins_without_parens()
12501+
&& self.peek_parens_less_nested_join()
12502+
{
1249912503
let joins = self.parse_joins()?;
1250012504
relation = TableFactor::NestedJoin {
1250112505
table_with_joins: Box::new(TableWithJoins { relation, joins }),

tests/sqlparser_common.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15359,6 +15359,29 @@ fn check_enforced() {
1535915359
);
1536015360
}
1536115361

15362+
#[test]
15363+
fn join_precedence() {
15364+
all_dialects_except(|d| !d.supports_left_associative_joins_without_parens())
15365+
.verified_query_with_canonical(
15366+
"SELECT *
15367+
FROM t1
15368+
NATURAL JOIN t5
15369+
INNER JOIN t0 ON (t0.v1 + t5.v0) > 0
15370+
WHERE t0.v1 = t1.v0",
15371+
// canonical string without parentheses
15372+
"SELECT * FROM t1 NATURAL JOIN t5 INNER JOIN t0 ON (t0.v1 + t5.v0) > 0 WHERE t0.v1 = t1.v0",
15373+
);
15374+
all_dialects_except(|d| d.supports_left_associative_joins_without_parens()).verified_query_with_canonical(
15375+
"SELECT *
15376+
FROM t1
15377+
NATURAL JOIN t5
15378+
INNER JOIN t0 ON (t0.v1 + t5.v0) > 0
15379+
WHERE t0.v1 = t1.v0",
15380+
// canonical string with parentheses
15381+
"SELECT * FROM t1 NATURAL JOIN (t5 INNER JOIN t0 ON (t0.v1 + t5.v0) > 0) WHERE t0.v1 = t1.v0",
15382+
);
15383+
}
15384+
1536215385
#[test]
1536315386
fn parse_create_procedure_with_language() {
1536415387
let sql = r#"CREATE PROCEDURE test_proc LANGUAGE sql AS BEGIN SELECT 1; END"#;

0 commit comments

Comments
 (0)