Repository: tobymao/sqlglot
Pull request: Fix: preserve quotes for projections produced by the eliminate_qualify rule

Problem statement:
Sqlglot produces Syntax Error on rewriting QUALIFY
```python
print(
    sqlglot.parse_one(
        "SELECT tr.`User_-_iD`, tr.time_stamp, tr.__is_deleted, tr.__timestamp FROM tmp_9154874609124146377 AS tr WHERE __timestamp > CAST('2024-05-14 10:09:06.470000+00:00' AS TIMESTAMP) QUALIFY ROW_NUMBER() OVER (PARTITION BY `User_-_iD` ORDER BY __timestamp DESC) = 1",
        dialect="databricks",
    ).sql("spark")
)

```
this produces:

```sql
SELECT User_ - _iD, time_stamp, __is_deleted, __timestamp FROM (SELECT tr.`User_-_iD`, tr.time_stamp, tr.__is_deleted, tr.__timestamp, ROW_NUMBER() OVER (PARTITION BY `User_-_iD` ORDER BY __timestamp DESC) AS _w FROM tmp_9154874609124146377 AS tr WHERE __timestamp > CAST('2024-05-14 10:09:06.470000+00:00' AS TIMESTAMP)) AS _t WHERE _w = 1
```
`User_ - _iD` MUST be quoted for Spark, the rewrite should keep the quote here or just quote by default in the rewrite

Hints from issue discussion:
My guess is that we're probably missing a `quoted=True` or omitting the arg altogether in the `eliminate_qualify` rule when copying an expression. This shouldn't be too hard, we'll take a look in a bit, but well-tested PRs are also welcome.

Commit message: fixes #https://github.com/tobymao/sqlglot/issues/3467

Patch to review:
diff --git a/sqlglot/transforms.py b/sqlglot/transforms.py
index ec2a0df01a..10cc4f90a3 100644
--- a/sqlglot/transforms.py
+++ b/sqlglot/transforms.py
@@ -105,7 +105,9 @@ def eliminate_qualify(expression: exp.Expression) -> exp.Expression:
                 select.replace(exp.alias_(select, alias))
                 taken.add(alias)
 
-        outer_selects = exp.select(*[select.alias_or_name for select in expression.selects])
+        outer_selects = exp.select(
+            *[exp.column(select.alias_or_name, quoted=True) for select in expression.selects]
+        )
         qualify_filters = expression.args["qualify"].pop().this
         expression_by_alias = {
             select.alias: select.this
diff --git a/tests/dialects/test_dialect.py b/tests/dialects/test_dialect.py
index 108ba216ea..691ac8ff0f 100644
--- a/tests/dialects/test_dialect.py
+++ b/tests/dialects/test_dialect.py
@@ -2235,6 +2235,20 @@ def test_qualify(self):
             },
         )
 
+    def test_qualify_spaces(self):
+        self.validate_all(
+            'SELECT "user id" FROM t QUALIFY COUNT(*) OVER () > 1',
+            write={
+                "duckdb": 'SELECT "user id" FROM t QUALIFY COUNT(*) OVER () > 1',
+                "snowflake": 'SELECT "user id" FROM t QUALIFY COUNT(*) OVER () > 1',
+                "clickhouse": 'SELECT "user id" FROM (SELECT "user id", COUNT(*) OVER () AS _w FROM t) AS _t WHERE _w > 1',
+                "mysql": "SELECT `user id` FROM (SELECT `user id`, COUNT(*) OVER () AS _w FROM t) AS _t WHERE _w > 1",
+                "oracle": 'SELECT "user id" FROM (SELECT "user id", COUNT(*) OVER () AS _w FROM t) _t WHERE _w > 1',
+                "postgres": 'SELECT "user id" FROM (SELECT "user id", COUNT(*) OVER () AS _w FROM t) AS _t WHERE _w > 1',
+                "tsql": "SELECT [user id] FROM (SELECT [user id] AS [user id], COUNT(*) OVER () AS _w FROM t) AS _t WHERE _w > 1",
+            },
+        )
+
     def test_nested_ctes(self):
         self.validate_all(
             "SELECT * FROM (WITH t AS (SELECT 1 AS c) SELECT c FROM t) AS subq",


Code review comments:
diff_hunk: @@ -105,7 +105,9 @@ def eliminate_qualify(expression: exp.Expression) -> exp.Expression:
                 select.replace(exp.alias_(select, alias))
                 taken.add(alias)
 
-        outer_selects = exp.select(*[select.alias_or_name for select in expression.selects])
+        outer_selects = exp.select(
+            *[exp.column(select.alias_or_name, quoted=True) for select in expression.selects]
path: sqlglot/transforms.py
text: @user1:
This shouldn't preemptively quote, we should only quote if the original column name / alias was quoted.

@user1:
My original [comment](https://github.com/tobymao/sqlglot/issues/3467#issuecomment-2110001697) may have been misleading, apologies. I didn't literally mean to set `quoted=True`, my intention was to point out that we're actually not preserving the quoting information.