diff --git a/src/dialect/postgresql.rs b/src/dialect/postgresql.rs index e980fc642..d342276e4 100644 --- a/src/dialect/postgresql.rs +++ b/src/dialect/postgresql.rs @@ -299,6 +299,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/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 9ee6ab3d6..9efa1daca 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -9582,3 +9582,20 @@ fn parse_limit_after_locking_clause() { // The pre-existing ordering keeps round-tripping unchanged. pg().verified_stmt("SELECT * FROM t ORDER BY id LIMIT 5 FOR UPDATE SKIP LOCKED"); } + +#[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"); +}