grammar+walker: 3j — shared insert/update/delete entry words (ADR-0033 §2 / Amendments 1 & 3)
Wire `insert`/`update`/`delete` as shared DSL/SQL entry words through the
category-grouped dispatcher (ADR-0033 Amendment 1): the Advanced SQL nodes
move off the dev words (`sqlinsert`/`sql_update`/`sql_delete`) to the real
keywords, registered alongside the Simple DSL nodes. Remove the dev-word
scaffold; collapse build_sql_{insert,update,delete} to source.trim();
de-duplicate the two REGISTRY entry-word listing sites.
Dispatch model (ADR-0033 Amendment 3, written this round):
- A command is the mode-rooted grammar-path outcome; identity is intrinsic.
Advanced mode tries SQL first, falling back to the Simple DSL command when
no SQL branch matches a token (`delete … --all-rows` falls back;
`update … --all-rows` does not — the SET expression absorbs it, harmless
since the engine treats `--all-rows` as a comment).
- Simple mode commits the DSL candidate for a shared word, surfacing the real
DSL error; bare "this is SQL" is reserved for SQL-only entry words
(`select`/`with`). A content rejection on the SQL candidate (internal
table) is committed, never masked by the DSL fallback.
Combined DSL-error + advanced-SQL pointer (ADR-0033 Amendment 3): a Simple-mode
definite DSL error that would run as SQL in advanced mode gains the
`advanced_mode.also_valid_sql` suffix — in the live hint (ambient_hint_in_mode)
and on submit (dispatch_dsl), via the shared advanced_alternative_note — so the
actionable DSL fix and the mode pointer coexist (submit covers constructs that
surface only on submit, e.g. `delete … returning`).
Internal-table rejection symmetrised (/runda finding B, ADR-0030 §6): the DSL
data-command target slots (insert/update/delete/show data/show table) gained
reject_internal_table, so `__rdbms_*` tables are refused in Simple mode too —
previously only the advanced SQL grammar rejected them.
Mode-awareness: classify_input_with_schema_in_mode and
invalid_ident_at_cursor_in_mode stop leaking the advanced SQL view into
simple-mode hints for shared words.
Tests: dev-word inputs migrated to the real words (advanced); DSL grammar /
completion / phase-D / db tests parse in Simple mode (the DSL surface); replay
keeps its advanced-mode model (one stale assertion fixed); dispatcher routing,
combined-pointer, and internal-table tests added. Suite 1626 pass / 0 fail /
1 ignored; clippy --all-targets -D warnings clean.
Defer M4 (execution-time mode side-channel; tracked in requirements.md) to its
own ADR.
This commit is contained in:
+52
@@ -1187,6 +1187,20 @@ impl App {
|
||||
"parse.error",
|
||||
detail = parse_error_message(&err)
|
||||
));
|
||||
// ADR-0033 Amendment 3: combine the DSL error with a
|
||||
// pointer to advanced mode when the same line would
|
||||
// run as SQL there. Only in simple mode (a one-shot
|
||||
// `:` or persistent advanced submission uses the SQL
|
||||
// surface already). This mirrors the live hint and
|
||||
// covers SQL constructs that surface only on submit
|
||||
// (e.g. `delete … returning`, where the live hint
|
||||
// shows WHERE-completion rather than an error).
|
||||
if submission_mode == Mode::Simple
|
||||
&& let Some(note) =
|
||||
crate::input_render::advanced_alternative_note(input, &self.schema_cache)
|
||||
{
|
||||
self.note_error(note);
|
||||
}
|
||||
// ADR-0021 §2: append the usage block (if a
|
||||
// known command-entry keyword was consumed) or
|
||||
// the available-commands fallback (§5).
|
||||
@@ -2326,6 +2340,44 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_mode_submit_of_sql_construct_appends_advanced_pointer() {
|
||||
// ADR-0033 Amendment 3: submitting a line in simple mode that
|
||||
// fails as DSL but would run as SQL in advanced mode appends
|
||||
// the `advanced_mode.also_valid_sql` pointer to the parse
|
||||
// error — keeping the DSL detail and pointing at advanced
|
||||
// mode. Multi-row VALUES is a definite DSL error and valid SQL
|
||||
// (no schema needed).
|
||||
let mut app = App::new();
|
||||
type_str(&mut app, "insert into T values (1, 2), (3, 4)");
|
||||
let actions = submit(&mut app);
|
||||
assert!(actions.is_empty(), "the bad line must not dispatch");
|
||||
let has_pointer = app
|
||||
.output
|
||||
.iter()
|
||||
.any(|l| l.kind == OutputKind::Error && l.text.contains("advanced mode"));
|
||||
assert!(
|
||||
has_pointer,
|
||||
"expected the advanced-mode pointer on submit; output:\n{}",
|
||||
error_lines(&app),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_mode_submit_of_pure_dsl_error_has_no_advanced_pointer() {
|
||||
// A DSL error that is *not* valid SQL either (unknown command)
|
||||
// must not carry the advanced-mode pointer — there is nothing
|
||||
// to switch modes for.
|
||||
let mut app = App::new();
|
||||
type_str(&mut app, "frobulate widgets");
|
||||
let _ = submit(&mut app);
|
||||
let has_pointer = app
|
||||
.output
|
||||
.iter()
|
||||
.any(|l| l.text.contains("valid as SQL in advanced mode"));
|
||||
assert!(!has_pointer, "unknown command must not point at advanced mode");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enter_in_advanced_mode_dispatches_select_with_advanced_tag() {
|
||||
// The pre-ADR-0030 placeholder echoed any advanced-mode
|
||||
|
||||
Reference in New Issue
Block a user