Repository: tobymao/sqlglot

Issue:
Clickhouse: Ternary if parsing
Minimal code to reproduce:
```python
import sqlglot

sqlglot.parse_one(
    "flagcolumn ? created : now()",
    read="clickhouse"
)
```

While trying to parse expression with clickhouse ternary if, get the following error:
```
sqlglot.errors.ParseError: Invalid expression / Unexpected token. Line 1, Col: 28.
  flagcolumn ? created : now()
```

Documentation:
https://clickhouse.com/docs/en/sql-reference/functions/conditional-functions


Discussion:

Patch:
diff --git a/sqlglot/dialects/bigquery.py b/sqlglot/dialects/bigquery.py
index 9705b35f..ab10052c 100644
--- a/sqlglot/dialects/bigquery.py
+++ b/sqlglot/dialects/bigquery.py
@@ -230,6 +230,7 @@ class BigQuery(Dialect):
         JOIN_HINTS = False
         TABLE_HINTS = False
         LIMIT_FETCH = "LIMIT"
+        RENAME_TABLE_WITH_DB = False
 
         TRANSFORMS = {
             **generator.Generator.TRANSFORMS,  # type: ignore
diff --git a/sqlglot/dialects/clickhouse.py b/sqlglot/dialects/clickhouse.py
index 0889522b..95dbbe86 100644
--- a/sqlglot/dialects/clickhouse.py
+++ b/sqlglot/dialects/clickhouse.py
@@ -78,12 +78,33 @@ class ClickHouse(Dialect):
             and self._parse_in(this, is_global=True),
         }
 
-        JOIN_KINDS = {*parser.Parser.JOIN_KINDS, TokenType.ANY, TokenType.ASOF}  # type: ignore
+        # The PLACEHOLDER entry is popped because 1) it doesn't affect Clickhouse (it corresponds to
+        # the postgres-specific JSONBContains parser) and 2) it makes parsing the ternary op simpler.
+        COLUMN_OPERATORS = parser.Parser.COLUMN_OPERATORS.copy()
+        COLUMN_OPERATORS.pop(TokenType.PLACEHOLDER)
 
-        TABLE_ALIAS_TOKENS = {*parser.Parser.TABLE_ALIAS_TOKENS} - {TokenType.ANY}  # type: ignore
+        JOIN_KINDS = {*parser.Parser.JOIN_KINDS, TokenType.ANY, TokenType.ASOF}
+
+        TABLE_ALIAS_TOKENS = {*parser.Parser.TABLE_ALIAS_TOKENS} - {TokenType.ANY}
 
         LOG_DEFAULTS_TO_LN = True
 
+        def _parse_expression(self, explicit_alias: bool = False) -> t.Optional[exp.Expression]:
+            return self._parse_alias(self._parse_ternary(), explicit=explicit_alias)
+
+        def _parse_ternary(self) -> t.Optional[exp.Expression]:
+            this = self._parse_conjunction()
+
+            if self._match(TokenType.PLACEHOLDER):
+                return self.expression(
+                    exp.If,
+                    this=this,
+                    true=self._parse_conjunction(),
+                    false=self._match(TokenType.COLON) and self._parse_conjunction(),
+                )
+
+            return this
+
         def _parse_in(
             self, this: t.Optional[exp.Expression], is_global: bool = False
         ) -> exp.Expression:
diff --git a/sqlglot/dialects/duckdb.py b/sqlglot/dialects/duckdb.py
index 6821c094..0172bd73 100644
--- a/sqlglot/dialects/duckdb.py
+++ b/sqlglot/dialects/duckdb.py
@@ -157,6 +157,7 @@ class DuckDB(Dialect):
         TABLE_HINTS = False
         LIMIT_FETCH = "LIMIT"
         STRUCT_DELIMITER = ("(", ")")
+        RENAME_TABLE_WITH_DB = False
 
         TRANSFORMS = {
             **generator.Generator.TRANSFORMS,
@@ -240,11 +241,3 @@ class DuckDB(Dialect):
             self, expression: exp.TableSample, seed_prefix: str = "SEED", sep=" AS "
         ) -> str:
             return super().tablesample_sql(expression, seed_prefix="REPEATABLE", sep=sep)
