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.
5.5 KiB
ADR-0039: EXPLAIN over advanced-mode SQL queries
Status
Accepted — decision recorded 2026-05-27. Implemented 2026-05-30 (issue #7; see Implementation below). Supersedes ADR-0030 §13 OOS-2.
Context
ADR-0028 gave the DSL explain command: a prefix over show data /
update / delete that runs EXPLAIN QUERY PLAN and renders an
annotated, span-styled plan tree. ADR-0030 §13 OOS-2 excluded
"EXPLAIN of advanced-mode SQL queries."
On readback (2026-05-27) that exclusion is a deferred out-of-scope
item, not a rejected one (see ADR-0000's out-of-scope discipline):
its own wording — "the DSL explain still works for what it already
wraps" — shows it was "not included in this surface," never "undesirable
for teaching." There was no pedagogical argument against it; it simply
fell outside the Phase-4/5 SQL-surface scope. It surfaced while
characterising advanced-mode explain (briefly suspected a bug; it was
OOS-2 behaving exactly as written).
Letting a learner see the plan for the SQL they wrote is a natural extension of ADR-0028's intent, so OOS-2 is lifted.
Decision
explain works over advanced-mode SQL queries — the SQL commands
Select / SqlInsert / SqlUpdate / SqlDelete — in addition to the
DSL ShowData / Update / Delete it already wraps (ADR-0028). It runs
EXPLAIN QUERY PLAN over the command's validated SQL text and renders
through the existing ADR-0028 plan tree. Advanced mode only (the SQL
commands are advanced-only); the DSL explain stays available in both
modes, unchanged. Supersedes ADR-0030 §13 OOS-2.
Design sketch (deferred to the build)
- Grammar. The
explaininner gains the SQL statement shapes in advanced mode, alongside the DSL trio — mirroring howexplainalready wraps the DSL nodes, here wrapping the SQL command shapes. - Execution. Run
EXPLAIN QUERY PLANover the carried SQL text (theSql*/Selectcommands already hold validated text); reuse ADR-0028's plan capture + renderer.EXPLAIN QUERY PLANnever executes the statement, so explaining a destructive SQL command is safe — the same property ADR-0028 already relies on. - Mode. SQL inner only in advanced mode; DSL inner in both, unchanged.
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
explainCommandNodes under one entry word. The originaldata::EXPLAIN(Simple, DSL inner) is unchanged. A newdata::EXPLAIN_SQL(Advanced) carriesEXPLAIN_SQL_SHAPE— aChoiceoverselect/with/insert/update/delete, each[Word, Subgrammar(&SQL_*_SHAPE)]reusing the standalone SQL command shapes. Both register underexplaininREGISTRY, exactly mirroring theinsert/update/deleteshared-word pattern (ADR-0033 §2). The walker'sdecidethen does all the work: advanced mode triesEXPLAIN_SQLfirst and falls back to the DSLEXPLAINwhen 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 aCommand::Select, in scope per the decision's AST naming. - Rejected
DynamicSubgrammarmode-gating. A factory readingctx.modewould be memoised wrongly: the dynamic-resolution cache key omitsmode, 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_sqlslices the inner SQL text from the source starting at the inner entry keyword's span (so the carried text excludesexplain), then delegates to the existingbuild_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_plangains arms forSelect/SqlInsert/SqlUpdate/SqlDeletethat runEXPLAIN QUERY PLANover the carried SQL text verbatim, no bound params (grammar-as-text).display_sqlis 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 PLANapplies toSELECT/INSERT/UPDATE/DELETE; DDL has no query plan. (Deferred — may be revisited if a useful rendering emerges; per ADR-0000's out-of-scope discipline, this is deferred, not rejected.)
Consequences
- A self-contained feature, orthogonal to the DSL → SQL echo (ADR-0038): the echo renders SQL from DSL commands; this explains SQL the user wrote. They share nothing but the plan renderer's lineage.
- One OOS item in ADR-0030 §13 is now superseded; the rest stand.
See also
- ADR-0028 — the DSL
explainand the span-styled plan tree this reuses. - ADR-0030 §13 — OOS-2, superseded here.
- ADR-0032 / ADR-0033 — the SQL
SELECT/ DML this explains. - ADR-0000 — the out-of-scope discipline that reframed OOS-2 as deferred.