You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(session): route WITH queries through Session::execute_query (issue 07)
CTE execution existed via PlanExecutor::execute_with_cte but the main
Session::execute_query path didn't use it -- WITH queries through the
user API silently produced wrong results because PlanBuilder::build_cte
built only the main SELECT plan without registering CTE materialisations.
Test coverage validated the executor's special path, not the API users
actually call.
Fix:
- Session::execute_query now detects pr.ast->type == NODE_CTE after
parsing. CTE queries route to executor.execute_with_cte(pr.ast) and
deliberately bypass the plan cache. Non-CTE queries follow the
existing parse -> plan -> optimize -> distribute -> execute -> cache
pipeline unchanged.
- PlanExecutor::execute_with_cte gains distribute_fn_ application: the
inner SELECT of each CTE definition and the main query both go
through the distributor when one is set, so CTE queries that scan
sharded tables work end-to-end. Materialised CTE rows themselves
live locally; the distributor leaves them alone.
- The CTE path is intentionally not cached. Per-call re-parse +
re-materialise is the trade-off for correctness; caching build_cte's
plan would silently skip materialisation on every cache hit.
Tests:
- tests/test_session.cpp adds four cases that exercise CTEs through
the user API:
* CteSimple -- WITH ... SELECT *
* CteWithAggregation -- WITH ... GROUP BY then outer filter
* CteMultipleDefinitions -- two CTEs, one consumed
* CteFilteredAfterMaterialisation -- outer WHERE against the CTE
- tests/test_cte.cpp unchanged; the executor-direct path still works
and is regression-tested.
Verification:
- make test: 1212 passed (+4 new), 37 skipped (live-backend), 0 failed.
Future work (separate issues / follow-ups):
- Recursive WITH RECURSIVE (parser-side gaps tracked under issue 06).
- Plan-cache awareness for CTE queries (cache the AST + build pipeline
so we re-materialise without re-parsing).
Copy file name to clipboardExpand all lines: docs/issues/07-session-cte-integration.md
+29Lines changed: 29 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -36,3 +36,32 @@ CTE execution exists through `PlanExecutor::execute_with_cte()`, but the main `S
36
36
- Existing CTE tests can be migrated or expanded to exercise the main path
37
37
- CTE support remains intentionally limited until recursive support is explicitly tackled
38
38
39
+
## Status
40
+
41
+
Resolved 2026-04-18.
42
+
43
+
## Resolution Notes
44
+
45
+
`Session::execute_query` now detects `NODE_CTE` at the AST root and routes those queries through `PlanExecutor::execute_with_cte`. CTE materialisation runs per-call: each CTE definition is built and executed, then registered into the catalog as a synthetic in-memory table; the main `SELECT` then runs against those synthetic tables.
46
+
47
+
CTE queries deliberately bypass the plan cache. `PlanBuilder::build_cte` only builds the main `SELECT` — caching that plan would silently return wrong results because it would not re-materialise the CTE rows. Per-call re-parse + re-materialise is the trade-off for correctness; recursive CTE optimisation remains explicitly out of scope.
48
+
49
+
`PlanExecutor::execute_with_cte` was extended to apply the executor's distribute callback to (a) the inner SELECT for each CTE definition and (b) the main query. CTE queries against sharded tables now go through the distributed planner the same way non-CTE queries do; the materialised CTE rows themselves live locally.
50
+
51
+
### Test Coverage
52
+
53
+
`tests/test_session.cpp` adds four cases that exercise CTEs through the user API:
54
+
55
+
-`CteSimple` — `WITH … SELECT *`
56
+
-`CteWithAggregation` — `WITH … GROUP BY` then outer filter
57
+
-`CteMultipleDefinitions` — two CTEs, one consumed
58
+
-`CteFilteredAfterMaterialisation` — outer `WHERE` against the materialised CTE
59
+
60
+
The existing `tests/test_cte.cpp` cases (which call `execute_with_cte` directly) are unchanged and still pass.
61
+
62
+
### Future Work
63
+
64
+
- Recursive `WITH RECURSIVE` (still deferred — see issue 06 for parser-side coverage gaps).
65
+
- Plan-cache awareness for CTE queries (cache the AST + build pipeline so we re-materialise without re-parsing).
66
+
- Push CTE materialisation across the boundary into ProxySQL when this engine is embedded — large-result CTEs may want to live on a backend rather than be pulled local.
0 commit comments