Files
rdbms-playground/docs/adr/0039-explain-over-advanced-sql.md
T
claude@clouddev1 f62cccec55 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.
2026-05-30 18:44:05 +00:00

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 explain inner gains the SQL statement shapes in advanced mode, alongside the DSL trio — mirroring how explain already wraps the DSL nodes, here wrapping the SQL command shapes.
  • Execution. Run EXPLAIN QUERY PLAN over the carried SQL text (the Sql* / Select commands already hold validated text); reuse ADR-0028's plan capture + renderer. EXPLAIN QUERY PLAN never 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 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 applies to SELECT / 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 explain and 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.