Skip to content

Commit 92ee124

Browse files
authored
fix(optimizer)!: postgres qualify GENERATE_SERIES and table projection (#6373)
1 parent a6e1581 commit 92ee124

File tree

6 files changed

+62
-2
lines changed

6 files changed

+62
-2
lines changed

sqlglot/dialects/dialect.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,14 @@ class Dialect(metaclass=_Dialect):
671671
Whether JSON_EXTRACT_SCALAR returns null if a non-scalar value is selected.
672672
"""
673673

674+
DEFAULT_FUNCTIONS_COLUMN_NAMES: t.Dict[t.Type[exp.Func], t.Union[str, t.Tuple[str, ...]]] = {}
675+
"""
676+
Maps function expressions to their default output column name(s).
677+
678+
For example, in Postgres, generate_series function outputs a column named "generate_series" by default,
679+
so we map the ExplodingGenerateSeries expression to "generate_series" string.
680+
"""
681+
674682
# --- Autofilled ---
675683

676684
tokenizer_class = Tokenizer

sqlglot/dialects/postgres.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,11 @@ class Postgres(Dialect):
297297
NULL_ORDERING = "nulls_are_large"
298298
TIME_FORMAT = "'YYYY-MM-DD HH24:MI:SS'"
299299
TABLESAMPLE_SIZE_IS_PERCENT = True
300+
TABLES_REFERENCEABLE_AS_COLUMNS = True
301+
302+
DEFAULT_FUNCTIONS_COLUMN_NAMES = {
303+
exp.ExplodingGenerateSeries: "generate_series",
304+
}
300305

301306
TIME_MAPPING = {
302307
"d": "%u", # 1-based day of week

sqlglot/optimizer/qualify_columns.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,7 @@ def _qualify_columns(
598598
and len(column.parts) == 1
599599
and column_name in scope.selected_sources
600600
):
601-
# BigQuery allows tables to be referenced as columns, treating them as structs
601+
# BigQuery and Postgres allow tables to be referenced as columns, treating them as structs/records
602602
scope.replace(column, exp.TableColumn(this=column.this))
603603

604604
for pivot in scope.pivots:

sqlglot/optimizer/qualify_tables.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from sqlglot import exp
66
from sqlglot.dialects.dialect import Dialect, DialectType
7-
from sqlglot.helper import name_sequence, seq_get
7+
from sqlglot.helper import name_sequence, seq_get, ensure_list
88
from sqlglot.optimizer.normalize_identifiers import normalize_identifiers
99
from sqlglot.optimizer.scope import Scope, traverse_scope
1010

@@ -79,6 +79,7 @@ def _set_alias(
7979
target_alias: t.Optional[str] = None,
8080
scope: t.Optional[Scope] = None,
8181
normalize: bool = False,
82+
columns: t.Optional[t.List[t.Union[str, exp.Identifier]]] = None,
8283
) -> None:
8384
alias = expression.args.get("alias") or exp.TableAlias()
8485

@@ -96,6 +97,10 @@ def _set_alias(
9697
quoted = True if canonicalize_table_aliases or not target_alias else None
9798

9899
alias.set("this", exp.to_identifier(new_alias_name, quoted=quoted))
100+
101+
if columns:
102+
alias.set("columns", [exp.to_identifier(c) for c in columns])
103+
99104
expression.set("alias", alias)
100105

101106
if scope:
@@ -132,11 +137,27 @@ def _set_alias(
132137
if pivot := seq_get(source.args.get("pivots") or [], 0):
133138
name = source.name
134139

140+
table_this = source.this
141+
table_alias = source.args.get("alias")
142+
function_columns: t.List[t.Union[str, exp.Identifier]] = []
143+
if isinstance(table_this, exp.Func):
144+
if not table_alias:
145+
function_columns = ensure_list(
146+
dialect.DEFAULT_FUNCTIONS_COLUMN_NAMES.get(type(table_this))
147+
)
148+
elif columns := table_alias.columns:
149+
function_columns = columns
150+
elif type(table_this) in dialect.DEFAULT_FUNCTIONS_COLUMN_NAMES:
151+
function_columns = ensure_list(source.alias_or_name)
152+
source.set("alias", None)
153+
name = None
154+
135155
_set_alias(
136156
source,
137157
canonical_aliases,
138158
target_alias=name or source.name or None,
139159
normalize=True,
160+
columns=function_columns,
140161
)
141162

142163
source_fqn = ".".join(p.name for p in source.parts)

tests/fixtures/optimizer/qualify_columns.sql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,11 @@ SELECT DATE_TRUNC(t.col1, WEEK(MONDAY)) AS _col_0, t.col2 AS col2 FROM t AS t;
287287
SELECT first, second FROM (SELECT 'val' AS col, STACK(2, 1, 2, 3) AS (first, second)) AS tbl;
288288
SELECT tbl.first AS first, tbl.second AS second FROM (SELECT 'val' AS col, STACK(2, 1, 2, 3) AS (first, second)) AS tbl;
289289

290+
# execute: false
291+
# dialect: postgres
292+
WITH t AS (SELECT 1 AS c) SELECT t FROM t;
293+
WITH t AS (SELECT 1 AS c) SELECT t AS _col_0 FROM t AS t;
294+
290295
--------------------------------------
291296
-- Derived tables
292297
--------------------------------------

tests/fixtures/optimizer/qualify_tables.sql

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,3 +260,24 @@ SELECT * FROM c.db.x AS "_1" WHERE "_1".a = (SELECT SUM("_0".c) AS c FROM c.db.y
260260
# canonicalize_table_aliases: true
261261
SELECT t.foo FROM t AS t, (SELECT t.bar FROM t AS t);
262262
SELECT "_2".foo FROM c.db.t AS "_2", (SELECT "_0".bar FROM c.db.t AS "_0") AS "_1";
263+
264+
# title: Qualify GENERATE_SERIES with its default column generate_series
265+
# dialect: postgres
266+
SELECT generate_series FROM GENERATE_SERIES(1,2);
267+
SELECT generate_series FROM GENERATE_SERIES(1, 2) AS "_0"(generate_series);
268+
269+
# title: Qualify GENERATE_SERIES with alias by wrapping it
270+
# dialect: postgres
271+
SELECT g FROM GENERATE_SERIES(1,2) AS g;
272+
SELECT g FROM GENERATE_SERIES(1, 2) AS "_0"(g);
273+
274+
# title: Qualify GENERATE_SERIES with alias on table and columns
275+
# dialect: postgres
276+
SELECT g FROM GENERATE_SERIES(1,2) AS t(g);
277+
SELECT g FROM GENERATE_SERIES(1, 2) AS t(g);
278+
279+
# title: Qualify GENERATE_SERIES with explicit column and canonicalize_table_aliases
280+
# dialect: postgres
281+
# canonicalize_table_aliases: true
282+
SELECT g FROM GENERATE_SERIES(1,2) AS t(g);
283+
SELECT g FROM GENERATE_SERIES(1, 2) AS "_0"(g);

0 commit comments

Comments
 (0)