Skip to content

Commit d36f6b3

Browse files
committed
fix(db2): unwrap Alias(Select) before SYSDUMMY1 injection to fix column alias placement
db2_sqlglot registers _add_sysibm_dual as a preprocessor on exp.Select. When the top-level node is Alias(Select, alias=name) — produced by exp.select(expr).as_("col") — the generator renders the inner Select (which adds FROM SYSIBM.SYSDUMMY1 via the preprocessor) and then appends AS name after the fully-rendered SQL string, giving: SELECT ... FROM SYSIBM.SYSDUMMY1 AS the_col ← broken instead of: SELECT ... AS the_col FROM SYSIBM.SYSDUMMY1 ← correct The column has no alias in the result so pandas sees column name '1'. Fix in _fetch_native_df: when the expression is Alias(Select), move the alias onto the first selected expression before the generator sees it. The generator then processes a bare Select, SYSDUMMY1 lands in the right place, and the column alias is emitted correctly. Also add db2 to the col_name uppercase branch in test_to_time_column: Db2 returns column names in uppercase (THE_COL) after normalize_identifiers, same as Snowflake. Root cause is the db2_sqlglot dialect Alias/Select bug; correct fix is in db2_sqlglot but is worked around here pending that fix.
1 parent d760fb4 commit d36f6b3

2 files changed

Lines changed: 23 additions & 1 deletion

File tree

sqlmesh/core/engine_adapter/db2.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,27 @@ def _fetch_native_df(
789789
"""
790790
if isinstance(query, exp.Expression):
791791
query = query.copy()
792+
# The db2_sqlglot generator injects FROM SYSIBM.SYSDUMMY1 via a
793+
# preprocessor registered on exp.Select. When the caller passes
794+
# Alias(Select, alias=name) — e.g. exp.select(expr).as_("col") —
795+
# the generator renders the inner Select (adding FROM SYSDUMMY1)
796+
# and then appends AS name after the fully-rendered SQL, producing:
797+
# SELECT ... FROM SYSIBM.SYSDUMMY1 AS name ← broken
798+
# instead of:
799+
# SELECT ... AS name FROM SYSIBM.SYSDUMMY1 ← correct
800+
# This is a db2_sqlglot dialect bug (the Alias wrapper is not
801+
# SELECT-aware). Work around it: when the top-level node is
802+
# Alias(Select), move the alias onto the first selected expression
803+
# so the generator only ever sees a bare Select node.
804+
if isinstance(query, exp.Alias) and isinstance(query.this, exp.Select):
805+
inner = query.this
806+
alias_name = query.alias
807+
inner.set(
808+
"expressions",
809+
[exp.Alias(this=inner.expressions[0], alias=exp.to_identifier(alias_name))]
810+
+ inner.expressions[1:],
811+
)
812+
query = inner
792813
normalize_identifiers(query, dialect=self.dialect)
793814
return super()._fetch_native_df(query, quote_identifiers=True)
794815

tests/core/engine_adapter/integration/test_integration.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2726,7 +2726,8 @@ def test_to_time_column(
27262726
time_column = to_time_column(time_column, time_column_type, ctx.dialect, time_column_format)
27272727
df = ctx.engine_adapter.fetchdf(exp.select(time_column).as_("the_col"))
27282728
expected = result.get(ctx.dialect, result.get("default"))
2729-
col_name = "THE_COL" if ctx.dialect == "snowflake" else "the_col"
2729+
# Db2 (UPPERCASE strategy) returns column names in uppercase, same as Snowflake
2730+
col_name = "THE_COL" if ctx.dialect in ("snowflake", "db2") else "the_col"
27302731
if expected is pd.NaT or expected is None:
27312732
assert df[col_name][0] is expected
27322733
else:

0 commit comments

Comments
 (0)