Estado: ✅ Aceptada Fecha: 2026-05-28 Bloque: W1 (primer sub-bloque del bloque W del roadmap) Bump on-disk: ninguno (las CTEs viven solo en runtime — no se persisten)

🧭 Contexto

El bloque W del roadmap agrupa CTEs y window functions como una sola entrega “muy alta complejidad”. Ese bundle es demasiado grande para un push siguiendo el workflow 1 bloque = 1 push. Lo partimos en tres sub-bloques independientes:

W1 cierra la pieza más común y de mayor valor: dar nombre a una subquery para reusarla, partir queries grandes en piezas legibles, y encadenar CTEs (WITH a AS (...), b AS (SELECT FROM a) ...).

💡 Decisión

1. Inlining en parse-time como derived tables

En lugar de extender el AST con un campo ctes: Vec<CommonTableExpr> en SelectStmt y propagar un “CTE scope” recursivamente por todo el Engine (invasivo, toca subqueries, JOIN scope, materialización), el parser reescribe el AST: toda referencia bare a un nombre de CTE en cualquier FROM / JOIN / subquery se substituye por un derived_source clonado del body de la CTE.

Después del parse, el SelectStmt resultante es indistinguible de uno donde el usuario hubiera escrito las subqueries inline. El executor no necesita saber que existen CTEs — ya las “ve” como derived tables, infraestructura entregada en el bloque H.

Trade-off: si una CTE se referencia N veces, su body se materializa N veces (re-ejecución). Para W1 es aceptable; la optimización (memoización por nombre, similar al fix Issue #1 del BENCHMARK) queda como deuda explícita.

2. Resolución de nombres y precedencia sobre tablas reales

La CTE gana sobre cualquier tabla del catálogo con el mismo nombre. La reescritura del AST sucede ANTES de la expansión de vistas (expand_view_in_from) y antes del catalog lookup en el executor, así que el FROM cte_name queda transformado en derived_source y nunca se consulta el catálogo. Esto matchea la semántica ANSI y la de PostgreSQL/SQLite.

Las referencias DENTRO del body de una CTE al MISMO nombre NO se reescriben (eso sería self-recursión, que requiere WITH RECURSIVE). El orden en inline_cte_into_select es deliberado: primero recursamos en derived_source pre-existente, recién después instalamos el nuevo derived. Una vez instalado, no volvemos a recursar dentro (el body ya fue procesado contra las CTEs declaradas ANTES en la misma cláusula WITH).

3. Encadenamiento de CTEs

WITH a AS (...), b AS (SELECT * FROM a) funciona: cuando parseamos el body de b, recorremos las CTEs previamente registradas (a) e inlineamos sus referencias en el body de b antes de guardar b. El orden de declaración es estricto — b puede usar a pero a no puede usar b (forward references rechazadas implícitamente porque b no está en la lista cuando se parsea a).

4. Set ops como query principal

WITH cte AS (...) SELECT ... UNION SELECT ... FROM cte está soportado: después del WITH parseamos la query principal vía parse_select_stmt + parse_set_ops_after, obteniendo un SelectQuery que puede ser un árbol de SetOp. El walker inline_cte_into_query recorre todas las ramas (SelectStmt hojas del árbol) e inlinea las CTEs en cada una. Las dos ramas del UNION ven la misma CTE.

5. Pendientes diferidos (rechazados con código explícito)

6. Sin bump de formato on-disk

Las CTEs viven solo en el AST en runtime. No tocan Catalog, no agregan slots a TableMeta, no cambian la serialización. La constante VERSION se mantiene en 13 (V de vistas). Una BD V13 abierta por un binario sin soporte de W1 simplemente devolvería “tabla no existe” al ver FROM cte_name — fail-safe.

📐 Códigos de error

Código Nombre Cuándo
GBY-4079 CTE_DUPLICATE_NAME Dos CTEs con el mismo nombre dentro del mismo WITH (case-insensitive).
GBY-4080 CTE_RECURSIVE_NOT_SUPPORTED WITH RECURSIVE — diferido a W2. Actualización 2026-06-15: W2 entregado por ADR-0027 el 2026-05-28; [GBY-4080] ya no se emite desde el path WITH RECURSIVE.
GBY-4081 CTE_COLUMN_ALIASES_NOT_SUPPORTED WITH name(c1, c2) AS (...) — diferido. Workaround inline.

🧪 Validación

Suite w1_* en tests/integration_test.rs (10 tests):

Suite total: 387/387 pass (cargo test --lib --tests).

🔭 Futuro