From bf94ce317497ab92e9fe0562b3034f3482601072 Mon Sep 17 00:00:00 2001 From: Toby Mao Date: Wed, 27 Mar 2024 18:49:36 -0700 Subject: [PATCH] fix: > 1 nested joins closes #3231 --- sqlglot/generator.py | 4 +++- sqlglot/parser.py | 21 +++++++++++++-------- tests/fixtures/pretty.sql | 11 +++++++++++ tests/test_transpile.py | 2 +- 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/sqlglot/generator.py b/sqlglot/generator.py index 5d3acaf3f..0ceff4005 100644 --- a/sqlglot/generator.py +++ b/sqlglot/generator.py @@ -1636,7 +1636,9 @@ def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: hints = f" {hints}" if hints and self.TABLE_HINTS else "" pivots = self.expressions(expression, key="pivots", sep=" ", flat=True) pivots = f" {pivots}" if pivots else "" - joins = self.expressions(expression, key="joins", sep="", skip_first=True) + joins = self.indent( + self.expressions(expression, key="joins", sep="", flat=True), skip_first=True + ) laterals = self.expressions(expression, key="laterals", sep="") file_format = self.sql(expression, "format") diff --git a/sqlglot/parser.py b/sqlglot/parser.py index fae0177a5..01d133462 100644 --- a/sqlglot/parser.py +++ b/sqlglot/parser.py @@ -2658,7 +2658,7 @@ def _parse_query_modifiers( self, this: t.Optional[exp.Expression] ) -> t.Optional[exp.Expression]: if isinstance(this, (exp.Query, exp.Table)): - for join in iter(self._parse_join, None): + for join in self._parse_joins(): this.append("joins", join) for lateral in iter(self._parse_lateral, None): this.append("laterals", lateral) @@ -2903,19 +2903,21 @@ def _parse_join( kwargs["on"] = self._parse_conjunction() elif self._match(TokenType.USING): kwargs["using"] = self._parse_wrapped_id_vars() - elif not (kind and kind.token_type == TokenType.CROSS): + elif not isinstance(kwargs["this"], exp.Unnest) and not ( + kind and kind.token_type == TokenType.CROSS + ): index = self._index - join = self._parse_join() + joins: t.Optional[list] = list(self._parse_joins()) - if join and self._match(TokenType.ON): + if joins and self._match(TokenType.ON): kwargs["on"] = self._parse_conjunction() - elif join and self._match(TokenType.USING): + elif joins and self._match(TokenType.USING): kwargs["using"] = self._parse_wrapped_id_vars() else: - join = None + joins = None self._retreat(index) - kwargs["this"].set("joins", [join] if join else None) + kwargs["this"].set("joins", joins if joins else None) comments = [c for token in (method, side, kind) if token for c in token.comments] return self.expression(exp.Join, comments=comments, **kwargs) @@ -3152,7 +3154,7 @@ def _parse_table( this = table_sample if joins: - for join in iter(self._parse_join, None): + for join in self._parse_joins(): this.append("joins", join) if self._match_pair(TokenType.WITH, TokenType.ORDINALITY): @@ -3301,6 +3303,9 @@ def _parse_table_sample(self, as_modifier: bool = False) -> t.Optional[exp.Table def _parse_pivots(self) -> t.Optional[t.List[exp.Pivot]]: return list(iter(self._parse_pivot, None)) or None + def _parse_joins(self) -> t.Iterator[exp.Join]: + return iter(self._parse_join, None) + # https://duckdb.org/docs/sql/statements/pivot def _parse_simplified_pivot(self) -> exp.Pivot: def _parse_on() -> t.Optional[exp.Expression]: diff --git a/tests/fixtures/pretty.sql b/tests/fixtures/pretty.sql index 23d951126..fac08bede 100644 --- a/tests/fixtures/pretty.sql +++ b/tests/fixtures/pretty.sql @@ -384,3 +384,14 @@ JOIN b CROSS JOIN d JOIN e ON d.id = e.id; + +SELECT * FROM a JOIN b JOIN c USING (e) JOIN d USING (f) USING (g); +SELECT + * +FROM a +JOIN b + JOIN c + USING (e) + JOIN d + USING (f) + USING (g); diff --git a/tests/test_transpile.py b/tests/test_transpile.py index 743c08dac..0c65da4c6 100644 --- a/tests/test_transpile.py +++ b/tests/test_transpile.py @@ -518,9 +518,9 @@ def test_comments(self): * FROM x INNER JOIN y + /* inner join z */ LEFT JOIN z USING (id) - /* inner join z */ USING (id)""", pretty=True, )