From 078bed42a7e2f15f54becf70af7cc83d658a8f3f Mon Sep 17 00:00:00 2001 From: Mosha Pasumansky Date: Sun, 14 Jun 2026 17:46:17 -0700 Subject: [PATCH] PostgreSQL: support right-deep join chains (deferred ON clauses) PostgreSQL accepts `t0 JOIN t1 JOIN t2 ON c1 ON c2` where ON clauses follow all JOIN keywords, associating right-to-left. Enable this by overriding supports_left_associative_joins_without_parens() to false for PostgresqlDialect, matching Snowflake's existing behavior. Co-Authored-By: Claude Sonnet 4.6 --- src/dialect/postgresql.rs | 6 ++++++ tests/sqlparser_postgres.rs | 17 +++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/dialect/postgresql.rs b/src/dialect/postgresql.rs index 8b52ef6e3..e3c25c447 100644 --- a/src/dialect/postgresql.rs +++ b/src/dialect/postgresql.rs @@ -290,6 +290,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 713d465a8..9972725f0 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -9261,3 +9261,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"); +}