feat(seed): set override clause + column-fill (ADR-0048 Phase 2)
Build the two SD2 surfaces Phase 1 deferred:
- `set` override clause (D2): comma-separated per-column pins —
`= 'v'` (fixed), `in ('a','b')` (pick-list), `as <generator>`
(named), `between x and y` (range; numeric and quoted dates).
Type-aware via the typed `current_column_value` slot; an override
drops its column from the generic-fill advisory (D13). Folded from
the flat matched path (build_seed_overrides) and applied to the
per-column plan (apply_seed_overrides).
- `<table>.<column>` column-fill (D1 form 2): an UPDATE over existing
rows. Refuses PK/autogen targets, empty-table no-op, FK-samples the
parent, collision-free for UNIQUE/identifier targets, one undo step;
`set` may only adjust the filled column.
Supporting work: KNOWN_GENERATORS vocabulary + generator_for_name
(src/seed/vocabulary.rs, D9); a range Generator + range_bounds_reason;
IdentSource::Generators and HighlightClass::Function; completion of the
generator vocabulary after `as` and the set/.col column slots; the
typing-time validity indicator for an unknown generator; help,
parse-error pedagogy rows, and the D13 advisory's Phase-2/3 wording.
A bounded override (fixed value / too-short pick-list) on a
single-column-UNIQUE target is a friendly error rather than a silent
uniqueness cap (post-implementation /runda finding, user-chosen).
Dates in the range form are quoted (no date-literal token exists);
ADR-0048 D2 amended accordingly. Both modes (D5); reproducible (D4).
This commit is contained in:
+83
-1
@@ -120,7 +120,13 @@ impl SchemaCache {
|
||||
IdentSource::Columns => &self.columns,
|
||||
IdentSource::Relationships => &self.relationships,
|
||||
IdentSource::Indexes => &self.indexes,
|
||||
IdentSource::NewName | IdentSource::Types | IdentSource::Free => &[],
|
||||
// Curated / invented sources never come from the schema
|
||||
// cache — `Generators` candidates are supplied separately
|
||||
// from the `seed` vocabulary (ADR-0048 D9).
|
||||
IdentSource::NewName
|
||||
| IdentSource::Types
|
||||
| IdentSource::Generators
|
||||
| IdentSource::Free => &[],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -709,6 +715,22 @@ pub fn candidates_at_cursor_with_in_mode(
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
// Source 1.9: fake-data generator names (ADR-0048 D9). At the
|
||||
// `seed … set <col> as ⟨here⟩` slot (`IdentSource::Generators`) the
|
||||
// curated vocabulary is offered so a learner can discover `email` /
|
||||
// `product` / … by Tab. Same `Function` kind / `tok_function` colour
|
||||
// as SQL functions (no new theme colour — ADR-0048 §Grammar).
|
||||
let has_generator_slot = expected
|
||||
.iter()
|
||||
.any(|e| matches!(e, Expectation::Ident { source: IdentSource::Generators, .. }));
|
||||
if has_generator_slot {
|
||||
functions.extend(
|
||||
crate::seed::KNOWN_GENERATORS
|
||||
.iter()
|
||||
.filter(|g| matches_prefix(g))
|
||||
.map(|g| (*g).to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
// Source 2: schema identifiers — accumulated across every
|
||||
// matching schema-listable `Ident { source }` expectation.
|
||||
@@ -1200,6 +1222,24 @@ pub fn invalid_ident_at_cursor_in_mode(
|
||||
if has_sql_expr_slot && crate::dsl::sql_functions::is_known_function_prefix(partial) {
|
||||
return None;
|
||||
}
|
||||
// ADR-0048 D9: the `seed … set <col> as <gen>` slot is a curated
|
||||
// vocabulary (`IdentSource::Generators`), not a schema source, so the
|
||||
// schema-column check below would never see it. A partial that
|
||||
// prefix-matches a known generator is an in-progress name; anything
|
||||
// else is an unknown generator → flag it `[ERR]` while typing.
|
||||
let has_generator_slot = expected
|
||||
.iter()
|
||||
.any(|e| matches!(e, Expectation::Ident { source: IdentSource::Generators, .. }));
|
||||
if has_generator_slot {
|
||||
if crate::seed::is_known_generator_prefix(partial) {
|
||||
return None;
|
||||
}
|
||||
return Some(InvalidIdent {
|
||||
range: (start, cursor),
|
||||
found: partial.to_string(),
|
||||
source: IdentSource::Generators,
|
||||
});
|
||||
}
|
||||
// Find every schema-listable source in the expected list.
|
||||
let sources: Vec<IdentSource> = expected
|
||||
.iter()
|
||||
@@ -2606,6 +2646,48 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_ident_fires_for_unknown_generator_after_as() {
|
||||
// ADR-0048 D9: an unknown name at the `set <col> as <gen>` slot is
|
||||
// flagged `[ERR]` while typing.
|
||||
let cache = two_table_schema();
|
||||
let input = "seed a set name as bogus";
|
||||
let inv = invalid_ident_at_cursor(input, input.len(), &cache)
|
||||
.expect("unknown generator must flag");
|
||||
assert_eq!(inv.found, "bogus");
|
||||
assert_eq!(inv.source, IdentSource::Generators);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_ident_fires_for_unknown_column_in_seed_set_and_column_fill() {
|
||||
// ADR-0048: an unknown column at the `set <col>` slot and the
|
||||
// `<table>.<col>` column-fill slot is flagged like any other
|
||||
// column slot (both are `IdentSource::Columns`).
|
||||
let cache = two_table_schema(); // table `a`; columns id, name
|
||||
let set_in = invalid_ident_at_cursor("seed a set xyz", 14, &cache)
|
||||
.expect("unknown column in `set` must flag");
|
||||
assert_eq!(set_in.found, "xyz");
|
||||
assert_eq!(set_in.source, IdentSource::Columns);
|
||||
|
||||
let fill = invalid_ident_at_cursor("seed a.xyz", 10, &cache)
|
||||
.expect("unknown column in column-fill must flag");
|
||||
assert_eq!(fill.source, IdentSource::Columns);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_ident_does_not_fire_for_generator_prefix() {
|
||||
// A prefix of a known generator is an in-progress name, not a typo.
|
||||
let cache = two_table_schema();
|
||||
assert!(
|
||||
invalid_ident_at_cursor("seed a set name as ema", 22, &cache).is_none(),
|
||||
"`ema` prefixes `email` — must not flag",
|
||||
);
|
||||
assert!(
|
||||
invalid_ident_at_cursor("seed a set name as email", 24, &cache).is_none(),
|
||||
"`email` is a known generator — must not flag",
|
||||
);
|
||||
}
|
||||
|
||||
fn two_table_schema() -> SchemaCache {
|
||||
use crate::dsl::types::Type;
|
||||
let mut s = SchemaCache::default();
|
||||
|
||||
Reference in New Issue
Block a user