Repository: tobymao/sqlglot

Issue:
Unexpected failure when qualifying columns in BigQuery
Hey there, I am not sure if it is expected for this sort of query to fail here so please do let me know if that is the case or if I am doing something wrong. The query is able to run on Bigquery without issue. 

I have this (simplified) query here:
sql = """
WITH 
cte_1 as(
    SELECT
       a,
       b
    FROM 
        (select a from cte_1, unnest (l_1) as ul_1 with offset as t_1_col) evts
    LEFT JOIN
        (select b from cte_1, unnest (l_2) as ul_2 with offset as t_2_col) types
    ON  
        evts.t_1_col = types.t_2_col
)
select * from cte_2
"""

parsed_query = parse_one(sql, dialect="bigquery")
parsed_query = eliminate_subqueries(parsed_query)
qualified = qualify_columns(parsed_query, schema={}, infer_schema=True)

When I run this it gives me:
Traceback (most recent call last):
  File "/Users/pablobeltran/SS_REPOS_SL/kelp/scripts/validate_all_dbt_sql.py", line 278, in <module>
    qualified = qualify_columns(parsed_query, schema={}, infer_schema=True)
  File "/Users/pablobeltran/SS_REPOS_SL/kelp/kelp_dagster/.venv/lib/python3.10/site-packages/sqlglot/optimizer/qualify_columns.py", line 61, in qualify_columns
    _qualify_columns(scope, resolver)
  File "/Users/pablobeltran/SS_REPOS_SL/kelp/kelp_dagster/.venv/lib/python3.10/site-packages/sqlglot/optimizer/qualify_columns.py", line 330, in _qualify_columns
    raise OptimizeError(f"Unknown column: {column_name}")
sqlglot.errors.OptimizeError: Unknown column: t_1_col






Discussion:
Hmm think I messed something up when simplifying the query going to close for now
As mentioned in https://github.com/tobymao/sqlglot/blob/main/sqlglot/optimizer/optimizer.py#L68, you can't just run the rules in isolation or in any arbitrary order, there are certain invariants that are expected to be true and are enforced by the default rule order. The `qualify` rule in particular is very important.

Resolving patch:
diff --git a/sqlglot/optimizer/annotate_types.py b/sqlglot/optimizer/annotate_types.py
--- a/sqlglot/optimizer/annotate_types.py
+++ b/sqlglot/optimizer/annotate_types.py
@@ -516,21 +516,20 @@ def _annotate_by_args(
             )
 
         if struct:
+            expressions = [
+                expr.type
+                if not expr.alias
+                else exp.ColumnDef(this=exp.to_identifier(expr.alias), kind=expr.type)
+                for expr in expressions
+            ]
             self._set_type(
                 expression,
                 exp.DataType(
                     this=exp.DataType.Type.STRUCT,
-                    expressions=[
-                        exp.ColumnDef(
-                            this=exp.to_identifier(expr.alias),
-                            kind=expr.type,
-                        )
-                        for expr in expressions
-                    ],
+                    expressions=expressions,
                     nested=True,
                 ),
             )
-
         return expression
 
     def _annotate_timeunit(


Test patch:
diff --git a/tests/test_optimizer.py b/tests/test_optimizer.py
--- a/tests/test_optimizer.py
+++ b/tests/test_optimizer.py
@@ -549,21 +549,18 @@ def test_scope_warning(self, logger):
         )
 
     def test_struct_type_annotation(self):
-        expression = annotate_types(parse_one("select struct(1 as col)"))
-        assert expression.expressions[0].is_type("STRUCT<col INT>")
-
-        expression = annotate_types(parse_one("select struct(1 as col, 2.5 as row)"))
-        assert expression.expressions[0].is_type("STRUCT<col INT, row DOUBLE>")
+        tests = {
+            "SELECT STRUCT(1 AS col)": "STRUCT<col INT>",
+            "SELECT STRUCT(1 AS col, 2.5 AS row)": "STRUCT<col INT, row DOUBLE>",
+            "SELECT STRUCT(1)": "STRUCT<INT>",
+            "SELECT STRUCT(1 AS col, 2.5 AS row, struct(3.5 AS inner_col, 4 AS inner_row) AS nested_struct)": "STRUCT<col INT, row DOUBLE, nested_struct STRUCT<inner_col DOUBLE, inner_row INT>>",
+            "SELECT STRUCT(1 AS col, 2.5, ARRAY[1, 2, 3] AS nested_array, 'foo')": "STRUCT<col INT, DOUBLE, nested_array ARRAY<INT>, VARCHAR>",
+            "SELECT STRUCT(1, 2.5, 'bar')": "STRUCT<INT, DOUBLE, VARCHAR>",
+        }
 
-        expression = annotate_types(
-            parse_one(
-                "SELECT struct(1 AS col, 2.5 AS row, struct(3.5 AS inner_col, 4 AS inner_row) AS nested_struct)",
-                read="spark",
-            )
-        )
-        assert expression.expressions[0].is_type(
-            "STRUCT<col INT, row DOUBLE, nested_struct STRUCT<inner_col DOUBLE, inner_row INT>>"
-        )
+        for sql, target_type in tests.items():
+            expression = annotate_types(parse_one(sql))
+            assert expression.expressions[0].is_type(target_type)
 
     def test_literal_type_annotation(self):
         tests = {