From 96a6b138b8c3d6a1c5a6b24353ecb604daf04603 Mon Sep 17 00:00:00 2001 From: fivetran-kwoodbeck Date: Mon, 29 Jun 2026 14:17:09 -0400 Subject: [PATCH 01/13] Normalization of synthesized output aliases --- sqlglot/optimizer/qualify_columns.py | 33 +++++++++++++++++-- tests/fixtures/optimizer/optimizer.sql | 2 +- .../optimizer/pushdown_projections.sql | 8 ++--- tests/test_optimizer.py | 13 ++++---- 4 files changed, 42 insertions(+), 14 deletions(-) diff --git a/sqlglot/optimizer/qualify_columns.py b/sqlglot/optimizer/qualify_columns.py index a976c02a57..13fd6b122c 100644 --- a/sqlglot/optimizer/qualify_columns.py +++ b/sqlglot/optimizer/qualify_columns.py @@ -101,7 +101,7 @@ def qualify_columns( pseudocolumns, annotator, ) - qualify_outputs(scope) + qualify_outputs(scope, dialect=dialect) _expand_group_by(scope, dialect) @@ -848,6 +848,16 @@ def _expand_stars( renamed_columns = rename_columns.get(table_id, {}) replaced_columns = replace_columns.get(table_id, {}) + # Preserve case-sensitivity of quoted source columns when expanding stars, so the + # synthesized output alias matches an explicit reference to the same column. + source = scope.sources.get(table) + source_expression = source.expression if isinstance(source, Scope) else None + quoted_columns = ( + {s.output_name: _output_identifier_quoted(s) for s in source_expression.selects} + if isinstance(source_expression, exp.Query) + else {} + ) + if pivot: pivot_columns = pivot.output_columns(columns) or pivot.alias_column_names @@ -876,6 +886,8 @@ def _expand_stars( else: alias_ = renamed_columns.get(name, name) selection_expr = replaced_columns.get(name) or exp.column(name, table=table) + if quoted_columns.get(name) and isinstance(selection_expr, exp.Column): + selection_expr.this.set("quoted", True) new_selections.append( alias(selection_expr, alias_, copy=False) if alias_ != name @@ -887,6 +899,18 @@ def _expand_stars( scope_expression.set("expressions", new_selections) +def _output_identifier_quoted(selection: exp.Expr) -> bool: + """Whether a projection's output column name is a quoted (case-sensitive) identifier.""" + if isinstance(selection, exp.Alias): + identifier = selection.args.get("alias") + elif isinstance(selection, exp.Column): + identifier = selection.this + else: + identifier = None + + return isinstance(identifier, exp.Identifier) and identifier.quoted + + def _add_ilike_columns(expression: exp.Expr) -> str | None: ilike = expression.args.get("ilike") @@ -936,7 +960,7 @@ def _add_replace_columns( replace_columns[id(table)] = columns -def qualify_outputs(scope_or_expression: Scope | exp.Expr) -> None: +def qualify_outputs(scope_or_expression: Scope | exp.Expr, dialect: DialectType = None) -> None: """Ensure all output columns are aliased""" if isinstance(scope_or_expression, exp.Expr): scope = build_scope(scope_or_expression) @@ -950,6 +974,8 @@ def qualify_outputs(scope_or_expression: Scope | exp.Expr) -> None: if not isinstance(expression, exp.Selectable): return + dialect = Dialect.get_or_raise(dialect) + new_selections = [] for i, (selection, aliased_column) in enumerate( @@ -962,11 +988,14 @@ def qualify_outputs(scope_or_expression: Scope | exp.Expr) -> None: if not selection.output_name: selection.set("alias", exp.TableAlias(this=exp.to_identifier(f"_col_{i}"))) elif not isinstance(selection, (exp.Alias, exp.Aliases)) and not selection.is_star: + source_quoted = isinstance(selection, exp.Column) and selection.this.quoted selection = alias( selection, alias=selection.output_name or f"_col_{i}", copy=False, ) + if not source_quoted: + dialect.normalize_identifier(selection.args["alias"]) if aliased_column: selection.set("alias", exp.to_identifier(aliased_column)) diff --git a/tests/fixtures/optimizer/optimizer.sql b/tests/fixtures/optimizer/optimizer.sql index 21fdab1c65..bea5ffa2d4 100644 --- a/tests/fixtures/optimizer/optimizer.sql +++ b/tests/fixtures/optimizer/optimizer.sql @@ -1588,5 +1588,5 @@ CROSS JOIN LATERAL FLATTEN(input => "OBJ"."DATA") AS "F"("SEQ", "KEY", "PATH", " SELECT array_agg(id) WITHIN GROUP (ORDER BY id) OVER (PARTITION BY grp) FROM t; SELECT ARRAY_AGG("T"."ID") WITHIN GROUP (ORDER BY - "T"."ID") OVER (PARTITION BY "T"."GRP") AS "_col_0" + "T"."ID") OVER (PARTITION BY "T"."GRP") AS "_COL_0" FROM "T" AS "T"; \ No newline at end of file diff --git a/tests/fixtures/optimizer/pushdown_projections.sql b/tests/fixtures/optimizer/pushdown_projections.sql index 02bdda7404..bf6f141290 100644 --- a/tests/fixtures/optimizer/pushdown_projections.sql +++ b/tests/fixtures/optimizer/pushdown_projections.sql @@ -122,11 +122,11 @@ SELECT _0.a AS a, _0.b AS b FROM (WITH cte1 AS (SELECT 1 AS a, 2 AS b) SELECT ct # dialect: snowflake SELECT OBJECT_CONSTRUCT(*) FROM (SELECT a, b FROM x) AS t; -SELECT OBJECT_CONSTRUCT(*) AS _col_0 FROM (SELECT a AS a, b AS b FROM x AS x) AS t; +SELECT OBJECT_CONSTRUCT(*) AS _COL_0 FROM (SELECT a AS A, b AS B FROM x AS x) AS t; # dialect: snowflake WITH base AS (SELECT 1 AS a, 2 AS b, 3 AS c, 4 AS d) SELECT OBJECT_INSERT(OBJECT_CONSTRUCT(*), 'e', 5) FROM base; -WITH base AS (SELECT 1 AS a, 2 AS b, 3 AS c, 4 AS d) SELECT OBJECT_INSERT(OBJECT_CONSTRUCT(*), 'e', 5) AS _col_0 FROM base AS base; +WITH base AS (SELECT 1 AS a, 2 AS b, 3 AS c, 4 AS d) SELECT OBJECT_INSERT(OBJECT_CONSTRUCT(*), 'e', 5) AS _COL_0 FROM base AS base; # dialect: snowflake WITH base AS (SELECT 1 AS a, 2 AS b, 3 AS c, 4 AS d) SELECT obj:A, obj:B FROM (SELECT OBJECT_INSERT(OBJECT_CONSTRUCT(*), 'e', 5) AS obj, a FROM base) AS t; @@ -134,11 +134,11 @@ WITH base AS (SELECT 1 AS a, 2 AS b, 3 AS c, 4 AS d) SELECT GET_PATH(t.obj, 'A') # dialect: snowflake WITH cte AS (SELECT 1 AS a, 2 as b) SELECT HASH_AGG(*) FROM cte; -WITH cte AS (SELECT 1 AS a, 2 AS b) SELECT HASH_AGG(*) AS _col_0 FROM cte AS cte; +WITH cte AS (SELECT 1 AS a, 2 AS b) SELECT HASH_AGG(*) AS _COL_0 FROM cte AS cte; # dialect: snowflake WITH cte AS (SELECT a, b FROM x) SELECT COUNT(* EXCLUDE a) FROM cte; -WITH cte AS (SELECT a AS a, b AS b FROM x AS x) SELECT COUNT(* EXCLUDE (a)) AS _col_0 FROM cte AS cte; +WITH cte AS (SELECT a AS A, b AS B FROM x AS x) SELECT COUNT(* EXCLUDE (a)) AS _COL_0 FROM cte AS cte; WITH cte1 AS (SELECT a, SUM(b) AS sale FROM x GROUP BY a), cte2 AS (SELECT cte1.a, COUNT(*) AS cnt FROM cte1 GROUP BY cte1.a) SELECT a, cnt FROM cte2; WITH cte1 AS (SELECT x.a AS a FROM x AS x GROUP BY x.a), cte2 AS (SELECT cte1.a AS a, COUNT(*) AS cnt FROM cte1 AS cte1 GROUP BY cte1.a) SELECT cte2.a AS a, cte2.cnt AS cnt FROM cte2 AS cte2; diff --git a/tests/test_optimizer.py b/tests/test_optimizer.py index 24a8c6b80e..38486cabc8 100644 --- a/tests/test_optimizer.py +++ b/tests/test_optimizer.py @@ -1345,9 +1345,8 @@ def test_canonicalize_internal_names(self): self.assertEqual(canon_pg_a, canon_pg_qa) # In Snowflake (upper-folding), unquoted `a` becomes `A`, while quoted `"a"` stays - # lowercase — they reference *different* columns. Base-table names are preserved, - # and the quote state on the lowercase column is retained because dropping it - # would let Snowflake re-case-fold `a` back to `A` (changing semantics). + # lowercase — they reference *different* columns. The generated alias for the quoted + # column keeps its exact spelling, since folding it would re-case-fold `a` back to `A`. sf_schema = {"X": {"A": "INT", '"a"': "INT"}} canon_sf = qualify_then_canonicalize( parse_one('SELECT a, "a" FROM x', dialect="snowflake"), @@ -2435,8 +2434,8 @@ def test_quotes(self): """ SELECT "source"."ID" AS "ID", - "source"."name" AS "name", - "source"."payload" AS "payload" + "source"."name" AS "NAME", + "source"."payload" AS "PAYLOAD" FROM "EXAMPLE"."source" AS "source" """, read="snowflake", @@ -2748,7 +2747,7 @@ def _parse_and_optimize(query: str, dialect: str) -> exp.Expr: # Databricks sql = _parse_and_optimize("SELECT col:A.a, col:a.A FROM t", dialect="databricks") - assert sql == "SELECT `t`.`col`:A.a AS `a`, `t`.`col`:a.A AS `A` FROM `t` AS `t`" + assert sql == "SELECT `t`.`col`:A.a AS `a`, `t`.`col`:a.A AS `a` FROM `t` AS `t`" # Clickhouse sql = _parse_and_optimize("SELECT col.A.a, col.a.A FROM t", dialect="clickhouse") @@ -2762,7 +2761,7 @@ def _parse_and_optimize(query: str, dialect: str) -> exp.Expr: sql = _parse_and_optimize("SELECT col:A.a, col:a.A FROM t", dialect="snowflake") assert ( sql - == '''SELECT GET_PATH("T"."COL", 'A.a') AS "a", GET_PATH("T"."COL", 'a.A') AS "A" FROM "T" AS "T"''' + == '''SELECT GET_PATH("T"."COL", 'A.a') AS "A", GET_PATH("T"."COL", 'a.A') AS "A" FROM "T" AS "T"''' ) query = parse_one( From 314b105c3eb18d76593bf1680dfe9a015c0f749b Mon Sep 17 00:00:00 2001 From: fivetran-kwoodbeck Date: Tue, 30 Jun 2026 12:22:09 -0400 Subject: [PATCH 02/13] PR tweaks / updates --- sqlglot/optimizer/qualify_columns.py | 16 ++++++++-------- tests/fixtures/optimizer/qualify_columns.sql | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/sqlglot/optimizer/qualify_columns.py b/sqlglot/optimizer/qualify_columns.py index 13fd6b122c..84929477be 100644 --- a/sqlglot/optimizer/qualify_columns.py +++ b/sqlglot/optimizer/qualify_columns.py @@ -831,7 +831,8 @@ def _expand_stars( continue for table in tables: - if table not in scope.sources: + source = scope.sources.get(table) + if source is None: raise OptimizeError(f"Unknown table: {table}") columns = resolver.get_source_columns(table, only_visible=True) @@ -848,9 +849,8 @@ def _expand_stars( renamed_columns = rename_columns.get(table_id, {}) replaced_columns = replace_columns.get(table_id, {}) - # Preserve case-sensitivity of quoted source columns when expanding stars, so the - # synthesized output alias matches an explicit reference to the same column. - source = scope.sources.get(table) + # Preserve case-sensitivity of quoted source columns when expanding stars, + # so the generated alias isn't folded by dialect normalization source_expression = source.expression if isinstance(source, Scope) else None quoted_columns = ( {s.output_name: _output_identifier_quoted(s) for s in source_expression.selects} @@ -960,7 +960,7 @@ def _add_replace_columns( replace_columns[id(table)] = columns -def qualify_outputs(scope_or_expression: Scope | exp.Expr, dialect: DialectType = None) -> None: +def qualify_outputs(scope_or_expression: Scope | exp.Expr, dialect: Dialect | None = None) -> None: """Ensure all output columns are aliased""" if isinstance(scope_or_expression, exp.Expr): scope = build_scope(scope_or_expression) @@ -974,8 +974,6 @@ def qualify_outputs(scope_or_expression: Scope | exp.Expr, dialect: DialectType if not isinstance(expression, exp.Selectable): return - dialect = Dialect.get_or_raise(dialect) - new_selections = [] for i, (selection, aliased_column) in enumerate( @@ -994,7 +992,9 @@ def qualify_outputs(scope_or_expression: Scope | exp.Expr, dialect: DialectType alias=selection.output_name or f"_col_{i}", copy=False, ) - if not source_quoted: + if source_quoted: + selection.args["alias"].set("quoted", True) + if dialect: dialect.normalize_identifier(selection.args["alias"]) if aliased_column: selection.set("alias", exp.to_identifier(aliased_column)) diff --git a/tests/fixtures/optimizer/qualify_columns.sql b/tests/fixtures/optimizer/qualify_columns.sql index f75a0a3ac4..35a5824fea 100644 --- a/tests/fixtures/optimizer/qualify_columns.sql +++ b/tests/fixtures/optimizer/qualify_columns.sql @@ -5,7 +5,7 @@ SELECT a FROM x; SELECT x.a AS a FROM x AS x; SELECT "a" FROM x; -SELECT x."a" AS a FROM x AS x; +SELECT x."a" AS "a" FROM x AS x; # execute: false SELECT a FROM zz GROUP BY a ORDER BY a; @@ -110,7 +110,7 @@ SELECT T."col" AS "col" FROM TBL T; # execute: false # dialect: oracle WITH base AS (SELECT x.dummy AS COL_1 FROM dual x) SELECT b."COL_1" FROM base b; -WITH BASE AS (SELECT X.DUMMY AS COL_1 FROM DUAL X) SELECT B."COL_1" AS COL_1 FROM BASE B; +WITH BASE AS (SELECT X.DUMMY AS COL_1 FROM DUAL X) SELECT B."COL_1" AS "COL_1" FROM BASE B; # execute: false -- this query seems to be invalid in postgres and duckdb but valid in bigquery From 762eb85e61896b761e3245a15f2ef5f561fc44a6 Mon Sep 17 00:00:00 2001 From: fivetran-kwoodbeck Date: Thu, 2 Jul 2026 13:32:28 -0400 Subject: [PATCH 03/13] tweak to deal with dialects that preserve case --- sqlglot/dialects/dialect.py | 7 +++++++ sqlglot/dialects/spark.py | 1 + sqlglot/optimizer/qualify_columns.py | 2 +- tests/test_optimizer.py | 2 +- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/sqlglot/dialects/dialect.py b/sqlglot/dialects/dialect.py index aa6304c41e..17a21e1f54 100644 --- a/sqlglot/dialects/dialect.py +++ b/sqlglot/dialects/dialect.py @@ -379,6 +379,13 @@ class Dialect(metaclass=_Dialect): e.g JSON_VALUE vs JSON_EXTRACT_SCALAR in BigQuery """ + PRESERVE_ORIGINAL_OUTPUT_NAME_CASE: bool = False + """ + Whether the dialect preserves the original case of derived output column names (e.g. from + semi-structured path access) rather than normalizing them. When True, qualify_outputs will + not apply normalize_identifier to synthesized aliases for non-Column expressions. + """ + LOG_BASE_FIRST: bool | None = True """ Whether the base comes first in the `LOG` function. diff --git a/sqlglot/dialects/spark.py b/sqlglot/dialects/spark.py index 0cc9ef7d07..4cf52dde15 100644 --- a/sqlglot/dialects/spark.py +++ b/sqlglot/dialects/spark.py @@ -14,6 +14,7 @@ class Spark(Spark2): SUPPORTS_LIMIT_ALL = True SUPPORTS_NULL_TYPE = True ARRAY_FUNCS_PROPAGATES_NULLS = True + PRESERVE_ORIGINAL_OUTPUT_NAME_CASE = True EXPRESSION_METADATA = EXPRESSION_METADATA.copy() class Tokenizer(Spark2.Tokenizer): diff --git a/sqlglot/optimizer/qualify_columns.py b/sqlglot/optimizer/qualify_columns.py index 84929477be..5d877ba65a 100644 --- a/sqlglot/optimizer/qualify_columns.py +++ b/sqlglot/optimizer/qualify_columns.py @@ -994,7 +994,7 @@ def qualify_outputs(scope_or_expression: Scope | exp.Expr, dialect: Dialect | No ) if source_quoted: selection.args["alias"].set("quoted", True) - if dialect: + if dialect and not (dialect.PRESERVE_ORIGINAL_OUTPUT_NAME_CASE): dialect.normalize_identifier(selection.args["alias"]) if aliased_column: selection.set("alias", exp.to_identifier(aliased_column)) diff --git a/tests/test_optimizer.py b/tests/test_optimizer.py index 38486cabc8..5578ad642e 100644 --- a/tests/test_optimizer.py +++ b/tests/test_optimizer.py @@ -2747,7 +2747,7 @@ def _parse_and_optimize(query: str, dialect: str) -> exp.Expr: # Databricks sql = _parse_and_optimize("SELECT col:A.a, col:a.A FROM t", dialect="databricks") - assert sql == "SELECT `t`.`col`:A.a AS `a`, `t`.`col`:a.A AS `a` FROM `t` AS `t`" + assert sql == "SELECT `t`.`col`:A.a AS `a`, `t`.`col`:a.A AS `A` FROM `t` AS `t`" # Clickhouse sql = _parse_and_optimize("SELECT col.A.a, col.a.A FROM t", dialect="clickhouse") From b854e229938ab3019932c3fc7706d393bab4b5cf Mon Sep 17 00:00:00 2001 From: fivetran-kwoodbeck Date: Thu, 2 Jul 2026 14:56:09 -0400 Subject: [PATCH 04/13] adjusted comment --- sqlglot/dialects/dialect.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sqlglot/dialects/dialect.py b/sqlglot/dialects/dialect.py index 17a21e1f54..1c5487cf24 100644 --- a/sqlglot/dialects/dialect.py +++ b/sqlglot/dialects/dialect.py @@ -381,9 +381,8 @@ class Dialect(metaclass=_Dialect): PRESERVE_ORIGINAL_OUTPUT_NAME_CASE: bool = False """ - Whether the dialect preserves the original case of derived output column names (e.g. from - semi-structured path access) rather than normalizing them. When True, qualify_outputs will - not apply normalize_identifier to synthesized aliases for non-Column expressions. + Whether the dialect preserves the original case of column aliases. When True, + qualify_outputs will not apply normalize_identifier to synthesized aliases. """ LOG_BASE_FIRST: bool | None = True From a76206ed2fc9d7dd54aedebb21eb93a8927edbb6 Mon Sep 17 00:00:00 2001 From: fivetran-kwoodbeck Date: Thu, 2 Jul 2026 14:56:29 -0400 Subject: [PATCH 05/13] updated tests --- sqlglot-integration-tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlglot-integration-tests b/sqlglot-integration-tests index 6b0ede1e19..2d768a7cfd 160000 --- a/sqlglot-integration-tests +++ b/sqlglot-integration-tests @@ -1 +1 @@ -Subproject commit 6b0ede1e19ff20ebbc3939e48eeb8f5f6db67fac +Subproject commit 2d768a7cfdcf77b179b5fc6677747626d1705c56 From 44caa9666ab7be2ad41b621cd662db02047c99b8 Mon Sep 17 00:00:00 2001 From: fivetran-kwoodbeck Date: Fri, 3 Jul 2026 11:06:06 -0400 Subject: [PATCH 06/13] updated tests --- sqlglot-integration-tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlglot-integration-tests b/sqlglot-integration-tests index 2d768a7cfd..99ceb986e9 160000 --- a/sqlglot-integration-tests +++ b/sqlglot-integration-tests @@ -1 +1 @@ -Subproject commit 2d768a7cfdcf77b179b5fc6677747626d1705c56 +Subproject commit 99ceb986e9ef7f2b82f3056c0a2a57787bc6176a From cf071d4208159b5bf536a5b89d686a8fd483146e Mon Sep 17 00:00:00 2001 From: fivetran-kwoodbeck Date: Fri, 3 Jul 2026 11:04:41 -0400 Subject: [PATCH 07/13] fixed test schema, made dialect non optional, and infers quotes from schema --- sqlglot/generators/tsql.py | 3 ++- sqlglot/optimizer/qualify_columns.py | 16 +++++++++++++--- tests/test_optimizer.py | 6 +++--- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/sqlglot/generators/tsql.py b/sqlglot/generators/tsql.py index 88de0bfc25..60c86a9f2a 100644 --- a/sqlglot/generators/tsql.py +++ b/sqlglot/generators/tsql.py @@ -74,6 +74,7 @@ def qualify_derived_table_outputs(expression: exp.Expr) -> exp.Expr: and isinstance(alias, exp.TableAlias) and not alias.columns ): + from sqlglot.dialects.tsql import TSQL from sqlglot.optimizer.qualify_columns import qualify_outputs # We keep track of the unaliased column projection indexes instead of the expressions @@ -84,7 +85,7 @@ def qualify_derived_table_outputs(expression: exp.Expr) -> exp.Expr: i for i, c in enumerate(query.selects) if isinstance(c, exp.Column) and not c.alias ) - qualify_outputs(query) + qualify_outputs(query, dialect=TSQL()) # Preserve the quoting information of columns for newly added Alias nodes query_selects = query.selects diff --git a/sqlglot/optimizer/qualify_columns.py b/sqlglot/optimizer/qualify_columns.py index 5d877ba65a..ba2ef7c17d 100644 --- a/sqlglot/optimizer/qualify_columns.py +++ b/sqlglot/optimizer/qualify_columns.py @@ -885,8 +885,18 @@ def _expand_stars( ) else: alias_ = renamed_columns.get(name, name) - selection_expr = replaced_columns.get(name) or exp.column(name, table=table) - if quoted_columns.get(name) and isinstance(selection_expr, exp.Column): + quoted = quoted_columns.get(name) or ( + # if it has characters that the dialect would have changed, infer that it was quoted. + not source_expression and dialect.case_sensitive(name) + ) + selection_expr = replaced_columns.get(name) or exp.column( + name, table=table, quoted=quoted + ) + if ( + quoted + and isinstance(selection_expr, exp.Column) + and not selection_expr.this.quoted + ): selection_expr.this.set("quoted", True) new_selections.append( alias(selection_expr, alias_, copy=False) @@ -960,7 +970,7 @@ def _add_replace_columns( replace_columns[id(table)] = columns -def qualify_outputs(scope_or_expression: Scope | exp.Expr, dialect: Dialect | None = None) -> None: +def qualify_outputs(scope_or_expression: Scope | exp.Expr, dialect: Dialect) -> None: """Ensure all output columns are aliased""" if isinstance(scope_or_expression, exp.Expr): scope = build_scope(scope_or_expression) diff --git a/tests/test_optimizer.py b/tests/test_optimizer.py index 5578ad642e..a792f14688 100644 --- a/tests/test_optimizer.py +++ b/tests/test_optimizer.py @@ -2423,7 +2423,7 @@ def test_quotes(self): schema = { "example": { '"source"': { - "id": "text", + '"ID"': "text", '"name"': "text", '"payload"': "text", } @@ -2434,8 +2434,8 @@ def test_quotes(self): """ SELECT "source"."ID" AS "ID", - "source"."name" AS "NAME", - "source"."payload" AS "PAYLOAD" + "source"."name" AS "name", + "source"."payload" AS "payload" FROM "EXAMPLE"."source" AS "source" """, read="snowflake", From eb6c093b21ba6105cf6723c48703125754a05d03 Mon Sep 17 00:00:00 2001 From: fivetran-kwoodbeck Date: Fri, 3 Jul 2026 11:15:47 -0400 Subject: [PATCH 08/13] updated tests --- sqlglot-integration-tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlglot-integration-tests b/sqlglot-integration-tests index 99ceb986e9..26ac91d990 160000 --- a/sqlglot-integration-tests +++ b/sqlglot-integration-tests @@ -1 +1 @@ -Subproject commit 99ceb986e9ef7f2b82f3056c0a2a57787bc6176a +Subproject commit 26ac91d9903dfe7c6fd85cafb1a678c17d0988b9 From 4bccdf67d68ccf46965f6dec934e55724603f3f5 Mon Sep 17 00:00:00 2001 From: fivetran-kwoodbeck Date: Fri, 3 Jul 2026 12:49:17 -0400 Subject: [PATCH 09/13] minor tweak --- sqlglot-integration-tests | 2 +- sqlglot/optimizer/qualify_columns.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sqlglot-integration-tests b/sqlglot-integration-tests index 26ac91d990..5fc1675ee1 160000 --- a/sqlglot-integration-tests +++ b/sqlglot-integration-tests @@ -1 +1 @@ -Subproject commit 26ac91d9903dfe7c6fd85cafb1a678c17d0988b9 +Subproject commit 5fc1675ee1182d3e574482b1960ea5f504301e2b diff --git a/sqlglot/optimizer/qualify_columns.py b/sqlglot/optimizer/qualify_columns.py index ba2ef7c17d..2cfdfab91d 100644 --- a/sqlglot/optimizer/qualify_columns.py +++ b/sqlglot/optimizer/qualify_columns.py @@ -887,7 +887,7 @@ def _expand_stars( alias_ = renamed_columns.get(name, name) quoted = quoted_columns.get(name) or ( # if it has characters that the dialect would have changed, infer that it was quoted. - not source_expression and dialect.case_sensitive(name) + isinstance(source, exp.Table) and dialect.case_sensitive(name) ) selection_expr = replaced_columns.get(name) or exp.column( name, table=table, quoted=quoted From 13a847cd7b9eed11ffcbe854339f7f3411be680c Mon Sep 17 00:00:00 2001 From: fivetran-kwoodbeck Date: Fri, 3 Jul 2026 12:53:37 -0400 Subject: [PATCH 10/13] fixed tests --- sqlglot-integration-tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlglot-integration-tests b/sqlglot-integration-tests index 5fc1675ee1..946b3d8d39 160000 --- a/sqlglot-integration-tests +++ b/sqlglot-integration-tests @@ -1 +1 @@ -Subproject commit 5fc1675ee1182d3e574482b1960ea5f504301e2b +Subproject commit 946b3d8d391d2e9af5706979cd72120506c823a0 From e4f77f889f83eda542196438f7c742acf6aa6329 Mon Sep 17 00:00:00 2001 From: fivetran-kwoodbeck Date: Fri, 3 Jul 2026 13:05:40 -0400 Subject: [PATCH 11/13] updated tests --- sqlglot-integration-tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlglot-integration-tests b/sqlglot-integration-tests index 946b3d8d39..05211d46a0 160000 --- a/sqlglot-integration-tests +++ b/sqlglot-integration-tests @@ -1 +1 @@ -Subproject commit 946b3d8d391d2e9af5706979cd72120506c823a0 +Subproject commit 05211d46a042e160d1694b61516387ec37760764 From 1c765e15347ca77724b07384bbd5e6cd0c582dd4 Mon Sep 17 00:00:00 2001 From: fivetran-kwoodbeck Date: Fri, 3 Jul 2026 13:41:07 -0400 Subject: [PATCH 12/13] fixed other branch --- sqlglot-integration-tests | 2 +- sqlglot/optimizer/qualify_columns.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/sqlglot-integration-tests b/sqlglot-integration-tests index 05211d46a0..0f179b342f 160000 --- a/sqlglot-integration-tests +++ b/sqlglot-integration-tests @@ -1 +1 @@ -Subproject commit 05211d46a042e160d1694b61516387ec37760764 +Subproject commit 0f179b342fa2a9f73b5d156a7fed914e15f1ac8f diff --git a/sqlglot/optimizer/qualify_columns.py b/sqlglot/optimizer/qualify_columns.py index 2cfdfab91d..162d80ef18 100644 --- a/sqlglot/optimizer/qualify_columns.py +++ b/sqlglot/optimizer/qualify_columns.py @@ -994,7 +994,10 @@ def qualify_outputs(scope_or_expression: Scope | exp.Expr, dialect: Dialect) -> if isinstance(selection, exp.Subquery): if not selection.output_name: - selection.set("alias", exp.TableAlias(this=exp.to_identifier(f"_col_{i}"))) + alias_identifier = exp.to_identifier(f"_col_{i}") + if dialect and not (dialect.PRESERVE_ORIGINAL_OUTPUT_NAME_CASE): + dialect.normalize_identifier(alias_identifier) + selection.set("alias", exp.TableAlias(this=alias_identifier)) elif not isinstance(selection, (exp.Alias, exp.Aliases)) and not selection.is_star: source_quoted = isinstance(selection, exp.Column) and selection.this.quoted selection = alias( From 3003df79b739c8e257b3fd45876bd538ca351b56 Mon Sep 17 00:00:00 2001 From: fivetran-kwoodbeck Date: Fri, 3 Jul 2026 14:01:23 -0400 Subject: [PATCH 13/13] formatting --- sqlglot-integration-tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlglot-integration-tests b/sqlglot-integration-tests index 0f179b342f..8f9acede6d 160000 --- a/sqlglot-integration-tests +++ b/sqlglot-integration-tests @@ -1 +1 @@ -Subproject commit 0f179b342fa2a9f73b5d156a7fed914e15f1ac8f +Subproject commit 8f9acede6de5aa48db36258db1d90cb628d59d17