Skip to content

Commit d760fb4

Browse files
committed
fix(db2): normalize_identifiers before quoting to fix CTE alias case mismatch (SQL0204N)
_fetch_native_df forced quote_identifiers=True to preserve case-sensitive column lookups. However quote_identifiers wraps unquoted identifiers with their original (pre-normalisation) case, so a CTE alias 'c' (unquoted) became '"c"' while the UPPERCASE normalisation strategy causes the SELECT reference to be '"C"' — a case-sensitive mismatch on Db2 causing SQL0204N. Fix: apply normalize_identifiers(dialect='db2') before the quote step so unquoted identifiers are uppercased first (c → C → "C"), matching the SELECT reference. Quoted identifiers are intentionally unchanged by normalize_identifiers. Also update test_dialects expected_columns to include db2 in the uppercase branch: Db2 returns column names in uppercase (W, X, Y, Z) for both quoted and unquoted column aliases, same as Snowflake.
1 parent 99422b4 commit d760fb4

2 files changed

Lines changed: 17 additions & 3 deletions

File tree

‎sqlmesh/core/engine_adapter/db2.py‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from functools import cached_property
77

88
from sqlglot import exp
9+
from sqlglot.optimizer.normalize_identifiers import normalize_identifiers
910

1011
from sqlmesh.core.engine_adapter.base import EngineAdapter, _get_data_object_cache_key
1112
from sqlmesh.core.engine_adapter.mixins import PandasNativeFetchDFSupportMixin
@@ -776,9 +777,19 @@ def _fetch_native_df(
776777
777778
Forcing quote_identifiers=True here ensures every SELECT issued by
778779
SQLMesh (evaluator, fetchdf, fetchall via execute) wraps identifiers in
779-
double-quotes so Db2 matches them exactly as stored. This mirrors the
780-
same pattern used by Snowflake, BigQuery, and Athena.
780+
double-quotes so Db2 matches them exactly as stored.
781+
782+
normalize_identifiers is applied first so that unquoted identifiers are
783+
uppercased before quoting (e.g. unquoted `c` → `C` → `"C"`). Quoted
784+
identifiers (e.g. `"a"`, `"B"`) are intentionally left unchanged by
785+
normalize_identifiers — they remain case-sensitive as the caller intended.
786+
This prevents the mismatch where a CTE alias defined as unquoted `c` would
787+
otherwise be emitted as `"c"` (lowercase) while a SELECT reference derived
788+
from Db2's UPPERCASE normalisation strategy uses `"C"` (SQL0204N).
781789
"""
790+
if isinstance(query, exp.Expression):
791+
query = query.copy()
792+
normalize_identifiers(query, dialect=self.dialect)
782793
return super()._fetch_native_df(query, quote_identifiers=True)
783794

784795
def _df_to_source_queries(

‎tests/core/engine_adapter/integration/test_integration.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2636,7 +2636,10 @@ def test_dialects(ctx: TestContext):
26362636
"""
26372637
)
26382638
df = ctx.engine_adapter.fetchdf(q)
2639-
expected_columns = ["W", "X", "Y", "Z"] if ctx.dialect == "snowflake" else ["w", "x", "y", "z"]
2639+
# Db2 (UPPERCASE strategy) returns uppercase column names regardless of alias case
2640+
expected_columns = (
2641+
["W", "X", "Y", "Z"] if ctx.dialect in ("snowflake", "db2") else ["w", "x", "y", "z"]
2642+
)
26402643
pd.testing.assert_frame_equal(
26412644
df, pd.DataFrame([[1, 1, 1, 1]], columns=expected_columns), check_dtype=False
26422645
)

0 commit comments

Comments
 (0)