diff --git a/wren/src/wren/mdl/cte_rewriter.py b/wren/src/wren/mdl/cte_rewriter.py index d4a59fdfc..fe373687f 100644 --- a/wren/src/wren/mdl/cte_rewriter.py +++ b/wren/src/wren/mdl/cte_rewriter.py @@ -226,9 +226,10 @@ def _collect_user_cte_names(ast: exp.Expression) -> set[str]: for cte in with_clause.expressions: alias = cte.args.get("alias") if alias: - names.add( + raw = ( alias.this.name if isinstance(alias.this, exp.Identifier) else str(alias.this) ) + names.add(raw.lower()) return names diff --git a/wren/tests/unit/test_cte_rewriter.py b/wren/tests/unit/test_cte_rewriter.py index aec5d122e..7395ef90c 100644 --- a/wren/tests/unit/test_cte_rewriter.py +++ b/wren/tests/unit/test_cte_rewriter.py @@ -208,6 +208,22 @@ def test_user_cte_shadows_model(self): # (wren-core may restructure the query but won't add a model CTE) assert not _has_cte(result, "orders") or _count_ctes(result) <= 1 + def test_user_cte_shadows_model_case_insensitive(self): + """User CTE 'Orders' (mixed case) should shadow model 'orders'.""" + rw = _make_rewriter(_SINGLE_MODEL_MANIFEST, fallback=True) + result = rw.rewrite( + "WITH Orders AS (SELECT 1 AS o_orderkey, 'OPEN' AS o_orderstatus) " + "SELECT * FROM Orders" + ) + ast = sqlglot.parse_one(result, dialect="duckdb") + with_clause = ast.args.get("with_") + if with_clause: + cte_names = [ + cte.args["alias"].this.name for cte in with_clause.expressions + ] + orders_count = sum(1 for n in cte_names if n.lower() == "orders") + assert orders_count <= 1, f"Duplicate 'orders' CTE: {result}" + def test_nested_cte_shadows_model(self): """CTE defined in a subquery WITH should also shadow the model name.""" rw = _make_rewriter(_MULTI_MODEL_MANIFEST, fallback=True)