feat: support explain over advanced-mode SQL queries

explain now wraps the advanced SQL commands — select, with (CTE),
insert, update, delete — in addition to the DSL show data/update/
delete it already covered, rendering through the same plan tree
(ADR-0039, closing the ADR-0030 OOS-2 gap).

Implemented as a second Advanced `explain` CommandNode under the
shared entry word, reusing the established shared-word dispatch
(SQL-first, DSL-fallback) rather than new grammar machinery.
build_explain_sql slices the inner SQL off the source and reuses the
existing SQL builders; do_explain_plan runs EXPLAIN QUERY PLAN over
the carried text verbatim (never executes, so safe for destructive
verbs). Advanced explain update/delete now route through SQL with an
identical plan; DSL-explain tests pinned to simple mode. Help and
usage text now list the advanced explain forms.
This commit is contained in:
claude@clouddev1
2026-05-30 18:44:05 +00:00
parent f7ca288fe1
commit f62cccec55
8 changed files with 503 additions and 14 deletions
+45 -5
View File
@@ -2,11 +2,8 @@
## Status
**Accepted** — decision recorded 2026-05-27. **Implementation deferred**
as a follow-up to the ADR-0037/ADR-0038 teaching-echo effort: the
decision below is settled, but the full design has not been `/runda`'d
or built, and is *not* part of that pass. **Supersedes ADR-0030 §13
OOS-2.**
**Accepted** — decision recorded 2026-05-27. **Implemented 2026-05-30**
(issue #7; see Implementation below). **Supersedes ADR-0030 §13 OOS-2.**
## Context
@@ -51,6 +48,49 @@ modes, unchanged. **Supersedes ADR-0030 §13 OOS-2.**
Built test-first when picked up.
## Implementation (2026-05-30)
Built as designed, with the mode-gating and DSL/SQL disambiguation
handled by the **existing shared-entry-word dispatch** rather than any
new grammar machinery:
- **Two `explain` CommandNodes under one entry word.** The original
`data::EXPLAIN` (`Simple`, DSL inner) is unchanged. A new
`data::EXPLAIN_SQL` (`Advanced`) carries `EXPLAIN_SQL_SHAPE` — a
`Choice` over `select` / `with` / `insert` / `update` / `delete`,
each `[Word, Subgrammar(&SQL_*_SHAPE)]` reusing the standalone SQL
command shapes. Both register under `explain` in `REGISTRY`, exactly
mirroring the `insert`/`update`/`delete` shared-word pattern
(ADR-0033 §2). The walker's `decide` then does all the work: advanced
mode tries `EXPLAIN_SQL` first and falls back to the DSL `EXPLAIN`
when no SQL branch matches (`explain show data …`, or a DSL-only
`--all-rows`); simple mode reaches only the DSL node. **`with` (CTE)
is included** — it builds a `Command::Select`, in scope per the
decision's AST naming.
- **Rejected `DynamicSubgrammar` mode-gating.** A factory reading
`ctx.mode` would be memoised wrongly: the dynamic-resolution cache
key omits `mode`, so a node resolved in one mode would be served back
in the other. The two-CommandNode route avoids this and stays on the
established dispatch path.
- **Clean inner SQL.** `build_explain_sql` slices the inner SQL text
from the source starting at the inner entry keyword's span (so the
carried text excludes `explain`), then delegates to the existing
`build_select` / `build_sql_*` builders. Their metadata extraction
(target table, etc.) reads the path by role, which is offset-
independent, so wrapping is transparent.
- **Execution.** `do_explain_plan` gains arms for
`Select` / `SqlInsert` / `SqlUpdate` / `SqlDelete` that run
`EXPLAIN QUERY PLAN` over the carried SQL text verbatim, no bound
params (grammar-as-text). `display_sql` is the user's text as written
(the DSL path canonicalises only because it *synthesises* SQL). The
ADR-0028 renderer is reused unchanged.
- **Behaviour note.** In advanced mode `explain update …` /
`explain delete …` now route through the SQL path (previously the
DSL inner). The plan is identical (§6/§7 parity), and the SQL grammar
accepts the full SQL syntax the DSL grammar rejected. DSL-explain
tests were pinned to simple mode; advanced SQL wrapping has its own
tests.
## Out of scope
- **EXPLAIN of DDL** (`CREATE` / `ALTER` / `DROP`). `EXPLAIN QUERY PLAN`