fix: insert VALUES between-values hint points at comma not close-paren
The ambient-hint fallback in ambient_hint_core_in_mode parsed schemalessly, so the type-blind grammar closed an `insert … values (…)` tuple after the first value and the "Next:" hint pointed at `)`. With a schema available the walk knows the remaining columns and the correct next token is `,`. Parse the fallback with the schema cache so the expected-token prose matches the rest of the (already schema-aware) hint ladder. Also corrects wrong-arity closed tuples where the schemaless parse accepted the input and the hint said "submit with Enter" for a command the schema-aware parse rejects — the hint now surfaces the accurate error. Three typing-surface snapshots updated to match. Docs: ADR-0022 Amendment 3 (+ README index) records the schema-aware fallback; requirements.md H1a cites the hint-accuracy improvement.
This commit is contained in:
+107
-2
@@ -24,7 +24,7 @@
|
||||
|
||||
use ratatui::style::{Color, Modifier, Style};
|
||||
|
||||
use crate::dsl::parser::{parse_command_in_mode, parse_command_with_schema, parse_command_with_schema_in_mode};
|
||||
use crate::dsl::parser::{parse_command_with_schema, parse_command_with_schema_in_mode};
|
||||
use crate::mode::Mode;
|
||||
use crate::dsl::walker;
|
||||
use crate::dsl::{ParseError, parse_command};
|
||||
@@ -748,7 +748,15 @@ fn ambient_hint_core_in_mode(
|
||||
// parsed in the active `mode` (ADR-0022 Amendment 1). In
|
||||
// simple mode a SQL form still surfaces the "this is SQL"
|
||||
// hint (ADR-0030 §2); in advanced mode it parses as SQL.
|
||||
match parse_command_in_mode(input, mode) {
|
||||
//
|
||||
// Issue #2: parse *with the schema* so the expected-token prose
|
||||
// reflects the schema-aware grammar. Between two values of an
|
||||
// `insert … values (…)` tuple, the type-blind (schemaless) grammar
|
||||
// closes the tuple after one value and points at `)`; the
|
||||
// schema-aware walk knows the remaining columns and correctly
|
||||
// points at `,`. The other hint paths above already use the cache,
|
||||
// so this keeps the whole ladder schema-consistent.
|
||||
match parse_command_with_schema_in_mode(input, cache, mode) {
|
||||
Ok(_) => Some(AmbientHint::Prose(crate::t!("hint.ambient_complete"))),
|
||||
Err(ParseError::Empty) => None,
|
||||
Err(ParseError::Invalid {
|
||||
@@ -1785,6 +1793,103 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ambient_hint_between_values_points_to_comma_not_close_paren() {
|
||||
// Issue #2: at the cursor just after a completed first value
|
||||
// (no comma yet) the between-values fallback hint must reflect
|
||||
// the SCHEMA-AWARE grammar — there is a second column still to
|
||||
// fill, so the meaningful next token is `,`, not `)`. The bug
|
||||
// was that this fallback parsed schemalessly, so the type-blind
|
||||
// grammar closed the tuple after one value and pointed at `)`.
|
||||
// Not literal-kind-specific: each case below uses a value that
|
||||
// is type-correct for the first column, so the only difference
|
||||
// from the report's string case is the literal shape.
|
||||
use crate::dsl::types::Type;
|
||||
let cases: &[(&[(&str, Type)], &str)] = &[
|
||||
// string first value (the report's case): first col text.
|
||||
(&[("Name", Type::Text), ("Age", Type::Int)],
|
||||
"insert into Customers values ('Oli'"),
|
||||
// integer first value: first col int.
|
||||
(&[("Age", Type::Int), ("Name", Type::Text)],
|
||||
"insert into Customers values (42"),
|
||||
// real first value: first col real.
|
||||
(&[("Score", Type::Real), ("Name", Type::Text)],
|
||||
"insert into Customers values (3.5"),
|
||||
];
|
||||
for (cols, input) in cases {
|
||||
let cache = schema_with_columns("Customers", cols);
|
||||
match ambient_hint(input, input.len(), None, &cache) {
|
||||
Some(AmbientHint::Prose(p)) => {
|
||||
assert!(
|
||||
p.contains(','),
|
||||
"expected a comma next-token hint for {input:?}, got: {p:?}",
|
||||
);
|
||||
assert!(
|
||||
!p.contains(')'),
|
||||
"must not point at the closing paren mid-tuple for \
|
||||
{input:?}, got: {p:?}",
|
||||
);
|
||||
}
|
||||
other => panic!("expected Prose for {input:?}, got {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn advanced_mode_wrong_arity_insert_keeps_friendly_diagnostic_over_fallback() {
|
||||
// Issue #2 no-masking guard. In advanced mode a wrong-arity
|
||||
// insert tuple structurally matches via the type-blind path and
|
||||
// the walker emits the friendly `insert_arity_mismatch_form_b`
|
||||
// diagnostic (ADR-0033 §8.1 / Amendment 5). That diagnostic is
|
||||
// checked EARLY in the hint ladder, so the issue-#2 schema-aware
|
||||
// fallback (which would otherwise say "expected `)`") must NOT
|
||||
// shadow it. Locks ladder ordering so the fix can't regress the
|
||||
// richer message.
|
||||
use crate::dsl::types::Type;
|
||||
let cache = schema_with_columns(
|
||||
"Customers",
|
||||
&[
|
||||
("id", Type::Serial),
|
||||
("Name", Type::Text),
|
||||
("Age", Type::Int),
|
||||
("SerNo", Type::Serial),
|
||||
],
|
||||
);
|
||||
let input = "insert into Customers values ('Oli', 52, 3)";
|
||||
match ambient_hint_in_mode(input, input.len(), None, &cache, Mode::Advanced) {
|
||||
Some(AmbientHint::Prose(p)) => {
|
||||
assert!(
|
||||
p.contains("value(s) are given"),
|
||||
"expected the friendly arity diagnostic, got: {p:?}",
|
||||
);
|
||||
}
|
||||
other => panic!("expected Prose, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ambient_hint_after_last_value_points_to_close_paren() {
|
||||
// Counterpart to the issue #2 fix: once every column has a
|
||||
// value, the schema-aware fallback SHOULD point at `)` — there
|
||||
// is nothing left to fill. Guards against over-correcting the
|
||||
// fix into never suggesting the close paren.
|
||||
use crate::dsl::types::Type;
|
||||
let cache = schema_with_columns(
|
||||
"Customers",
|
||||
&[("Name", Type::Text), ("Age", Type::Int)],
|
||||
);
|
||||
let input = "insert into Customers values ('Oli', 52";
|
||||
match ambient_hint(input, input.len(), None, &cache) {
|
||||
Some(AmbientHint::Prose(p)) => {
|
||||
assert!(
|
||||
p.contains(')'),
|
||||
"expected a close-paren next-token hint, got: {p:?}",
|
||||
);
|
||||
}
|
||||
other => panic!("expected Prose, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ambient_hint_at_value_slot_falls_back_to_generic_without_schema() {
|
||||
// Empty cache: the walker can't resolve the column type
|
||||
|
||||
Reference in New Issue
Block a user