feat: create m:n relationship convenience command (C4, ADR-0045)
`create m:n relationship from <T1> to <T2> [as <name>]` generates a
junction table with one FK column per parent PK column ({table}_{pkcol},
typed via fk_target_type), a compound PK over them, and two CASCADE 1:n
relationships -- all in one do_create_table call = one undo step.
Auto-named {T1}_{T2} (optional `as`), both modes, compound-parent PKs
supported (ADR-0043). Self-referential m:n / PK-less parent / internal
junction name / name collision all refused.
Wired across every surface: grammar (separate CREATE_M2N node), worker
executor, runtime dispatch, completion ("m:n" composite), hints,
highlighting, help + usage catalog + disambiguator, and the advanced-mode
DSL->SQL teaching echo (render_create_m2n, round-trips as valid SQL).
Generalized/fixed framework assumptions the build + two /runda passes
surfaced (all behaviour-preserving for existing commands):
- simple-mode dispatch committed simple.first() unconditionally -> tries
candidates, so `create table` no longer shadows `create m:n`.
- the completion continuation-merge was advanced-only -> runs in simple
mode too when an entry word has >1 DSL form (gated simple_count>1).
- do_create_table now rejects internal `__rdbms_*` names (closes a
pre-existing hole on the DSL create-table path too, not just m:n).
- usage disambiguator now recognizes the `m:n` opener.
Tests: 14 integration (tests/it/m2n.rs), 7 typing-surface matrix, echo /
highlight / usage / internal-name units. Closes C4.
2237 pass / 0 fail / 1 ignored. Clippy clean.
This commit is contained in:
+11
-3
@@ -31,6 +31,7 @@ use crate::mode::Mode;
|
||||
/// fragments the user thinks of as a single phrase:
|
||||
///
|
||||
/// - `1:n` — the opener for `add 1:n relationship`.
|
||||
/// - `m:n` — the opener for `create m:n relationship` (ADR-0045).
|
||||
/// - `double precision` — the lone two-word SQL type alias
|
||||
/// (ADR-0035 §6.3; the grammar has a dedicated branch so the per-word
|
||||
/// `Ident` validator never has to make sense of `double` alone).
|
||||
@@ -40,7 +41,7 @@ use crate::mode::Mode;
|
||||
/// composite replaces the bare opener rather than appearing
|
||||
/// alongside it.
|
||||
const COMPOSITE_CANDIDATES: &[(&str, &str)] =
|
||||
&[("1", "1:n"), ("double", "double precision")];
|
||||
&[("1", "1:n"), ("m", "m:n"), ("double", "double precision")];
|
||||
|
||||
/// Per-project schema lookup cache (ADR-0022 §9, ADR-0024 §Phase D).
|
||||
///
|
||||
@@ -1346,12 +1347,19 @@ mod tests {
|
||||
fn at_token_boundary_offers_next_expected_keyword() {
|
||||
// After `create ` advanced mode offers `table` (valid in both
|
||||
// modes) plus the SQL-only `unique` (`create unique index`) and
|
||||
// `index` — the shared-entry-word merge (ADR-0035 §4i d).
|
||||
// `index` — the shared-entry-word merge (ADR-0035 §4i d) — and
|
||||
// `m:n` (`create m:n relationship`, ADR-0045), surfaced as the
|
||||
// composite (the bare `m` opener is filtered).
|
||||
// `table` (Both) blocks before the Advanced-only `unique`/`index`.
|
||||
let cs = cands("create ", 7);
|
||||
assert_eq!(
|
||||
cs,
|
||||
vec!["table".to_string(), "unique".to_string(), "index".to_string()]
|
||||
vec![
|
||||
"table".to_string(),
|
||||
"unique".to_string(),
|
||||
"index".to_string(),
|
||||
"m:n".to_string()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user