@@ -121,6 +121,19 @@ def _count_ctes(sql: str, dialect: str = "duckdb") -> int:
121121 return len (with_clause .expressions )
122122
123123
124+ def _cte_body_sql (sql : str , cte_name : str , dialect : str = "duckdb" ) -> str | None :
125+ """Return the SQL body of a named CTE, or None if not found."""
126+ ast = sqlglot .parse_one (sql , dialect = dialect )
127+ with_clause = ast .args .get ("with_" )
128+ if not with_clause :
129+ return None
130+ for cte in with_clause .expressions :
131+ alias = cte .args .get ("alias" )
132+ if alias and alias .this .name == cte_name :
133+ return cte .this .sql (dialect = dialect )
134+ return None
135+
136+
124137# ---------------------------------------------------------------------------
125138# Tests: basic model resolution
126139# ---------------------------------------------------------------------------
@@ -134,10 +147,20 @@ def test_single_model_generates_cte(self):
134147 assert isinstance (result , str )
135148 assert len (result ) > 0
136149
137- def test_star_expansion (self ):
150+ def test_star_not_expanded (self ):
151+ """SELECT * should pass star to wren-core so all columns are included."""
138152 rw = _make_rewriter (_SINGLE_MODEL_MANIFEST )
139153 result = rw .rewrite ('SELECT * FROM "orders"' )
140154 assert _has_cte (result , "orders" )
155+ cte_body = _cte_body_sql (result , "orders" )
156+ assert cte_body is not None
157+ # wren-core expands SELECT * into all model columns — verify every
158+ # non-hidden, non-relationship column is present in the CTE body.
159+ body_lower = cte_body .lower ()
160+ for col_name in ("o_orderkey" , "o_custkey" , "o_orderstatus" , "order_cust_key" ):
161+ assert col_name .lower () in body_lower , (
162+ f"Expected column { col_name !r} in CTE body: { cte_body } "
163+ )
141164
142165 def test_alias_resolution (self ):
143166 rw = _make_rewriter (_SINGLE_MODEL_MANIFEST )
@@ -163,10 +186,39 @@ def test_join_generates_two_ctes(self):
163186 assert _has_cte (result , "customer" )
164187 assert _count_ctes (result ) >= 2
165188
166- def test_qualified_star (self ):
189+ def test_qualified_star_not_expanded (self ):
190+ """SELECT table.* should pass star to wren-core so all columns appear."""
167191 rw = _make_rewriter (_MULTI_MODEL_MANIFEST )
168192 result = rw .rewrite ('SELECT "orders".* FROM "orders"' )
169193 assert _has_cte (result , "orders" )
194+ cte_body = _cte_body_sql (result , "orders" )
195+ assert cte_body is not None
196+ body_lower = cte_body .lower ()
197+ for col_name in ("o_orderkey" , "o_custkey" , "o_orderstatus" , "order_cust_key" ):
198+ assert col_name .lower () in body_lower , (
199+ f"Expected column { col_name !r} in CTE body: { cte_body } "
200+ )
201+
202+ def test_mixed_star_and_explicit_columns (self ):
203+ """orders.* should include all columns; customer CTE only referenced ones."""
204+ rw = _make_rewriter (_MULTI_MODEL_MANIFEST )
205+ result = rw .rewrite (
206+ 'SELECT "orders".*, c.c_name '
207+ 'FROM "orders" JOIN "customer" c ON "orders".o_custkey = c.c_custkey'
208+ )
209+ assert _has_cte (result , "orders" )
210+ assert _has_cte (result , "customer" )
211+ # orders used star → all columns present
212+ orders_body = _cte_body_sql (result , "orders" )
213+ assert orders_body is not None
214+ orders_lower = orders_body .lower ()
215+ for col_name in ("o_orderkey" , "o_custkey" , "o_orderstatus" , "order_cust_key" ):
216+ assert col_name .lower () in orders_lower
217+ # customer used explicit column → only c_name (not necessarily c_custkey
218+ # in the select list, though it may appear in the subquery structure)
219+ customer_body = _cte_body_sql (result , "customer" )
220+ assert customer_body is not None
221+ assert "c_name" in customer_body .lower ()
170222
171223
172224# ---------------------------------------------------------------------------
0 commit comments