-
-        def renametable_sql(self, expression: exp.RenameTable) -> str:
-            """DuckDB only supports renaming a table in the same schema"""
-            return super().renametable_sql(
-                expression.transform(
-                    lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
-                )
-            )
diff --git a/sqlglot/dialects/redshift.py b/sqlglot/dialects/redshift.py
index 855c98bb..09ddf644 100644
--- a/sqlglot/dialects/redshift.py
+++ b/sqlglot/dialects/redshift.py
@@ -77,6 +77,7 @@ class Redshift(Postgres):
     class Generator(Postgres.Generator):
         LOCKING_READS_SUPPORTED = False
         SINGLE_STRING_INTERVAL = True
+        RENAME_TABLE_WITH_DB = False
 
         TYPE_MAPPING = {
             **Postgres.Generator.TYPE_MAPPING,  # type: ignore
@@ -141,16 +142,6 @@ class Redshift(Postgres):
             """Redshift doesn't have `WITH` as part of their with_properties so we remove it"""
             return self.properties(properties, prefix=" ", suffix="")
 
-        def renametable_sql(self, expression: exp.RenameTable) -> str:
-            """Redshift only supports defining the table name itself (not the db) when renaming tables"""
-            expression = expression.copy()
-            target_table = expression.this
-            for arg in target_table.args:
-                if arg != "this":
-                    target_table.set(arg, None)
-            this = self.sql(expression, "this")
-            return f"RENAME TO {this}"
-
         def datatype_sql(self, expression: exp.DataType) -> str:
             """
             Redshift converts the `TEXT` data type to `VARCHAR(255)` by default when people more generally mean
diff --git a/sqlglot/generator.py b/sqlglot/generator.py
index 1f3ffde5..81cbe796 100644
--- a/sqlglot/generator.py
+++ b/sqlglot/generator.py
@@ -134,6 +134,9 @@ class Generator:
     # Whether or not limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH")
     LIMIT_FETCH = "ALL"
 
+    # Whether a table is allowed to be renamed with a db
+    RENAME_TABLE_WITH_DB = True
+
     TYPE_MAPPING = {
         exp.DataType.Type.NCHAR: "CHAR",
         exp.DataType.Type.NVARCHAR: "VARCHAR",
@@ -1975,6 +1978,11 @@ class Generator:
         return f"ALTER COLUMN {this} DROP DEFAULT"
 
     def renametable_sql(self, expression: exp.RenameTable) -> str:
+        if not self.RENAME_TABLE_WITH_DB:
+            # Remove db from tables
+            expression = expression.transform(
+                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
+            )
         this = self.sql(expression, "this")
         return f"RENAME TO {this}"
 
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
index b30b707a..369236c7 100644
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -552,12 +552,12 @@ class Parser(metaclass=_Parser):
     RANGE_PARSERS = {
         TokenType.BETWEEN: lambda self, this: self._parse_between(this),
         TokenType.GLOB: binary_range_parser(exp.Glob),
-        TokenType.OVERLAPS: binary_range_parser(exp.Overlaps),
+        TokenType.ILIKE: binary_range_parser(exp.ILike),
         TokenType.IN: lambda self, this: self._parse_in(this),
+        TokenType.IRLIKE: binary_range_parser(exp.RegexpILike),
         TokenType.IS: lambda self, this: self._parse_is(this),
         TokenType.LIKE: binary_range_parser(exp.Like),
-        TokenType.ILIKE: binary_range_parser(exp.ILike),
-        TokenType.IRLIKE: binary_range_parser(exp.RegexpILike),
+        TokenType.OVERLAPS: binary_range_parser(exp.Overlaps),
         TokenType.RLIKE: binary_range_parser(exp.RegexpLike),
         TokenType.SIMILAR_TO: binary_range_parser(exp.SimilarTo),
     }
@@ -2677,8 +2677,8 @@ class Parser(metaclass=_Parser):
             expression=self._parse_set_operations(self._parse_select(nested=True)),
         )
 
-    def _parse_expression(self) -> t.Optional[exp.Expression]:
-        return self._parse_alias(self._parse_conjunction())
+    def _parse_expression(self, explicit_alias: bool = False) -> t.Optional[exp.Expression]:
+        return self._parse_alias(self._parse_conjunction(), explicit=explicit_alias)
 
     def _parse_conjunction(self) -> t.Optional[exp.Expression]:
         return self._parse_tokens(self._parse_equality, self.CONJUNCTION)
@@ -2985,7 +2985,7 @@ class Parser(metaclass=_Parser):
                 field = self._parse_types()
                 if not field:
                     self.raise_error("Expected type")
-            elif op:
+            elif op and self._curr:
                 self._advance()
                 value = self._prev.text
                 field = (
@@ -3045,9 +3045,7 @@ class Parser(metaclass=_Parser):
             if query:
                 expressions = [query]
             else:
-                expressions = self._parse_csv(
-                    lambda: self._parse_alias(self._parse_conjunction(), explicit=True)
-                )
+                expressions = self._parse_csv(lambda: self._parse_expression(explicit_alias=True))
 
             this = seq_get(expressions, 0)
             self._parse_query_modifiers(this)


Test patch:
diff --git a/tests/dialects/test_bigquery.py b/tests/dialects/test_bigquery.py
index 87bba6f7..0da828fd 100644
--- a/tests/dialects/test_bigquery.py
+++ b/tests/dialects/test_bigquery.py
@@ -470,3 +470,12 @@ class TestBigQuery(Validator):
                 "snowflake": "MERGE INTO dataset.Inventory AS T USING dataset.NewArrivals AS S ON FALSE WHEN NOT MATCHED AND product LIKE '%a%' THEN DELETE WHEN NOT MATCHED AND product LIKE '%b%' THEN DELETE",
             },
         )
+
+    def test_rename_table(self):
+        self.validate_all(
+            "ALTER TABLE db.t1 RENAME TO db.t2",
+            write={
+                "snowflake": "ALTER TABLE db.t1 RENAME TO db.t2",
+                "bigquery": "ALTER TABLE db.t1 RENAME TO t2",
+            },
+        )
diff --git a/tests/dialects/test_clickhouse.py b/tests/dialects/test_clickhouse.py
index 58f23b20..0bd42d2d 100644
--- a/tests/dialects/test_clickhouse.py
+++ b/tests/dialects/test_clickhouse.py
@@ -1,3 +1,4 @@
+from sqlglot import exp, parse_one
 from tests.dialects.test_dialect import Validator
 
 
@@ -68,6 +69,36 @@ class TestClickhouse(Validator):
         self.validate_identity("WITH (SELECT foo) AS bar SELECT bar + 5")
         self.validate_identity("WITH test1 AS (SELECT i + 1, j + 1 FROM test1) SELECT * FROM test1")
 
+    def test_ternary(self):
+        self.validate_all("x ? 1 : 2", write={"clickhouse": "CASE WHEN x THEN 1 ELSE 2 END"})
+        self.validate_all(
+            "x AND FOO() > 3 + 2 ? 1 : 2",
+            write={"clickhouse": "CASE WHEN x AND FOO() > 3 + 2 THEN 1 ELSE 2 END"},
+        )
+        self.validate_all(
+            "x ? (y ? 1 : 2) : 3",
+            write={"clickhouse": "CASE WHEN x THEN (CASE WHEN y THEN 1 ELSE 2 END) ELSE 3 END"},
+        )
+        self.validate_all(
+            "x AND (foo() ? FALSE : TRUE) ? (y ? 1 : 2) : 3",
+            write={
+                "clickhouse": "CASE WHEN x AND (CASE WHEN foo() THEN FALSE ELSE TRUE END) THEN (CASE WHEN y THEN 1 ELSE 2 END) ELSE 3 END"
+            },
+        )
+
+        ternary = parse_one("x ? (y ? 1 : 2) : 3", read="clickhouse")
+
+        self.assertIsInstance(ternary, exp.If)
+        self.assertIsInstance(ternary.this, exp.Column)
+        self.assertIsInstance(ternary.args["true"], exp.Paren)
+        self.assertIsInstance(ternary.args["false"], exp.Literal)
+
+        nested_ternary = ternary.args["true"].this
+
+        self.assertIsInstance(nested_ternary.this, exp.Column)
+        self.assertIsInstance(nested_ternary.args["true"], exp.Literal)
+        self.assertIsInstance(nested_ternary.args["false"], exp.Literal)
+
     def test_signed_and_unsigned_types(self):
         data_types = [
             "UInt8",