feat: bring simple-mode insert arity diagnostics to parity with advanced
A wrong-count simple-mode insert now shows the friendly per-column arity message at typing time (instead of a bare "expected `,`/`)`") and is blocked from dispatch at submit — unifying simple and advanced mode onto the one ADR-0027 model (structural parse + ERROR diagnostic), where they had diverged. Grammar: a simple-mode-only arity gate (dsl_insert_value_list) routes a wrong-count DSL insert tuple to the type-blind fallback so it matches structurally and the per-tuple arity diagnostic fires. The gate is gated to simple mode, so advanced behaviour is unchanged. count_tuple_values and the target-column selection (insert_target_columns) are now shared by both grammars. Diagnostic: dml_insert_arity_diagnostics is mode-aware — advanced Form B expects all columns; simple Form B/C expects the user-fillable columns (serial/shortid auto-fill). It counts the DSL Form A role and scans the keyword-less Form C tuple. New catalog keys name the fillable/auto split and the all-auto-table case. Submit: a wrong-count DSL insert now parses Ok + carries the ERROR diagnostic, so a unified Ok-arm pre-flight (dsl_insert_count_mismatch_notes) blocks dispatch and teaches; the previous Err-arm note retires. advanced_alternative_note's gate now reads the validity verdict so it still fires for the parse-Ok-with-error shape. Docs: ADR-0036 Amendment 2 (+ README index) and requirements.md H1a.
This commit is contained in:
+55
-12
@@ -27,7 +27,10 @@
|
||||
use crate::dsl::command::{Command, Expr, RowFilter};
|
||||
use crate::dsl::grammar::{
|
||||
CommandNode, IdentSource, Node, NumberValidator, ValidationError, Word, expr,
|
||||
shared::{column_value_list, current_column_value},
|
||||
shared::{
|
||||
FALLBACK_VALUE_LIST, column_value_list, count_tuple_values,
|
||||
current_column_value, insert_target_columns,
|
||||
},
|
||||
sql_delete, sql_insert, sql_select, sql_update,
|
||||
};
|
||||
use crate::dsl::walker::context::WalkContext;
|
||||
@@ -141,12 +144,13 @@ static INSERT_COMMA: Node = Node::Punct(',');
|
||||
/// First-paren resolver (ADR-0024 §Phase D Form-C type-awareness).
|
||||
/// Peeks the first token after `(` to route to Form A's
|
||||
/// column-name list or Form C's typed value list.
|
||||
fn insert_first_paren(_ctx: &WalkContext, source: &str, pos: usize) -> Node {
|
||||
fn insert_first_paren(ctx: &WalkContext, source: &str, pos: usize) -> Node {
|
||||
if first_paren_item_is_value_literal(source, pos) {
|
||||
// Form C — bare value list. `column_value_list` with no
|
||||
// user-listed columns dispatches per non-auto-generated
|
||||
// column, exactly as Form B does.
|
||||
Node::DynamicSubgrammar(column_value_list)
|
||||
// Form C — bare value list. Arity-gated exactly like Form B's
|
||||
// `values (…)`: a correct-count tuple gets the typed per-column
|
||||
// slots; a wrong-count tuple routes to the type-blind fallback
|
||||
// so it still matches and the arity diagnostic fires (issue #17).
|
||||
dsl_insert_value_list(ctx, source, pos)
|
||||
} else {
|
||||
// Form A (or Form A in progress / empty paren).
|
||||
Node::Repeated {
|
||||
@@ -189,12 +193,51 @@ fn first_paren_item_is_value_literal(source: &str, pos: usize) -> bool {
|
||||
|
||||
const INSERT_PAREN_LIST: Node = Node::Lookahead(insert_first_paren);
|
||||
|
||||
/// Schema-aware value list: when the walker has a populated
|
||||
/// `current_table_columns`, unfolds to a `Seq` of typed slots
|
||||
/// per column (`int_slot`, `text_slot`, …). When schemaless,
|
||||
/// falls back to the pre-Phase-D `Repeated(VALUE_LITERAL, ',', 1)`
|
||||
/// shape (ADR-0024 §Phase D §column_value_list).
|
||||
const INSERT_VALUES_LIST: Node = Node::DynamicSubgrammar(column_value_list);
|
||||
/// Insert value-list arity gate (issue #17) — the simple-mode DSL
|
||||
/// counterpart of the advanced grammar's `tuple_value_list`
|
||||
/// (`sql_insert.rs`). Routes a correct-arity tuple to the typed
|
||||
/// per-column slots ([`column_value_list`]) and a wrong-arity tuple to
|
||||
/// the type-blind [`FALLBACK_VALUE_LIST`], so the wrong-count tuple
|
||||
/// still structurally matches and the per-tuple arity diagnostic
|
||||
/// (ADR-0033 §8.1, made mode-aware for issue #17) fires its friendly
|
||||
/// message instead of a bare "expected `,`/`)`".
|
||||
///
|
||||
/// Target arity comes from [`insert_target_columns`] — the same source
|
||||
/// `column_value_list` uses, so gate and slots never disagree. `None`
|
||||
/// (schemaless / unknown table / all-auto-generated) → fallback: either
|
||||
/// we can't gate (schemaless) or the all-auto case wants the tuple to
|
||||
/// match so the diagnostic can explain it.
|
||||
///
|
||||
/// **Simple-mode only.** The fallback routing is what lets a wrong-count
|
||||
/// tuple structurally match (so the diagnostic fires); that is a
|
||||
/// simple-mode behaviour. In advanced mode the DSL insert node must stay
|
||||
/// strict — otherwise a non-SQL shape like Form C (`insert into T
|
||||
/// (1, 2)`, no `values`) would spuriously match here and be accepted in
|
||||
/// advanced mode, where SQL requires `values` and the dedicated SQL
|
||||
/// grammar (`sql_insert.rs`) owns inserts. Keeping advanced strict
|
||||
/// preserves the pre-#17 advanced behaviour exactly (issue #17).
|
||||
fn dsl_insert_value_list(ctx: &WalkContext, source: &str, pos: usize) -> Node {
|
||||
if ctx.mode != crate::mode::Mode::Simple {
|
||||
return Node::DynamicSubgrammar(column_value_list);
|
||||
}
|
||||
let Some(cols) = insert_target_columns(ctx) else {
|
||||
return FALLBACK_VALUE_LIST;
|
||||
};
|
||||
let (count, closed) = count_tuple_values(source, pos);
|
||||
let arity_ok = if closed { count == cols.len() } else { count <= cols.len() };
|
||||
if arity_ok {
|
||||
Node::DynamicSubgrammar(column_value_list)
|
||||
} else {
|
||||
FALLBACK_VALUE_LIST
|
||||
}
|
||||
}
|
||||
|
||||
/// Schema-aware value list, arity-gated (issue #17): a correct-count
|
||||
/// tuple unfolds to a `Seq` of typed slots per column (`int_slot`,
|
||||
/// `text_slot`, …); a wrong-count tuple or a schemaless walk falls back
|
||||
/// to the type-blind `Repeated(VALUE_LITERAL, ',', 1)` shape (ADR-0024
|
||||
/// §Phase D §column_value_list).
|
||||
const INSERT_VALUES_LIST: Node = Node::Lookahead(dsl_insert_value_list);
|
||||
|
||||
const INSERT_OPTIONAL_VALUES_NODES: &[Node] = &[
|
||||
Node::Word(Word::keyword("values")),
|
||||
|
||||
Reference in New Issue
Block a user