Repository: tobymao/sqlglot

Issue:
Pushdown projections seems to handle QUALIFY clauses incorrectly
**Fully reproducible code snippet**
```
sql = """
with cte as (
  select
    a,
    row_number() over (
        partition by id, timestamp
        order by a desc
    ) as index

  from source
  qualify index
)

select a from cte
"""

schema = {"source":{"a": "STRING", "id": "STRING", "timestamp": "TIMESTAMP"}}
```

If you run, 

```
from sqlglot.optimizer import optimize
from sqlglot.optimizer.qualify_columns import qualify_columns, validate_qualify_columns
from sqlglot.optimizer.qualify_tables import qualify_tables
from sqlglot.optimizer.isolate_table_selects import isolate_table_selects
from sqlglot.optimizer.pushdown_predicates import pushdown_predicates
from sqlglot.optimizer.pushdown_projections import pushdown_projections
from sqlglot.optimizer.lower_identities import lower_identities
optimize(sql, dialect = "bigquery", schema = schema, rules = (lower_identities,
                                        qualify_tables,
                                        isolate_table_selects,
                                        qualify_columns,
                                        # pushdown_projections,
                                        )).sql(dialect="bigquery")
```

With the above code, SQLglot correctly returns:

```
WITH cte AS (
    SELECT
        source.a AS a,
        ROW_NUMBER() OVER (
            PARTITION BY source.id,
            source.timestamp
            ORDER BY
                source.a DESC
        ) AS index
    FROM
        source AS source QUALIFY index
)
SELECT
    cte.a AS a
FROM
    cte
```

But if you also run with pushdown_projections, the same optimization returns

```
WITH cte AS (
    SELECT
        source.a AS a
    FROM
        source AS source QUALIFY index
)
SELECT
    cte.a AS a
FROM
    cte
```

This is now invalid SQL, since the QUALIFY statement is included, but it's definition in the SELECT clause is not included.

I ran the above code for Snowflake, Postgres, and Redshift and see the same thing, so it appears to be a cross-dialect issue.


Discussion:
@tobymao I think the problem here is that pushdown projections doesn't take into account projections that are referenced in the same scope and discards the `index` projection because it deems it's not selected?

I can take a look shortly.

Resolving patch:
diff --git a/sqlglot/optimizer/qualify_columns.py b/sqlglot/optimizer/qualify_columns.py
--- a/sqlglot/optimizer/qualify_columns.py
+++ b/sqlglot/optimizer/qualify_columns.py
@@ -194,6 +194,7 @@ def replace_columns(
     replace_columns(expression.args.get("where"))
     replace_columns(expression.args.get("group"))
     replace_columns(expression.args.get("having"), resolve_agg=True)
+    replace_columns(expression.args.get("qualify"), resolve_agg=True)
     replace_columns(expression.args.get("order"), expand=False, resolve_agg=True)
     scope.clear_cache()
 


Test patch:
diff --git a/tests/fixtures/optimizer/pushdown_projections.sql b/tests/fixtures/optimizer/pushdown_projections.sql
--- a/tests/fixtures/optimizer/pushdown_projections.sql
+++ b/tests/fixtures/optimizer/pushdown_projections.sql
@@ -61,9 +61,13 @@ SELECT q.x AS x FROM (VALUES (1, 2)) AS q(x, y);
 SELECT i.a FROM x AS i LEFT JOIN (SELECT a, b FROM (SELECT a, b FROM x)) AS j ON i.a = j.a;
 SELECT i.a AS a FROM x AS i LEFT JOIN (SELECT _q_0.a AS a FROM (SELECT x.a AS a FROM x AS x) AS _q_0) AS j ON i.a = j.a;
 
+WITH cte AS (SELECT source.a AS a, ROW_NUMBER() OVER (PARTITION BY source.id, source.timestamp ORDER BY source.a DESC) AS index FROM source AS source QUALIFY index) SELECT cte.a AS a FROM cte;
+WITH cte AS (SELECT source.a AS a FROM source AS source QUALIFY ROW_NUMBER() OVER (PARTITION BY source.id, source.timestamp ORDER BY source.a DESC)) SELECT cte.a AS a FROM cte;
+
 --------------------------------------
 -- Unknown Star Expansion
 --------------------------------------
+
 SELECT a FROM (SELECT * FROM zz) WHERE b = 1;
 SELECT _q_0.a AS a FROM (SELECT zz.a AS a, zz.b AS b FROM zz AS zz) AS _q_0 WHERE _q_0.b = 1;
 
diff --git a/tests/fixtures/optimizer/qualify_columns.sql b/tests/fixtures/optimizer/qualify_columns.sql
--- a/tests/fixtures/optimizer/qualify_columns.sql
+++ b/tests/fixtures/optimizer/qualify_columns.sql
@@ -396,16 +396,20 @@ SELECT x.a + 1 AS a, ROW_NUMBER() OVER (PARTITION BY x.b ORDER BY x.a) AS row_nu
 
 # dialect: bigquery
 SELECT ROW_NUMBER() OVER (PARTITION BY a ORDER BY b) AS row_num FROM x QUALIFY row_num = 1;
-SELECT ROW_NUMBER() OVER (PARTITION BY x.a ORDER BY x.b) AS row_num FROM x AS x QUALIFY row_num = 1;
+SELECT ROW_NUMBER() OVER (PARTITION BY x.a ORDER BY x.b) AS row_num FROM x AS x QUALIFY ROW_NUMBER() OVER (PARTITION BY x.a ORDER BY x.b) = 1;
 
 # dialect: bigquery
 SELECT x.b, x.a FROM x LEFT JOIN y ON x.b = y.b QUALIFY ROW_NUMBER() OVER(PARTITION BY x.b ORDER BY x.a DESC) = 1;
 SELECT x.b AS b, x.a AS a FROM x AS x LEFT JOIN y AS y ON x.b = y.b QUALIFY ROW_NUMBER() OVER (PARTITION BY x.b ORDER BY x.a DESC) = 1;
 
+# dialect: snowflake
+SELECT * FROM x QUALIFY COUNT(a) OVER (PARTITION BY b) > 1;
+SELECT x.a AS a, x.b AS b FROM x AS x QUALIFY COUNT(x.a) OVER (PARTITION BY x.b) > 1;
 
 --------------------------------------
 -- Expand laterals
 --------------------------------------
+
 # title: expand alias reference
 SELECT
   x.a + 1 AS i,