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:
@@ -14,7 +14,7 @@
|
||||
//! sub-phases.
|
||||
|
||||
use crate::completion::TableColumn;
|
||||
use crate::dsl::grammar::shared::SET_VALUE;
|
||||
use crate::dsl::grammar::shared::{SET_VALUE, count_tuple_values};
|
||||
use crate::dsl::grammar::sql_expr;
|
||||
use crate::dsl::grammar::sql_select::{
|
||||
RETURNING_CLAUSE, SQL_SELECT_COMPOUND, WHERE_CLAUSE, reject_internal_table,
|
||||
@@ -127,57 +127,9 @@ fn target_value_columns(ctx: &WalkContext) -> Vec<TableColumn> {
|
||||
)
|
||||
}
|
||||
|
||||
/// Count the value positions in the `VALUES` tuple whose contents begin
|
||||
/// at `pos` (just past the opening `(`), and whether the tuple is
|
||||
/// *closed* (a depth-0 `)` was reached) vs still being typed (scan hit
|
||||
/// end-of-input first). Depth-aware: commas nested in a function call /
|
||||
/// subquery (paren depth ≥ 1) or inside a string literal are not
|
||||
/// separators. Returns `(0, _)` for an empty tuple `()`.
|
||||
fn count_tuple_values(source: &str, pos: usize) -> (usize, bool) {
|
||||
let bytes = source.as_bytes();
|
||||
let mut i = pos;
|
||||
let mut depth: i32 = 0;
|
||||
let mut commas = 0usize;
|
||||
let mut seen_value = false;
|
||||
let mut closed = false;
|
||||
while i < bytes.len() {
|
||||
match bytes[i] {
|
||||
b'\'' => {
|
||||
// Skip a single-quoted string literal (`''` escape).
|
||||
i += 1;
|
||||
seen_value = true;
|
||||
while i < bytes.len() {
|
||||
if bytes[i] == b'\'' {
|
||||
if bytes.get(i + 1) == Some(&b'\'') {
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
i += 1;
|
||||
break;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
b'(' => {
|
||||
depth += 1;
|
||||
seen_value = true;
|
||||
}
|
||||
b')' => {
|
||||
if depth == 0 {
|
||||
closed = true;
|
||||
break; // tuple close
|
||||
}
|
||||
depth -= 1;
|
||||
}
|
||||
b',' if depth == 0 => commas += 1,
|
||||
b if !b.is_ascii_whitespace() => seen_value = true,
|
||||
_ => {}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
(if seen_value { commas + 1 } else { 0 }, closed)
|
||||
}
|
||||
// `count_tuple_values` moved to `grammar::shared` (issue #17) so the
|
||||
// simple-mode DSL insert arity gate can share it; the advanced grammar
|
||||
// imports it above.
|
||||
|
||||
/// Tuple value-list lookahead (ADR-0036 Phase 3b). Gates the typed
|
||||
/// per-column path on arity so the typed `Seq` is used only where it
|
||||
|
||||
Reference in New Issue
Block a user