Skip to content

Commit 0bd0c46

Browse files
authored
fix(optimizer): handle ColumnDef in typed table alias columns (#7542)
qualify_tables._set_alias called exp.to_identifier() on every column, which rejects ColumnDef nodes produced by typed table-function signatures like FROM fn() AS t(col INT). Route only strings through to_identifier and copy any other expression (Identifier, ColumnDef) to preserve the existing copy-before-reparent semantics. Widens the columns/function_columns type annotations to include exp.ColumnDef so mypyc's runtime type checks accept it.
1 parent a88dfce commit 0bd0c46

3 files changed

Lines changed: 25 additions & 3 deletions

File tree

sqlglot/optimizer/qualify_tables.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def _set_alias(
8080
target_alias: str | None = None,
8181
scope: Scope | None = None,
8282
normalize: bool = False,
83-
columns: Sequence[str | exp.Identifier] | None = None,
83+
columns: Sequence[str | exp.Identifier | exp.ColumnDef] | None = None,
8484
) -> None:
8585
alias = expression.args.get("alias") or exp.TableAlias()
8686

@@ -97,7 +97,10 @@ def _set_alias(
9797
alias.set("this", exp.to_identifier(new_alias_name))
9898

9999
if columns:
100-
alias.set("columns", [exp.to_identifier(c) for c in columns])
100+
alias.set(
101+
"columns",
102+
[exp.to_identifier(c) if isinstance(c, str) else c.copy() for c in columns],
103+
)
101104

102105
expression.set("alias", alias)
103106

@@ -137,7 +140,7 @@ def _set_alias(
137140

138141
table_this = source.this
139142
table_alias = source.args.get("alias")
140-
function_columns: Sequence[str | exp.Identifier] | None = None
143+
function_columns: Sequence[str | exp.Identifier | exp.ColumnDef] | None = None
141144
if isinstance(table_this, exp.Func):
142145
if not table_alias:
143146
function_columns = ensure_list(

tests/fixtures/optimizer/qualify_tables.sql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,3 +281,9 @@ SELECT g FROM GENERATE_SERIES(1, 2) AS t(g);
281281
# canonicalize_table_aliases: true
282282
SELECT g FROM GENERATE_SERIES(1,2) AS t(g);
283283
SELECT g FROM GENERATE_SERIES(1, 2) AS _0(g);
284+
285+
# title: Qualify JSONB_TO_RECORDSET with typed alias columns and canonicalize_table_aliases
286+
# dialect: postgres
287+
# canonicalize_table_aliases: true
288+
SELECT jsonvalue.id FROM deal, JSONB_TO_RECORDSET(CAST(deal.type AS JSONB)) AS jsonvalue(id INT, key VARCHAR, text VARCHAR);
289+
SELECT _1.id FROM c.db.deal AS _0, JSONB_TO_RECORDSET(CAST(_0.type AS JSONB)) AS _1(id INT, key VARCHAR, text VARCHAR);

tests/test_optimizer.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,19 @@ def test_qualify_tables(self):
306306
catalog="c",
307307
)
308308

309+
def test_qualify_tables_copies_typed_alias_columns(self):
310+
expression = parse_one('SELECT * FROM JSON_TO_RECORDSET(z) AS y("rank" INT)')
311+
312+
original = expression.find(exp.Table).args["alias"].columns[0]
313+
self.assertIsInstance(original, exp.ColumnDef)
314+
315+
optimizer.qualify_tables.qualify_tables(expression, canonicalize_table_aliases=True)
316+
317+
new = expression.find(exp.Table).args["alias"].columns[0]
318+
self.assertIsInstance(new, exp.ColumnDef)
319+
self.assertIsNot(original, new)
320+
self.assertEqual(original.sql(), new.sql())
321+
309322
def test_normalize(self):
310323
self.assertEqual(
311324
optimizer.normalize.normalize(

0 commit comments

Comments
 (0)