From a67b2dea4812efe7dd50e9a26639c76c3701baa3 Mon Sep 17 00:00:00 2001 From: Constantine Peresypkin Date: Sat, 13 May 2023 22:58:36 -0400 Subject: [PATCH] Fix(clickhouse): join type/kind ordering issues (#1614) CH can parse both `LEFT ANY JOIN` and `ANY LEFT JOIN` successfully But the canonical order (by the docs) is `LEFT ANY JOIN` This PR parses "inverted" order and produces a canonical one --- sqlglot/dialects/clickhouse.py | 19 +++++++++++++++++-- tests/dialects/test_clickhouse.py | 22 ++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/sqlglot/dialects/clickhouse.py b/sqlglot/dialects/clickhouse.py index ac0c0f9c2a..379beaaa4a 100644 --- a/sqlglot/dialects/clickhouse.py +++ b/sqlglot/dialects/clickhouse.py @@ -73,10 +73,19 @@ class Parser(parser.Parser): COLUMN_OPERATORS = parser.Parser.COLUMN_OPERATORS.copy() COLUMN_OPERATORS.pop(TokenType.PLACEHOLDER) - JOIN_KINDS = {*parser.Parser.JOIN_KINDS, TokenType.ANY, TokenType.ASOF} + JOIN_KINDS = { + *parser.Parser.JOIN_KINDS, + TokenType.ANY, + TokenType.ASOF, + TokenType.ANTI, + TokenType.SEMI, + } TABLE_ALIAS_TOKENS = {*parser.Parser.TABLE_ALIAS_TOKENS} - { TokenType.ANY, + TokenType.ASOF, + TokenType.SEMI, + TokenType.ANTI, TokenType.SETTINGS, TokenType.FORMAT, } @@ -146,8 +155,14 @@ def _parse_cte(self) -> exp.Expression: def _parse_join_side_and_kind( self, ) -> t.Tuple[t.Optional[Token], t.Optional[Token], t.Optional[Token]]: + is_global = self._match(TokenType.GLOBAL) and self._prev + kind_pre = self._match_set(self.JOIN_KINDS, advance=False) and self._prev + if kind_pre: + kind = self._match_set(self.JOIN_KINDS) and self._prev + side = self._match_set(self.JOIN_SIDES) and self._prev + return is_global, side, kind return ( - self._match(TokenType.GLOBAL) and self._prev, + is_global, self._match_set(self.JOIN_SIDES) and self._prev, self._match_set(self.JOIN_KINDS) and self._prev, ) diff --git a/tests/dialects/test_clickhouse.py b/tests/dialects/test_clickhouse.py index c7692386ca..65fc928c41 100644 --- a/tests/dialects/test_clickhouse.py +++ b/tests/dialects/test_clickhouse.py @@ -76,6 +76,28 @@ def test_clickhouse(self): self.validate_identity( "SELECT * FROM x LIMIT 10 SETTINGS max_results = 100, result_ FORMAT PrettyCompact" ) + self.validate_all( + "SELECT * FROM foo ANY LEFT JOIN bla ON foo.c1 = bla.c2", + write={"clickhouse": "SELECT * FROM foo LEFT ANY JOIN bla ON foo.c1 = bla.c2"}, + ) + self.validate_all( + "SELECT * FROM foo GLOBAL ANY LEFT JOIN bla ON foo.c1 = bla.c2", + write={"clickhouse": "SELECT * FROM foo GLOBAL LEFT ANY JOIN bla ON foo.c1 = bla.c2"}, + ) + self.validate_all( + """ + SELECT + loyalty, + count() + FROM hits SEMI LEFT JOIN users USING (UserID) + GROUP BY loyalty + ORDER BY loyalty ASC + """, + write={ + "clickhouse": "SELECT loyalty, COUNT() FROM hits LEFT SEMI JOIN users USING (UserID)" + + " GROUP BY loyalty ORDER BY loyalty" + }, + ) def test_cte(self): self.validate_identity("WITH 'x' AS foo SELECT foo")