docs(adr): design the DSL→SQL teaching echo (ADR-0038) + dependencies
Realises ADR-0030 §10 (the DSL→SQL teaching bridge) as a /runda'd design
set, before implementation:
- ADR-0037 (new): execution-time mode side-channel — SubmissionMode
{Simple, Advanced, AdvancedOneShot} threaded Action→worker, output-only;
redeems ADR-0033 Amendment 3's deferred follow-up. Replay stays silent.
- ADR-0038 (new): the teaching echo + full catalogue (Buckets A/B/C),
the copy-paste round-trip contract, the three-category framework, and
the Value→SQL-literal renderer. DDL + show-data centric (overlapping
DML is SQL-first, so already SQL). Build-order deps recorded.
- ADR-0035 Amendment 2: standard-first dialect stance + ALTER COLUMN
SET/DROP NOT NULL, SET/DROP DEFAULT, ISO SET DATA TYPE gap-fill.
- ADR-0033 Amendment 4: reclassifies the `update … --all-rows`
non-fall-back as a bug; it now falls back to the DSL Update and echoes
(keyed on adjacent `--`; spaced arithmetic preserved).
- ADR-0039 (new): EXPLAIN over advanced SQL — decision recorded, build
deferred; supersedes ADR-0030 §13 OOS-2.
- ADR-0000: out-of-scope discipline (deferred vs rejected). README index
updated for all of the above.
Reconcile CLAUDE.md: simple-mode column ops are implemented, not pending
(requirements.md C2/B2 already [x]).
This commit is contained in:
@@ -38,6 +38,25 @@ The index lists ADRs in numerical order. Each entry shows the
|
||||
number, title, and — where relevant — status annotations such as
|
||||
"Superseded by ADR-NNNN" or "Deprecated".
|
||||
|
||||
## Out-of-scope discipline
|
||||
|
||||
ADRs (and the plans they spawn) lean heavily on "out of scope" language.
|
||||
The phrase carries two very different meanings, and conflating them
|
||||
misleads a later reader:
|
||||
|
||||
- **Deferred** — out of scope *for this plan / phase / step*, but a
|
||||
reasonable thing to do later. A sequencing decision, effectively a
|
||||
tracked TODO. Where possible, point at where it will be picked up.
|
||||
- **Rejected** — considered and deliberately *not* done, on principle.
|
||||
Durable. State the reason.
|
||||
|
||||
When writing an out-of-scope item, **say which kind it is** — e.g.
|
||||
`OOS (deferred)` / `OOS (rejected: <reason>)`, or the equivalent in prose
|
||||
— so a future reader can tell a standing decision from a not-yet. A bare
|
||||
"out of scope" is ambiguous and tends to read, wrongly, as permanent.
|
||||
(Motivating example: ADR-0030 §13 OOS-2 was a deferred scope exclusion
|
||||
that read as a permanent rejection until ADR-0039 lifted it.)
|
||||
|
||||
## Consequences
|
||||
|
||||
- New significant decisions require an ADR before or alongside the
|
||||
|
||||
@@ -301,6 +301,9 @@ until then.
|
||||
by the items panel's design (`S2`) but need their own model.
|
||||
- **OOS-2.** `EXPLAIN` of advanced-mode SQL queries. The DSL
|
||||
`explain` (ADR-0028) still works for what it already wraps.
|
||||
**(Superseded by ADR-0039, 2026-05-27 — this was a *deferred*
|
||||
scope exclusion, not a principled rejection; EXPLAIN over advanced
|
||||
SQL is now in scope, as a deferred follow-up.)**
|
||||
- **OOS-3.** A function/expression allowlist for full
|
||||
expression-level engine neutrality (§7) — best-effort now.
|
||||
- **OOS-4.** Multi-statement batches and transaction control.
|
||||
|
||||
@@ -1476,6 +1476,83 @@ forward-references that future ADR so the requirement is not lost.
|
||||
side-channel until then. No structure built in 3j assumes mode is
|
||||
irrelevant to execution.
|
||||
|
||||
## Amendment 4 — `update … --all-rows` falls back to the DSL; Amendment 3's counter-example was a bug (2026-05-27)
|
||||
|
||||
Designing the DSL → SQL teaching echo (ADR-0038) re-examined Amendment 3's
|
||||
**counter-example** — the claim that `update T set x = 42 --all-rows` in
|
||||
Advanced mode does *not* fall back to the DSL because the SQL `UPDATE`'s
|
||||
`SET <expr>` "greedily consumes `--all-rows` as the expression
|
||||
`42 - -all - rows` (with `all`/`rows` as column refs)." **That is a bug,
|
||||
not correct behaviour**, and this amendment reverses it. Recorded with
|
||||
explicit user approval (2026-05-27).
|
||||
|
||||
### Why it is a bug
|
||||
|
||||
- The walker has **no `--` comment support** (it lexes two minus
|
||||
operators); the engine *does* treat `--` as a line comment. So the
|
||||
walker's parse (`42 - -all - rows`) **diverges from execution** (which
|
||||
runs `update T set x = 42`).
|
||||
- Absent columns literally named `all` and `rows`, the walker is
|
||||
**silently accepting a `SET` expression over non-existent columns** —
|
||||
precisely the case ADR-0027 says to flag ("mark error if we know it
|
||||
will fail at runtime"). It only *appears* to succeed because the
|
||||
engine's comment leniency masks the divergence.
|
||||
- **Trailing SQL comments are not a supported feature** of the surface,
|
||||
so relying on the engine to treat `--all-rows` as one is wrong.
|
||||
|
||||
So the SQL `UPDATE` shape should **not** match `… --all-rows`.
|
||||
|
||||
### Decision
|
||||
|
||||
The `--all-rows` token sequence makes the **SQL `UPDATE` shape fail**, so
|
||||
dispatch **falls back to the DSL** `Command::Update { filter: AllRows }`
|
||||
— restoring symmetry with `delete … --all-rows`, which already falls
|
||||
back (the SQL `DELETE` has no slot to absorb the flag). Consequences:
|
||||
|
||||
- `update … --all-rows` in Advanced mode is the **DSL** command, runs all
|
||||
rows via the safe DSL path, and (ADR-0038) gains the teaching echo
|
||||
`UPDATE T SET … ` with the `WHERE` omitted.
|
||||
- **No `--` comment feature is introduced**; the playground still does
|
||||
not support trailing SQL comments (consistent with the rest of the
|
||||
surface, engine-neutral).
|
||||
|
||||
### Mechanism — key on the **adjacent `--`**
|
||||
|
||||
The DSL `--all-rows` is matched by `Node::Flag("all-rows")` via
|
||||
`consume_flag` (`walker/lex_helpers.rs`), which requires an **adjacent
|
||||
`--`**. The SQL expression grammar has no flag node, so it consumes the
|
||||
same source as `- -all - rows` (two minus operators, `all`/`rows` as
|
||||
column refs) and the SQL `UPDATE` shape wins (SQL-first). Crucially,
|
||||
`--all-rows` and a legitimate `- -all` tokenise *identically* once split,
|
||||
so the **only** robust discriminator is the adjacency of the two dashes:
|
||||
|
||||
- **The fix:** the SQL expression treats an **adjacent `--`** as a
|
||||
boundary it will not consume, so the SQL `UPDATE` shape **fails** there
|
||||
and dispatch falls back to the DSL, whose `Node::Flag` matches
|
||||
`--all-rows`. (Adjacency is what separates the DSL flag from real
|
||||
arithmetic — see the preserved case below.)
|
||||
|
||||
Verified consequences (probed against the current grammar), which the
|
||||
build must lock down test-first:
|
||||
|
||||
- `update T set x = 42 --all-rows` → DSL `Update { AllRows }` *(the goal;
|
||||
inverts `sql_dml_e2e.rs::e2e_update_all_rows_in_advanced_does_not_fall_back_to_dsl`)*.
|
||||
- `update T set x = 42 - -3` → **unchanged**, stays `SqlUpdate` (= 45):
|
||||
the dashes are space-separated, not an adjacent `--`. This invariant is
|
||||
non-negotiable and gets its own test.
|
||||
- `update T set x = 42--3` and `update T set x = 42 --col` *(adjacent
|
||||
`--` before a number or a real column)* → today they parse `Ok` as
|
||||
`SqlUpdate`; after the fix the SQL expression stops at `--` and the DSL
|
||||
flag grammar (which accepts only `all-rows`) rejects the rest, so they
|
||||
become **parse errors**. This is a deliberate, acceptable behaviour
|
||||
change for these contrived adjacent-dash inputs — the playground does
|
||||
not support `--` line comments, so there is no valid reading of an
|
||||
adjacent `--` here. *(User heads-up flagged 2026-05-27.)*
|
||||
|
||||
The existing `delete … --all-rows` fall-back is unaffected. Folded into
|
||||
the ADR-0038 echo effort (the fix that makes `update … --all-rows`
|
||||
echoable).
|
||||
|
||||
## See also
|
||||
|
||||
- ADR-0005 — the ten-type vocabulary INSERT works with.
|
||||
|
||||
@@ -616,6 +616,117 @@ Single-column UNIQUE column drops are a **parallel** gap (different
|
||||
mechanism — ADR-0029 column-level `drop constraint`) and are **out of
|
||||
scope** here.
|
||||
|
||||
## Amendment 2 — Standard-first dialect + `ALTER COLUMN` constraint gap-fill (2026-05-27)
|
||||
|
||||
Designing the **DSL → SQL teaching echo** (ADR-0030 §10, specified in
|
||||
ADR-0038) surfaced two related things about this ADR's surface. First, a
|
||||
**dialect drift**: ADR-0030 frames advanced mode as "the **standard-SQL**
|
||||
surface," but §4f shipped the type-change verb as bare
|
||||
`ALTER COLUMN … TYPE` — the **PostgreSQL shorthand** — and explicitly
|
||||
declined the ISO `SET DATA TYPE` synonym (§4f, line "no `SET DATA TYPE`
|
||||
synonym"). Second, **gaps**: advanced mode has no way to toggle `NOT NULL`
|
||||
or `DEFAULT` on an existing column, though simple mode does
|
||||
(ADR-0029 `add`/`drop constraint`), and the rebuild primitive that would
|
||||
back them is already in place (it backs §4f type-change and §4g
|
||||
constraint-add). This amendment records a **dialect stance** and **fills
|
||||
the clean gaps** so the echo can emit portable SQL that is also runnable
|
||||
in advanced mode. Recorded with explicit user approval (2026-05-27).
|
||||
|
||||
### The dialect stance — standard-first (refines ADR-0030)
|
||||
|
||||
Where ISO SQL provides a spelling, the **authored grammar's canonical
|
||||
form is the ISO one**, and the **echo emits the ISO form**. A widely-
|
||||
recognised vendor shorthand *may* be **accepted** as a synonym (so a
|
||||
learner who knows it is not punished), but it is never the canonical or
|
||||
emitted form. Where ISO provides **no** spelling for an operation the
|
||||
playground teaches, **one** widely-recognised vendor spelling is adopted
|
||||
as a **deliberate, documented extension** — not silently. This realigns
|
||||
the surface with ADR-0030's stated posture and makes the divergence a
|
||||
conscious, recorded choice rather than drift.
|
||||
|
||||
The stance applies to the whole advanced surface going forward; this
|
||||
amendment exercises it on the `ALTER COLUMN` family.
|
||||
|
||||
### Type change: ISO `SET DATA TYPE` canonical, `TYPE` retained as a synonym
|
||||
|
||||
Reverses §4f's "no `SET DATA TYPE` synonym." The grammar now accepts
|
||||
**both** `ALTER COLUMN <c> SET DATA TYPE <type>` (ISO; canonical) and
|
||||
`ALTER COLUMN <c> TYPE <type>` (PostgreSQL; accepted synonym, no
|
||||
breakage for already-shipped usage). Both decompose to the same §4f
|
||||
`AlterColumnType` action and the same `ChangeColumnMode::ForceConversion`
|
||||
executor — semantics are **unchanged**; only the accepted spelling set
|
||||
and the *canonical/echoed* form change. The echo (ADR-0038) emits
|
||||
`SET DATA TYPE`.
|
||||
|
||||
### New: `SET/DROP DEFAULT` (ISO) and `SET/DROP NOT NULL` (the one extension)
|
||||
|
||||
Four new `AlterColumnType`-family actions under `ALTER COLUMN <c>`:
|
||||
|
||||
| Spelling | Standing | Decomposes to (ADR-0029 executor) |
|
||||
|---|---|---|
|
||||
| `SET DEFAULT <expr>` | **ISO-standard** | `do_add_constraint(Default)` |
|
||||
| `DROP DEFAULT` | **ISO-standard** | `do_drop_constraint(Default)` |
|
||||
| `SET NOT NULL` | **documented extension** | `do_add_constraint(NotNull)` |
|
||||
| `DROP NOT NULL` | **documented extension** | `do_drop_constraint(NotNull)` |
|
||||
|
||||
`SET DEFAULT`/`DROP DEFAULT` are taken directly from the ISO
|
||||
`<alter column action>` set. **`NOT NULL` toggling has no ISO spelling**
|
||||
— in the standard `NOT NULL` is a column constraint, not an in-place
|
||||
`ALTER COLUMN` verb, and the vendors diverge (PostgreSQL
|
||||
`SET/DROP NOT NULL`; SQL Server `ALTER COLUMN <c> <type> NOT NULL`;
|
||||
MySQL `MODIFY`; Oracle `MODIFY`). Per the stance, **one** spelling is
|
||||
adopted as a deliberate extension: **PostgreSQL's `SET/DROP NOT NULL`**,
|
||||
chosen because it is the only form that is type-independent (it does not
|
||||
force the user to restate the column type), reads as plain English, and
|
||||
composes uniformly with the ISO `SET/DROP DEFAULT` it sits beside.
|
||||
|
||||
`SET DEFAULT`'s value slot reuses the §4a.2 / §4e **raw `sql_expr`**
|
||||
default mechanism (`default_sql`), so a default may be any expression
|
||||
the create-table `DEFAULT` accepts — one syntax, not a third
|
||||
(ADR-0030 §11).
|
||||
|
||||
### Execution — rebuild-backed, no new low-level op
|
||||
|
||||
Each new action **runtime-decomposes to an existing ADR-0029 executor**
|
||||
(`do_add_constraint` / `do_drop_constraint`), exactly as §4e/§4f
|
||||
decompose their actions — the populated-column **pre-flight dry-run
|
||||
guard** (ADR-0029 §5) and the internal-`__rdbms_*` guard come for free.
|
||||
No new worker layer. The grammar discriminates the `ALTER COLUMN <c> …`
|
||||
tail by its leading keyword: `type` / `set data type` (type change),
|
||||
`set not null` / `drop not null`, `set default` / `drop default` — the
|
||||
`set`/`drop` lead is new alongside §4f's `type` lead.
|
||||
|
||||
### Parity reached, and the one residual gap
|
||||
|
||||
This brings advanced mode to **constraint-modification parity with
|
||||
simple mode (ADR-0029) for `NOT NULL` and `DEFAULT`** — add and drop,
|
||||
both directions. It closes the simple↔advanced asymmetry the echo design
|
||||
flagged for those ops.
|
||||
|
||||
**Residual gap (deliberately not closed here):** dropping a
|
||||
**column-level `UNIQUE` or `CHECK`** (the single-column, *anonymous*
|
||||
constraints simple mode adds via ADR-0029 `add constraint unique/check`).
|
||||
`DROP CONSTRAINT <name>` (§4g) + the derived composite-UNIQUE name
|
||||
(Amendment 1) resolve *table-level* / *named* constraints; a single-
|
||||
column column-level `UNIQUE` lives as the column's `unique` flag and a
|
||||
column-level `CHECK` is likewise anonymous, so neither has a portable
|
||||
name to address. This is the same class Amendment 1 called a "parallel
|
||||
gap … out of scope." Consequently ADR-0038's catalogue marks
|
||||
`drop constraint unique/check from T.col` as **no headline echo** (a
|
||||
residual gap), rather than inventing a name or a recipe. Flagged for the
|
||||
user; closing it (e.g. extending the derived-name approach to single-
|
||||
column UNIQUE) would be its own small follow-up.
|
||||
|
||||
### Engine neutrality holds (the rebuild stays hidden)
|
||||
|
||||
The chosen spellings are **portable SQL**, not engine features. The fact
|
||||
that *this* engine satisfies `SET NOT NULL` / `SET DATA TYPE` via a table
|
||||
rebuild (because it lacks in-place `ALTER`) is a **Category-1 engine
|
||||
implementation detail** (ADR-0038's taxonomy) and stays **invisible** —
|
||||
no recipe, no rebuild steps surfaced — exactly as §9 and ADR-0030 §7
|
||||
require. A learner sees the standard statement; the engine's means of
|
||||
honouring it is not the lesson.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Advanced mode reaches DDL parity with simple mode and adds
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
# ADR-0037: Execution-time mode side-channel (the three-way submission mode)
|
||||
|
||||
## Status
|
||||
|
||||
Proposed (design agreed with the user 2026-05-27; pending `/runda` +
|
||||
implementation). Redeems the follow-up **deferred by ADR-0033
|
||||
Amendment 3**, which named this very ADR and its motivating consumer.
|
||||
|
||||
## Context
|
||||
|
||||
ADR-0033 Amendment 3 ("Execution-time mode is side-channel — deferred
|
||||
to its own ADR") recorded a requirement and deferred it:
|
||||
|
||||
> every command should — at **execution time** — know which of three
|
||||
> modes it ran under: `simple`, `advanced`, or `advanced-one-shot`
|
||||
> (the `:` escape from Simple mode, ADR-0003), so execution can adjust
|
||||
> **output** without changing **identity** (e.g. a Simple-mode
|
||||
> `create table` echoing the generated SQL when run in Advanced mode,
|
||||
> while staying silent in Simple mode).
|
||||
|
||||
That echo is the **DSL → SQL teaching bridge** (ADR-0030 §10), specified
|
||||
as its own Phase-5 ADR (ADR-0038). ADR-0038 is the motivating — and, at
|
||||
time of writing, only — consumer of this side-channel. This ADR
|
||||
establishes the channel; ADR-0038 consumes it.
|
||||
|
||||
### What exists today
|
||||
|
||||
- The persistent input mode `Mode` (`src/mode.rs`) is **two-way**:
|
||||
`Simple` / `Advanced`. Its doc comment is explicit that the one-shot
|
||||
`:` escape "is handled at submission time in `app::App::submit`, **not
|
||||
as additional state here**."
|
||||
- The one-shot `:` is therefore a **transient, per-submission**
|
||||
property, collapsed to an effective mode at submit time. It is not
|
||||
persistent state and was deliberately kept out of `Mode`.
|
||||
- The only mode information that survives past submission is a
|
||||
**rendering** side-channel: `OutputLine.mode_at_submission: Mode`
|
||||
(the `[simple]`/`[advanced]` echo-line tag in `ui.rs`). It is two-way
|
||||
and exists purely to label the input-echo line.
|
||||
- Neither `Action::ExecuteDsl` nor the database worker
|
||||
(ADR-0010) carries any mode. Execution is mode-agnostic.
|
||||
|
||||
### Why the gap blocks the echo
|
||||
|
||||
The teaching echo (ADR-0038) renders the equivalent SQL **beneath the
|
||||
`[ok]` summary**. That summary is built in the App's outcome handler
|
||||
(`App::update`, the `Dsl*Succeeded` arms) from the **worker's result
|
||||
event** — not at dispatch time. At that point:
|
||||
|
||||
- The typed `Command` is available (the success events already carry it).
|
||||
- The submission mode is **not** available. `self.mode` is the *current*
|
||||
persistent mode, which is unreliable for this purpose: it can change
|
||||
between submission and outcome, and for a `:` one-shot it never
|
||||
reflected the effective mode of that single line at all.
|
||||
|
||||
So the echo needs the **effective submission mode delivered to
|
||||
execution/outcome**. And several echo forms (ADR-0038 — auto-resolved
|
||||
index / relationship names, generated `shortid` values, lossy-conversion
|
||||
counts) are only knowable **after the worker has run**, so the echo's
|
||||
data is fundamentally worker-produced. The natural place to gate and
|
||||
build mode-dependent output is therefore the execution path, with the
|
||||
mode threaded to it — exactly the side-channel Amendment 3 anticipated.
|
||||
|
||||
## Decision
|
||||
|
||||
Introduce a **three-way effective submission mode**, computed at submit
|
||||
time and threaded through the `Action` → worker interface, available at
|
||||
**execution time** to adjust **output only**. Command identity, dispatch,
|
||||
and execution semantics are **unchanged** (ADR-0033 Amendment 3: identity
|
||||
is intrinsic to the mode-rooted grammar path, not a flag).
|
||||
|
||||
### 1. A new per-submission enum, distinct from persistent `Mode`
|
||||
|
||||
```rust
|
||||
/// The effective mode of a single submitted line, resolved at submit
|
||||
/// time. Distinct from the persistent input `Mode` (which stays
|
||||
/// two-way) because the one-shot `:` escape is a transient per-line
|
||||
/// property, never persistent state (ADR-0003; `mode.rs`).
|
||||
pub enum SubmissionMode {
|
||||
Simple,
|
||||
Advanced,
|
||||
AdvancedOneShot,
|
||||
}
|
||||
```
|
||||
|
||||
**This refines Amendment 3's framing.** Amendment 3 sketched "widening
|
||||
`Mode` to the three-way distinction." On implementation review, a
|
||||
**separate enum** is cleaner: `Mode` models *persistent input state*,
|
||||
and `mode.rs` deliberately keeps the one-shot out of it. Folding a
|
||||
transient `AdvancedOneShot` into the persistent `Mode` would contradict
|
||||
that design note and let a non-persistable value leak into persistent-
|
||||
mode call sites. `SubmissionMode` carries the three-way distinction on
|
||||
the per-submission channel where it belongs; `Mode` stays two-way and
|
||||
untouched. The *requirement* Amendment 3 recorded is met; only the
|
||||
**shape** is refined. (Flagged for `/runda`/user challenge — it is an
|
||||
internal architecture choice with no user-facing effect.)
|
||||
|
||||
The effective mode is resolved at submit time from the persistent
|
||||
`Mode` plus whether the `:` sigil was used:
|
||||
|
||||
- persistent `Simple`, no `:` → `Simple`
|
||||
- persistent `Simple`, `:` prefix → `AdvancedOneShot`
|
||||
- persistent `Advanced` → `Advanced` (the `:` is a no-op there)
|
||||
|
||||
### 2. Threaded through `Action::ExecuteDsl` to the worker
|
||||
|
||||
`Action::ExecuteDsl` gains a `submission_mode: SubmissionMode` field;
|
||||
the worker request mirrors it. Execution thus knows, per command, the
|
||||
effective mode it ran under. The value is **output-only**: no executor
|
||||
branches its *effect* on it (that would be a behavioural mode dependency,
|
||||
which ADR-0033 Amendment 3 forbids — identity and effect are intrinsic).
|
||||
|
||||
### 3. The worker produces mode-dependent output; the App renders it
|
||||
|
||||
For the first consumer (ADR-0038): when the command is a **DSL-form**
|
||||
command (`Command::CreateTable`/`Insert`/… — *not* the `Sql*` variants)
|
||||
and `submission_mode` is `Advanced` or `AdvancedOneShot`, the worker
|
||||
builds the teaching echo (equivalent SQL + any category-3 expansion
|
||||
data — ADR-0038) and returns it on the result event. In `Simple` mode,
|
||||
or for a command typed as SQL, no echo is produced. The App renders the
|
||||
returned echo as de-emphasised `OutputLine`(s) beneath `[ok]`.
|
||||
|
||||
Co-locating echo construction with execution is deliberate: the echo's
|
||||
harder forms (resolved auto-names, generated `shortid`s, conversion
|
||||
counts) are facts the worker already computes. Gating on the threaded
|
||||
mode means the work happens **only when an echo will be shown**.
|
||||
|
||||
**Non-interactive re-execution does not echo.** `replay` (ADR-0034)
|
||||
re-runs recorded commands through the dispatch pipeline in advanced mode
|
||||
(ADR-0033 Amendment 3) — but a per-line teaching echo there would bury
|
||||
the replay summary in noise. So replayed lines (and any future
|
||||
programmatic re-execution) dispatch with a `SubmissionMode` that
|
||||
**suppresses** mode-dependent output: command *identity* still parses in
|
||||
advanced mode (Amendment 3, unchanged), but no echo fires. The mechanism
|
||||
— a fourth non-echoing context, or an `interactive: bool` alongside the
|
||||
mode — is a build choice; the contract fixed here is *replay is silent*.
|
||||
|
||||
### 4. Scope of this ADR
|
||||
|
||||
This ADR establishes the channel and the resolution rule **only**. The
|
||||
echo renderer, its catalogue, and the `Value → SQL-literal` machinery
|
||||
are ADR-0038. The advanced-mode `ALTER COLUMN` gap-fill the echo relies
|
||||
on is the ADR-0035 amendment. No echo behaviour is specified here beyond
|
||||
the gating contract in §3.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- **Widen `Mode` to three-way (Amendment 3's literal sketch).** Avoids a
|
||||
new type but conflates transient per-submission state with persistent
|
||||
input state, against `mode.rs`'s explicit design note. Rejected for
|
||||
§1's separate enum.
|
||||
- **App-side gating, worker always returns echo data.** Keep the worker
|
||||
mode-agnostic; have it return echo data on *every* result, and let the
|
||||
App decide whether to render from `mode_at_submission` + command shape.
|
||||
Rejected: it computes the echo unconditionally (including in Simple
|
||||
mode, where it is never shown), does not generalise to other mode-
|
||||
dependent output, and re-opens exactly the "the echo is purely render-
|
||||
side" framing the user has twice ruled against — the settled direction
|
||||
is execution-time mode awareness, per Amendment 3.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Additive and small: a new enum, one field on `Action::ExecuteDsl` and
|
||||
the worker request, and the submit-time resolution rule. No change to
|
||||
parsing, dispatch, command identity, or any executor's effect.
|
||||
- The persistent `Mode` enum and `OutputLine.mode_at_submission` are
|
||||
unchanged. (ADR-0038 may enrich the render tag separately; not required
|
||||
here.)
|
||||
- Future mode-dependent **output** has a home. Anything touching command
|
||||
*identity* or *effect* does not belong here (Amendment 3).
|
||||
- Tests: submit-time resolution for all three cases (incl. `:` one-shot
|
||||
and the Advanced-mode `:`-is-a-no-op case); the field survives the
|
||||
`Action` → worker round-trip; a Simple-mode DSL command yields no echo
|
||||
request while an Advanced / one-shot one does (the gating contract).
|
||||
|
||||
## See also
|
||||
|
||||
- ADR-0033 Amendment 3 — deferred this side-channel; defines the
|
||||
intrinsic command-identity model this ADR must not disturb.
|
||||
- ADR-0030 §10 — the DSL → SQL teaching bridge (the motivating consumer).
|
||||
- ADR-0038 — the teaching echo; the consumer built on this channel.
|
||||
- ADR-0003 — input modes and the one-shot `:` escape.
|
||||
- ADR-0010 — the database worker thread this mode is threaded to.
|
||||
@@ -0,0 +1,299 @@
|
||||
# ADR-0038: The DSL → SQL teaching echo
|
||||
|
||||
## Status
|
||||
|
||||
Proposed (design agreed with the user 2026-05-27; pending `/runda` +
|
||||
implementation). **Realises ADR-0030 §10** ("The DSL → SQL teaching
|
||||
bridge") — the Phase-5 echo that **ADR-0035 §12 forward-referenced** as
|
||||
"a separate ADR." Builds on **ADR-0037** (the execution-time mode
|
||||
side-channel that gates it) and **ADR-0035 Amendment 2** (the standard-
|
||||
first dialect + `ALTER COLUMN` gap-fill that makes its DDL echoes
|
||||
runnable).
|
||||
|
||||
## Context
|
||||
|
||||
ADR-0030 §10 specified a teaching bridge: when a **DSL-form** command
|
||||
runs **in advanced mode**, its output includes the equivalent SQL, so a
|
||||
learner who knows the simple-mode form reads off how to spell it in SQL.
|
||||
The §10 contract: fires only for DSL-entered commands in advanced mode
|
||||
(a command already typed as SQL is not echoed; simple mode stays
|
||||
uncluttered); renders as a de-emphasised `OutputLine` beneath the `[ok]`
|
||||
summary (styled-runs, ADR-0028); app-level commands have no SQL form and
|
||||
are not echoed; the reverse SQL → DSL echo is out of scope (§13 OOS-5).
|
||||
|
||||
### The firing reality — this is a DDL + `show data` feature
|
||||
|
||||
A consequence of ADR-0033 Amendment 3 sharpens the scope and must be
|
||||
stated plainly, because it is easy to over-estimate coverage: **in
|
||||
advanced mode the overlapping data commands route SQL-first.** A user who
|
||||
types `insert into T values (…)` / `update T set …` / `delete from T
|
||||
where …` in advanced mode produces `Command::SqlInsert` / `SqlUpdate` /
|
||||
`SqlDelete` — they **already typed SQL**, so §10 explicitly does not echo
|
||||
them. The echo is therefore meaningful only where the **DSL spelling
|
||||
differs from the SQL spelling** — i.e. the DSL-*only* surface:
|
||||
|
||||
- **DDL DSL spellings** with no winning SQL competitor — `create table …
|
||||
with pk`, `add`/`drop`/`rename`/`change column`, `add`/`drop
|
||||
constraint`, `add`/`drop index`, `add`/`drop relationship`.
|
||||
- **`show data`** (DSL-only; SQL uses `SELECT`).
|
||||
- The **`--all-rows` fall-throughs** — `delete … --all-rows` falls back
|
||||
to the DSL `Delete` (the SQL `DELETE` has no slot for the flag), and
|
||||
`update … --all-rows` likewise falls back to the DSL `Update` per
|
||||
**ADR-0033 Amendment 4** (which reclassifies Amendment 3's non-fall-
|
||||
back of `update … --all-rows` as a bug and reverses it). Plain /
|
||||
`where`-filtered `insert`/`update`/`delete` stay SQL-first (`Sql*`).
|
||||
|
||||
Everything a learner types that is *already SQL* needs no echo by
|
||||
construction. So the catalogue below is dominated by DDL.
|
||||
|
||||
### Dependencies
|
||||
|
||||
- **ADR-0037** delivers the `SubmissionMode` to the worker, so a command
|
||||
knows at execution time whether it ran under `Advanced` /
|
||||
`AdvancedOneShot` (echo) or `Simple` (silent).
|
||||
- **ADR-0035 Amendment 2** adds the `ALTER COLUMN SET/DROP NOT NULL`,
|
||||
`SET/DROP DEFAULT`, and the ISO `SET DATA TYPE` canonical form, so the
|
||||
constraint-modification echoes are runnable advanced-mode SQL, and
|
||||
fixes the dialect stance the echoes emit in.
|
||||
|
||||
## Decision
|
||||
|
||||
### 1. The copy-paste contract — every echo is runnable advanced-mode SQL
|
||||
|
||||
The defining invariant, stronger than "looks like SQL": **each echoed
|
||||
line is exactly what the user could paste into advanced mode and run —
|
||||
to the same effect, except where a Category-3 *caveat* line (§6) flags a
|
||||
divergence the SQL surface cannot express** (today the sole such case is
|
||||
`change column … --dont-convert`). This is testable as a **round-trip**:
|
||||
parse the echo through the advanced-mode walker and assert it yields a
|
||||
command with the same effect as the one that produced it (a caveated
|
||||
line still parses and runs — it just runs differently, which the caveat
|
||||
states). A planned one-shot "copy the
|
||||
echo" UX affordance depends on this contract, so it is a hard
|
||||
requirement, not a nicety. Multi-statement echoes (§6 category 2) hold
|
||||
the contract per line.
|
||||
|
||||
### 2. Type vocabulary — the playground's own keywords
|
||||
|
||||
The echo emits the playground's `Type::keyword()` spelling (`serial`,
|
||||
`shortid`, `decimal`, `bool`, `date`, `datetime`, …), **not** engine
|
||||
storage types. This is sound because the advanced-mode type slot
|
||||
(`Type::from_sql_name`) accepts those ten keywords, so the echo round-
|
||||
trips (§1), and because the curated vocabulary is what the learner knows
|
||||
from the DSL and what carries the teaching information (`serial`'s auto-
|
||||
increment intent, `shortid`'s identity flavour) that `INTEGER`/`TEXT`
|
||||
would erase. The **statement shape** follows the standard-first dialect
|
||||
(ADR-0035 Amendment 2); the **type slots** carry playground keywords.
|
||||
|
||||
### 3. Firing rule
|
||||
|
||||
The echo fires **iff** the executed command is a **DSL-form** command —
|
||||
*not* a `Sql*` variant and *not* a `Command::App(_)` — **and** its
|
||||
`SubmissionMode` (ADR-0037) is `Advanced` or `AdvancedOneShot` **and** the
|
||||
command is an interactive submission (**not** a `replay`ed line). Silent
|
||||
in `Simple`; silent for SQL-entered commands (they are `Sql*`); silent
|
||||
for app commands (no SQL form, §10); silent during `replay` (per-line
|
||||
echoes would bury the replay summary — ADR-0037 §3).
|
||||
|
||||
### 4. Where it is built and rendered
|
||||
|
||||
The **worker builds** the echo (ADR-0037 §3) — it alone holds the facts
|
||||
several echoes need: auto-resolved index / relationship names, generated
|
||||
`shortid` values, and lossy-conversion counts. It returns the echo
|
||||
payload on the result event. The **App renders** it as one or more
|
||||
**de-emphasised** `OutputLine`s beneath the `[ok]` summary, using the
|
||||
ADR-0028 styled-runs mechanism (a dimmed `Executing SQL:` prefix; the SQL
|
||||
itself in a code-ish run). One statement per line (§6 category 2).
|
||||
|
||||
### 5. `Value → SQL-literal` rendering
|
||||
|
||||
DML echoes substitute the **actual literal values** (not `?`
|
||||
placeholders — decision B1), so the line is runnable. A per-type renderer
|
||||
produces SQL literals that **round-trip** (§1):
|
||||
|
||||
| Type | Literal form |
|
||||
|---|---|
|
||||
| `text` / `shortid` | single-quoted, `'` doubled (`'O''Hara'`) |
|
||||
| `int` / `serial` | bare integer |
|
||||
| `real` / `decimal` | bare numeric (decimal as authored) |
|
||||
| `bool` | `true` / `false` |
|
||||
| `date` / `datetime` | quoted ISO-8601 (`'2026-05-27'`) |
|
||||
| `NULL` | `NULL` |
|
||||
|
||||
**`blob` has no literal:** neither the DSL nor advanced SQL has a blob-
|
||||
literal syntax (the playground deliberately does not provide one), so a
|
||||
DSL command **cannot carry a blob value** for the echo to render — this
|
||||
is moot, not a gap.
|
||||
|
||||
**Auto-generated columns are omitted** from an `INSERT` echo: `do_insert`
|
||||
filters out PK `serial` (rowid alias), non-PK `serial` (MAX+1), and
|
||||
`shortid` columns the user did not list, and advanced-mode SQL auto-fills
|
||||
the same omissions (`requirements.md` X4). So the echo omits them too —
|
||||
matching both the executed statement and the advanced-mode form a learner
|
||||
would type.
|
||||
|
||||
### 6. The three-category framework
|
||||
|
||||
Everything that happens "beyond the literal SQL line" sorts into exactly
|
||||
three categories; naming them makes the rule testable:
|
||||
|
||||
- **Category 1 — engine-implementation-hiding. Never surfaced.** The
|
||||
table rebuild that backs `ALTER COLUMN …` / `ADD CONSTRAINT` /
|
||||
`RENAME TO` on this engine; the rowid alias behind a PK `serial`; the
|
||||
`MAX(col)+1` behind a non-PK `serial`. These are how *this* engine
|
||||
honours a standard operation — not the lesson (ADR-0030 §7,
|
||||
ADR-0035 §9). The headline echo shows the clean statement; the means
|
||||
stays invisible. (Non-PK `serial` auto-fill is **here, not category 3**:
|
||||
auto-increment is an ordinary database feature; only the engine's
|
||||
*means* of faking it on a non-PK column is hidden.)
|
||||
- **Category 2 — decomposable into advanced-mode SQL. Shown as the
|
||||
runnable multi-line sequence.** One statement per line; the lines
|
||||
*are* the explanation, no prose. See `drop column --cascade` and
|
||||
`add relationship --create-fk` below.
|
||||
- **Category 3 — a playground type-system behaviour the SQL line cannot
|
||||
express (no function, no clause exists to write it). Surfaced as a
|
||||
de-emphasised prose line.** The note is one of two kinds:
|
||||
*illuminating* — the headline echo *is* equivalent and the note merely
|
||||
reveals a value-add the SQL does not show; or *caveat* — the headline
|
||||
is the **nearest** SQL but **not** equivalent because a playground
|
||||
flag's semantics have no SQL form (these caveats are the §1 contract's
|
||||
only exceptions). Members:
|
||||
- **`shortid` generation** *(illuminating)* — no `shortid()` function
|
||||
exists, so the generation cannot be written in SQL: "generated unique
|
||||
shortid(s) for `<col>`".
|
||||
- **type-conversion transform** *(illuminating)* — `change column type`
|
||||
when cells are actually transformed; the playground auto-converts
|
||||
where standard SQL would need a cast clause it deliberately omits
|
||||
(the Postgres `USING`, ADR-0035 §12): "converted `<col>` from
|
||||
`<old>` to `<new>` [N values with loss]".
|
||||
- **`change column … --dont-convert`** *(caveat)* — the headline
|
||||
`ALTER TABLE T ALTER COLUMN c SET DATA TYPE <ty>` converts, but
|
||||
`--dont-convert` left the stored values as-is, so the line is not
|
||||
equivalent: "`--dont-convert` kept the stored values as-is; standard
|
||||
SQL always converts, so running the line above would transform them
|
||||
instead."
|
||||
|
||||
All are built from the worker's existing `client_side.*` result notes
|
||||
(ADR-0017 §6 / ADR-0018 §9), not recomputed.
|
||||
|
||||
### 7. The catalogue
|
||||
|
||||
DSL-form commands that surface in advanced mode, with their echo. The
|
||||
SQL uses the standard-first dialect (ADR-0035 Amendment 2) and playground
|
||||
type keywords (§2). `<name>` denotes a worker-resolved name (auto-
|
||||
generated or positional → resolved at execution, "B1").
|
||||
|
||||
**Bucket A — single runnable statement.**
|
||||
|
||||
| DSL command | Echoed SQL | Cat-3 expansion |
|
||||
|---|---|---|
|
||||
| `create table T with pk` | `CREATE TABLE T (id serial PRIMARY KEY)` | — |
|
||||
| `create table T with pk a(int),b(int)` | `CREATE TABLE T (a int, b int, PRIMARY KEY (a, b))` | — |
|
||||
| `add column to T: c (<ty>) [not null] [unique] [default v] [check e]` | `ALTER TABLE T ADD COLUMN c <ty> [NOT NULL] [UNIQUE] [DEFAULT v] [CHECK (e)]` | shortid (if `<ty>` = shortid) |
|
||||
| `drop column from T: c` *(no covering index)* | `ALTER TABLE T DROP COLUMN c` | — |
|
||||
| `rename column in T: old to new` | `ALTER TABLE T RENAME COLUMN old TO new` | — |
|
||||
| `change column in T: c (<ty>)` | `ALTER TABLE T ALTER COLUMN c SET DATA TYPE <ty>` | conversion *(illum.)* if transformed (incl. → shortid); `--dont-convert` → *caveat* (§6) |
|
||||
| `add constraint not null to T.c` | `ALTER TABLE T ALTER COLUMN c SET NOT NULL` | — |
|
||||
| `add constraint default <v> to T.c` | `ALTER TABLE T ALTER COLUMN c SET DEFAULT <v>` | — |
|
||||
| `add constraint unique to T.c` | `ALTER TABLE T ADD UNIQUE (c)` | — |
|
||||
| `add constraint check <e> to T.c` | `ALTER TABLE T ADD CHECK (e)` | — |
|
||||
| `drop constraint not null from T.c` | `ALTER TABLE T ALTER COLUMN c DROP NOT NULL` | — |
|
||||
| `drop constraint default from T.c` | `ALTER TABLE T ALTER COLUMN c DROP DEFAULT` | — |
|
||||
| `add index [as N] on T (cols)` | `CREATE INDEX <name> ON T (cols)` | — |
|
||||
| `show data T [where …] [limit n]` | `SELECT * FROM T [WHERE …] [ORDER BY <pk> LIMIT n]` | — |
|
||||
| `delete from T --all-rows` | `DELETE FROM T` | — |
|
||||
| `update T set … --all-rows` | `UPDATE T SET …` | — |
|
||||
|
||||
**Bucket B — resolved-name and multi-line (one statement per line).**
|
||||
|
||||
| DSL command | Echoed SQL | Notes |
|
||||
|---|---|---|
|
||||
| `drop index on T(cols)` *(positional)* | `DROP INDEX <name>` | name resolved at execution |
|
||||
| `add 1:n relationship [as N] from P.pc to C.cc [on delete X] [on update Y]` | `ALTER TABLE C ADD CONSTRAINT <name> FOREIGN KEY (cc) REFERENCES P (pc) [ON DELETE X] [ON UPDATE Y]` | name resolved |
|
||||
| `drop relationship (from P.pc to C.cc \| N)` | `ALTER TABLE C DROP CONSTRAINT <name>` | name resolved |
|
||||
| `add 1:n relationship … --create-fk` *(child col created)* | `ALTER TABLE C ADD COLUMN cc <ty>` ⏎ `ALTER TABLE C ADD CONSTRAINT <name> FOREIGN KEY (cc) REFERENCES P (pc) …` | category 2: two runnable lines (one if the column already existed) |
|
||||
| `drop column T.c --cascade` *(drops covering indexes)* | `DROP INDEX <ix1>` ⏎ … ⏎ `ALTER TABLE T DROP COLUMN c` | category 2: index names resolved; plain `DROP COLUMN` would refuse an indexed column |
|
||||
|
||||
**Bucket C — no echo.**
|
||||
|
||||
| DSL command | Reason |
|
||||
|---|---|
|
||||
| `show table T` | structure display; no SQL spelling in the surface |
|
||||
| `explain …` | `EXPLAIN` of advanced SQL is OOS-2 (ADR-0030 §13) |
|
||||
| `replay <path>` | app-lifecycle; no SQL form |
|
||||
| `drop constraint unique/check from T.c` *(column-level)* | residual gap (ADR-0035 Amendment 2): anonymous column constraint, no portable name |
|
||||
| `Command::App(_)` (all app commands) | §10 — no SQL form |
|
||||
| `Sql*` variants, `select`/`with` | already SQL-entered — not echoed by construction |
|
||||
| `insert …`, `update …` / `update … where …`, `delete …` / `delete … where …` | route SQL-first in advanced mode (`Sql*`) — already SQL, nothing to echo (the two `--all-rows` forms fall back to DSL and *do* echo — Bucket A) |
|
||||
|
||||
### 8. Coverage and phasing
|
||||
|
||||
The renderer is one mechanism, so the natural unit is "Bucket A + B at
|
||||
once." A reasonable phasing if a smaller first slice is wanted:
|
||||
|
||||
- **Phase 1** — Bucket A single-statement DDL + `show data` (the bulk of
|
||||
the teaching value; no resolved-name or multi-line machinery).
|
||||
- **Phase 2** — Bucket B (worker-resolved names; multi-line sequences).
|
||||
- **Phase 3** — the category-3 prose expansion.
|
||||
|
||||
(Coverage/phasing is a sequencing choice for the user; the catalogue
|
||||
itself is fixed.)
|
||||
|
||||
**Build-order dependencies (verified against the current grammar).** Two
|
||||
groups of echoes emit SQL the advanced surface does **not yet parse**, so
|
||||
their round-trip tests (§1) are gated on their prerequisites landing
|
||||
first:
|
||||
|
||||
- The **constraint-modification** rows and the `change column` row —
|
||||
`ALTER COLUMN … SET DATA TYPE / SET NOT NULL / DROP NOT NULL /
|
||||
SET DEFAULT / DROP DEFAULT` — require **ADR-0035 Amendment 2**. Today
|
||||
`alter table T alter column c …` accepts only `type <ty>` (probed:
|
||||
`set data type` / `set not null` / `set default` all error with
|
||||
"expected `type`"). So Amendment 2 must build **before** these rows'
|
||||
round-trip tests. (`change column` could echo the `TYPE` synonym in
|
||||
the interim, but the canonical/emitted form is `SET DATA TYPE` per
|
||||
Amendment 2.)
|
||||
- The **`update … --all-rows`** row requires **ADR-0033 Amendment 4**
|
||||
(today it is `SqlUpdate`, not the DSL `Update` the echo needs).
|
||||
|
||||
All other Bucket A/B rows round-trip against the surface as it stands
|
||||
today (probe-confirmed: `create table T (id serial primary key)`,
|
||||
compound PK, `add unique (c)`, `add check (…)`, `create index … on …`,
|
||||
`select * from …` all parse in advanced mode).
|
||||
|
||||
### 9. Out of scope
|
||||
|
||||
- **The reverse SQL → DSL echo** (ADR-0030 §13 OOS-5).
|
||||
- **App commands**, `show table`, `explain`, `replay` (Bucket C).
|
||||
- **A `blob` literal renderer** (§5 — moot; no blob literal exists).
|
||||
- **Closing the column-level UNIQUE/CHECK drop gap** (ADR-0035
|
||||
Amendment 2 residual) — those stay Bucket C until that gap is closed.
|
||||
- **Surfacing category-1 engine internals** (the rebuild, rowid, MAX+1) —
|
||||
never (§6).
|
||||
|
||||
## Consequences
|
||||
|
||||
- A new `Command → SQL` renderer plus the `Value → SQL-literal` renderer,
|
||||
worker-side (§4), gated by `SubmissionMode` (ADR-0037). Each Bucket A/B
|
||||
row is a round-trip test (§1); each category-3 row asserts the prose
|
||||
fires from the worker note.
|
||||
- The teaching surface is honest: a learner reads runnable, portable SQL
|
||||
in the playground's own type vocabulary; engine quirks stay hidden;
|
||||
playground-only conveniences are named, not mysterious.
|
||||
- The echo's coverage is **DDL-centric by construction** (the firing
|
||||
reality, Context). This is correct, not a shortfall — overlapping DML
|
||||
is already SQL in advanced mode.
|
||||
- Adding a new DSL-form DDL command later must add its catalogue row +
|
||||
round-trip test, or consciously place it in Bucket C.
|
||||
|
||||
## See also
|
||||
|
||||
- ADR-0030 §10 — the teaching bridge this realises; §13 OOS-5 (no reverse
|
||||
echo).
|
||||
- ADR-0037 — the `SubmissionMode` side-channel that gates firing.
|
||||
- ADR-0035 Amendment 2 — the standard-first dialect + `ALTER COLUMN`
|
||||
gap-fill the echoes emit and rely on.
|
||||
- ADR-0033 Amendment 3 — the SQL-first dispatch that makes this a
|
||||
DDL + `show data` feature.
|
||||
- ADR-0028 — the `OutputLine` styled-runs the echo renders through.
|
||||
- ADR-0017 / ADR-0018 — the `client_side.*` notes feeding category-3.
|
||||
@@ -0,0 +1,73 @@
|
||||
# ADR-0039: EXPLAIN over advanced-mode SQL queries
|
||||
|
||||
## 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.**
|
||||
|
||||
## 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.
|
||||
|
||||
## 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.
|
||||
+6
-3
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user