feat: ADR-0036 Phase 3a — live typed-slot hints + highlighting for SQL SET values
Wire the DSL's column-typed value slots into the advanced-mode SQL
UPDATE/UPSERT `SET col = <rhs>` value position so a learner gets the same
per-column hint ("for `Email`: type a quoted string") and live numeric-
shape mismatch highlight the simple-mode DSL gives.
Discriminate literal-vs-expression with a boundary-aware lookahead
(shared::SET_VALUE), NOT the naive `Choice(typed-slot, sql_expr)` the ADR
originally sketched: the walker's Choice is first-match-wins with no
backtrack, so a typed slot would greedily match the leading `1` of `1 + 2`
and commit, regressing valid SQL (e.g. the existing `values (1, 1 + 2)`
test). The lookahead peeks the whole value position: a literal routes to
the typed slot only when it fills the position up to the next
`,`/`)`/`;`/`where`/`returning`/end; everything else falls through to the
full sql_expr grammar unchanged. The SET column ident gets
`writes_column: true` so `current_column` drives the slot + hint.
Scope: Phase 3a covers UPDATE's assignment list and INSERT's ON CONFLICT
DO UPDATE SET. Phase 3b (INSERT VALUES — needs a per-position grammar
restructure + multi-row) is deferred. Records ADR-0036 Amendment 1 with
the mechanism correction + the 3a/3b split.
Tests: 1939 passing (+5), 0 failed, 0 skipped, 1 ignored; clippy clean.
This commit is contained in:
@@ -20,9 +20,12 @@
|
||||
//! worker-level tests call `db.run_sql_insert` directly with the
|
||||
//! real reconstructed SQL.
|
||||
|
||||
use rdbms_playground::completion::{SchemaCache, TableColumn};
|
||||
use rdbms_playground::db::{Database, DbError, InsertResult};
|
||||
use rdbms_playground::dsl::{ColumnSpec, Command, Type, Value, parse_command};
|
||||
use rdbms_playground::event::AppEvent;
|
||||
use rdbms_playground::input_render::{AmbientHint, ambient_hint_in_mode};
|
||||
use rdbms_playground::mode::Mode;
|
||||
use rdbms_playground::persistence::Persistence;
|
||||
use rdbms_playground::project;
|
||||
use rdbms_playground::runtime::run_replay;
|
||||
@@ -1182,3 +1185,35 @@ fn sql_insert_natural_order_validates_against_schema_columns() {
|
||||
"natural-order insert validates the date against column `d`; events: {events:?}"
|
||||
);
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// ADR-0036 Phase 3a — live typed-slot hint for the UPSERT
|
||||
// `ON CONFLICT … DO UPDATE SET col = <rhs>` value position.
|
||||
// =================================================================
|
||||
|
||||
#[test]
|
||||
fn advanced_upsert_do_update_set_offers_typed_slot_hint() {
|
||||
// ADR-0036 Phase 3a: the `DO UPDATE SET col = ` value position
|
||||
// shares the SQL UPDATE `SET` treatment, so it drives the same
|
||||
// column-typed slot hint (boundary-aware lookahead → typed slot).
|
||||
let mut cache = SchemaCache::default();
|
||||
let cols = vec![
|
||||
TableColumn { name: "id".to_string(), user_type: Type::Int, not_null: false, has_default: false },
|
||||
TableColumn { name: "Name".to_string(), user_type: Type::Text, not_null: false, has_default: false },
|
||||
];
|
||||
cache.tables.push("Customers".to_string());
|
||||
cache.columns.push("id".to_string());
|
||||
cache.columns.push("Name".to_string());
|
||||
cache.table_columns.insert("Customers".to_string(), cols);
|
||||
|
||||
let input = "insert into Customers (id, Name) values (1, 'x') on conflict (id) do update set Name=";
|
||||
let hint = ambient_hint_in_mode(input, input.len(), None, &cache, Mode::Advanced);
|
||||
let Some(AmbientHint::Prose(prose)) = hint else {
|
||||
panic!("expected a Prose hint at the UPSERT SET value slot, got {hint:?}");
|
||||
};
|
||||
assert!(prose.contains("Name"), "hint names the column `Name`: {prose:?}");
|
||||
assert!(
|
||||
prose.contains("quoted string"),
|
||||
"text-column hint says `quoted string`: {prose:?}"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user