fix: INSERT Form B value-count UX (ADR-0033 Amendment 5)
Three layered fixes for advanced/simple-mode positional INSERT
value-count mismatches (e.g. `insert into T values (...)` with the
wrong number of values for T's column count), plus ADR-0033
Amendment 5 recording the gate refinement.
Walker diagnostic (dml_insert_arity_diagnostics): the function's own
doc-comment recorded the no-column-list (Form B) case as deferred.
This commit closes that gap. Form B mismatches now emit a new
diagnostic.insert_arity_mismatch_form_b ERROR per offending tuple,
keyed off the target table's column count from the schema cache. The
[ERR] validity indicator (ADR-0027) lights up at typing time for the
reported scenario, no longer needing a submit.
Cross-mode pointer gate (advanced_alternative_note): refactored from
a hand-rolled Form B count check to a single input_verdict_in_mode(
input, schema, Mode::Advanced) call. The pointer fires only when the
verdict is None — the ADR-0027 sense of "valid". Any future static
check added to the verdict pipeline participates automatically; no
per-feature maintenance.
Teaching notes for the value-count cases users can hit before the
indicator turns red:
- simple-mode submit: insert.form_b_extra_values_note covers under-,
in-window, and over-supply against the Form B contract; suppressed
when the cross-mode pointer fires (to avoid parallel advice).
- advanced-mode dispatch pre-flight:
insert.form_b_positional_count_mismatch_note catches a submitted
mismatch with a teaching message before the engine produces its
raw NOT-NULL / type error.
The advanced_mode.also_valid_sql pointer wording was reworked to
"trying to write SQL? switch with `mode advanced`, or prefix `:` to
run once". One insta snapshot regenerated.
ADR-0033 Amendment 5 records the gate change: Amendment 3's "would
parse in advanced mode" now reads as "valid in advanced mode" in the
explicit verdict-is-None sense, with the precise definition spelled
out so future readers can't drift back to the syntactic-only reading.
ADR-0000 index entry updated; docs/requirements.md H1a citation added
listing the three new pedagogical strings.
Tests added (8): four walker arity tests (under-supply, over-supply,
match, unknown-table); two app-level teaching-note tests for the
sibling cases (under-supply, over-supply beyond total); one pointer-
gate unit test pinning the bug-case suppression; one gate-precedence
test ensuring only one advice line per error. Existing
simple_mode_submit_of_sql_construct_appends_advanced_pointer updated
to use a known schema (the new validity gate requires it).
Full suite: 2031 passed, 0 failed, 0 unexpected skips. Clippy clean.
This commit is contained in:
@@ -1553,6 +1553,100 @@ The existing `delete … --all-rows` fall-back is unaffected. Folded into
|
||||
the ADR-0038 echo effort (the fix that makes `update … --all-rows`
|
||||
echoable).
|
||||
|
||||
## Amendment 5 — `also_valid_sql` fires on *validity*, not just *parse* (2026-05-28)
|
||||
|
||||
Amendment 3 introduced the `advanced_mode.also_valid_sql` pointer and
|
||||
described its gate as *"the same line would parse in advanced mode."*
|
||||
Issue #1 surfaced that this is too weak: an advanced-mode positional
|
||||
`INSERT INTO T VALUES (…)` with a value count that doesn't match the
|
||||
target table's column count **parses** (the SQL grammar accepts any
|
||||
positional value count) but then fails at the engine. The pointer
|
||||
fired, telling the user to switch modes, and the same line failed
|
||||
again — the suggestion was misleading. Recorded with explicit user
|
||||
approval (2026-05-28).
|
||||
|
||||
### Why "parse" was the wrong bar
|
||||
|
||||
"Would parse" is a purely syntactic check: the structural grammar
|
||||
accepts the input. It says nothing about whether subsequent static
|
||||
checks (type slots, INSERT arity, predicate warnings, schema-existence
|
||||
diagnostics, …) would also pass. The cross-mode pointer's job is to
|
||||
direct the user to the mode that **actually** runs the line; a
|
||||
syntactic-only gate over-promises in every case where a non-grammar
|
||||
static check would still reject. The Form B positional arity case is
|
||||
just the first one observed.
|
||||
|
||||
### "Valid" — the explicit definition used by this amendment
|
||||
|
||||
The pointer fires only when the line is **valid** in advanced mode,
|
||||
where **valid** is the ADR-0027 sense — operationally, the value
|
||||
returned by `crate::dsl::walker::input_verdict_in_mode(input, schema,
|
||||
Mode::Advanced)` is `None`:
|
||||
|
||||
- The structural parse succeeds (`WalkOutcome::Match`), **and**
|
||||
- No diagnostic of `Severity::Warning` or `Severity::Error` is
|
||||
produced by any pass that contributes to `input_verdict_in_mode`
|
||||
(schema-existence, SQL-predicate warnings, compound-arity errors,
|
||||
INSERT-arity errors — Form A *and* Form B — and any further
|
||||
diagnostic added to the pipeline in the future).
|
||||
|
||||
This is the same condition the in-app `[ERR]` / `[WRN]` validity
|
||||
indicator uses (ADR-0027 §2). Tying the pointer to it gives one
|
||||
canonical "is this line OK in this mode?" answer that every surface
|
||||
shares — by construction, the indicator and the pointer can never
|
||||
disagree about a given line.
|
||||
|
||||
### Decision
|
||||
|
||||
- `advanced_alternative_note` (`src/input_render.rs`) reads
|
||||
`input_verdict_in_mode(…, Mode::Advanced)`. The pointer fires only
|
||||
for verdict `None`.
|
||||
- Any static diagnostic that participates in the verdict automatically
|
||||
participates in the gate. No bespoke pre-flight check lives in the
|
||||
pointer helper.
|
||||
- The Amendment 3 description (*"would parse in advanced mode"*)
|
||||
should be read as a synonym for **valid** in this amendment's
|
||||
sense, not as a literal syntactic-only test.
|
||||
|
||||
### Diagnostic added to close the issue #1 gap
|
||||
|
||||
ADR-0033 §8.1's existing `dml_insert_arity_diagnostics` checked only
|
||||
Form A (column list present). Its own doc-comment recorded the Form B
|
||||
case as deferred. This amendment closes that gap: the same function
|
||||
now also checks the Form B case (no column list) by comparing each
|
||||
VALUES tuple's arity against the target table's full column count from
|
||||
the schema cache, emitting a new diagnostic key
|
||||
`diagnostic.insert_arity_mismatch_form_b`. The `[ERR]` validity
|
||||
indicator now lights up at typing time for the user-reported scenario,
|
||||
and the cross-mode pointer gate sees the verdict and stays silent.
|
||||
|
||||
### Tests
|
||||
|
||||
The amendment's behavioural promise is locked down by:
|
||||
|
||||
- `dsl::walker::tests::insert_form_b_arity_mismatch_under_supply_fires` —
|
||||
the user's reported 4-col / 3-value case produces an Error diagnostic.
|
||||
- `dsl::walker::tests::insert_form_b_arity_mismatch_over_supply_fires` —
|
||||
symmetric case, 5 values for a 4-col table.
|
||||
- `dsl::walker::tests::insert_form_b_arity_match_is_silent` — happy path
|
||||
(every column supplied, autos included) stays silent.
|
||||
- `dsl::walker::tests::insert_form_b_arity_unknown_table_is_silent` —
|
||||
defensive: an unknown table defers to the schema-existence pass.
|
||||
- `input_render::tests::ambient_hint_omits_advanced_pointer_when_form_b_value_count_wouldnt_match` —
|
||||
the pointer is suppressed for the bug case via the validity verdict.
|
||||
- `app::tests::simple_mode_submit_of_sql_construct_appends_advanced_pointer` —
|
||||
multi-row VALUES (a true SQL-only construct) still fires the pointer
|
||||
when the table exists in the schema.
|
||||
|
||||
### Behaviour preserved
|
||||
|
||||
The pointer still fires for every input that is genuinely SQL-only
|
||||
syntax in simple mode and is valid as advanced SQL — `delete …
|
||||
returning *`, multi-row VALUES against a known table, `INSERT … ON
|
||||
CONFLICT …` over a known target, and so on. The narrowing is
|
||||
**exactly** the set of inputs where switching modes wouldn't help, no
|
||||
more.
|
||||
|
||||
## See also
|
||||
|
||||
- ADR-0005 — the ten-type vocabulary INSERT works with.
|
||||
|
||||
Reference in New Issue
Block a user