diff --git a/src/dialect/postgresql.rs b/src/dialect/postgresql.rs index fda676eb26..2a9e79dd17 100644 --- a/src/dialect/postgresql.rs +++ b/src/dialect/postgresql.rs @@ -286,6 +286,12 @@ impl Dialect for PostgreSqlDialect { true } + /// PostgreSQL supports right-deep join chains: `t0 JOIN t1 JOIN t2 ON c1 ON c2` + /// See: + fn supports_left_associative_joins_without_parens(&self) -> bool { + false + } + /// Postgres supports `NOTNULL` as an alias for `IS NOT NULL` /// See: fn supports_notnull_operator(&self) -> bool { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 0f21d777d7..511a419e86 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -16001,6 +16001,7 @@ impl<'a> Parser<'a> { if !self .dialect .supports_left_associative_joins_without_parens() + && !natural && self.peek_parens_less_nested_join() { let joins = self.parse_joins()?; diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 67421070ac..76e12306b7 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -17329,8 +17329,7 @@ fn column_check_enforced() { #[test] fn join_precedence() { - all_dialects_except(|d| !d.supports_left_associative_joins_without_parens()) - .verified_query_with_canonical( + all_dialects().verified_query_with_canonical( "SELECT * FROM t1 NATURAL JOIN t5 @@ -17339,15 +17338,6 @@ fn join_precedence() { // canonical string without parentheses "SELECT * FROM t1 NATURAL JOIN t5 INNER JOIN t0 ON (t0.v1 + t5.v0) > 0 WHERE t0.v1 = t1.v0", ); - all_dialects_except(|d| d.supports_left_associative_joins_without_parens()).verified_query_with_canonical( - "SELECT * - FROM t1 - NATURAL JOIN t5 - INNER JOIN t0 ON (t0.v1 + t5.v0) > 0 - WHERE t0.v1 = t1.v0", - // canonical string with parentheses - "SELECT * FROM t1 NATURAL JOIN (t5 INNER JOIN t0 ON (t0.v1 + t5.v0) > 0) WHERE t0.v1 = t1.v0", - ); } #[test] diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 86315b1ef9..6edf9df5a1 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -9221,3 +9221,20 @@ fn parse_lock_table() { } } } + +#[test] +fn parse_right_deep_join_chain() { + // PostgreSQL supports right-deep join syntax where ON clauses follow all JOIN keywords: + // t0 JOIN t1 JOIN t2 ON c1 ON c2 + // which is equivalent to (and serialized as) t0 JOIN (t1 JOIN t2 ON c1) ON c2. + pg().one_statement_parses_to( + "SELECT * FROM t0 INNER JOIN t1 INNER JOIN t2 ON true ON true", + "SELECT * FROM t0 INNER JOIN (t1 INNER JOIN t2 ON true) ON true", + ); + pg().one_statement_parses_to( + "SELECT * FROM t0 INNER JOIN t1 INNER JOIN t2 INNER JOIN t3 ON true ON true ON true", + "SELECT * FROM t0 INNER JOIN (t1 INNER JOIN (t2 INNER JOIN t3 ON true) ON true) ON true", + ); + // NATURAL JOIN followed by a constrained join must stay left-associative. + pg().verified_stmt("SELECT * FROM t0 NATURAL JOIN t1 INNER JOIN t2 ON true"); +}