refactor: relationship model to column lists for compound FK (ADR-0043)
Move the FK column fields String->Vec<String> through all six layers (AddRelationship/SqlForeignKey AST, RelationshipSchema, metadata, project.yaml, ReadForeignKey, RelationshipEnd). Metadata stores comma-joined lists in the existing TEXT cells; project.yaml endpoints now columns: [a, b] (house style). Executor logic is multi-column ready: resolve_fk_parent_columns (full-PK F-A + auto-expand F-D), per-pair type-compat, schema_to_ddl multi-column emission, pragma FK read grouped by id, auto-name + --create-fk per-column, multi-column teaching echo. Single-column behaviour preserved (one-element vecs); all 2181 tests green. The grammar to parse multi-column input lands next.
This commit is contained in:
+37
-30
@@ -264,12 +264,16 @@ pub(crate) fn render_drop_index(name: &str) -> String {
|
||||
pub(crate) fn render_add_relationship(
|
||||
name: &str,
|
||||
parent_table: &str,
|
||||
parent_column: &str,
|
||||
parent_columns: &[String],
|
||||
child_table: &str,
|
||||
child_column: &str,
|
||||
child_columns: &[String],
|
||||
on_delete: ReferentialAction,
|
||||
on_update: ReferentialAction,
|
||||
) -> String {
|
||||
// Multi-column FK (ADR-0043): comma-join each side; a
|
||||
// single-column FK is the one-element case.
|
||||
let child_column = child_columns.join(", ");
|
||||
let parent_column = parent_columns.join(", ");
|
||||
let mut s = format!(
|
||||
"ALTER TABLE {child_table} ADD CONSTRAINT {name} FOREIGN KEY ({child_column}) REFERENCES {parent_table} ({parent_column})"
|
||||
);
|
||||
@@ -325,28 +329,31 @@ pub(crate) fn render_drop_column_cascade(
|
||||
pub(crate) fn render_add_relationship_create_fk(
|
||||
name: &str,
|
||||
parent_table: &str,
|
||||
parent_column: &str,
|
||||
parent_columns: &[String],
|
||||
child_table: &str,
|
||||
child_column: &str,
|
||||
child_columns: &[String],
|
||||
on_delete: ReferentialAction,
|
||||
on_update: ReferentialAction,
|
||||
new_child_column_type: crate::dsl::types::Type,
|
||||
// The child columns `--create-fk` newly creates, with their types
|
||||
// (ADR-0043: one per missing column, typed to the matching parent
|
||||
// PK column's `fk_target_type`). Columns that already existed are
|
||||
// omitted — no `ADD COLUMN` line for them.
|
||||
new_columns: &[(String, crate::dsl::types::Type)],
|
||||
) -> Vec<String> {
|
||||
vec![
|
||||
format!(
|
||||
"ALTER TABLE {child_table} ADD COLUMN {child_column} {}",
|
||||
new_child_column_type.keyword()
|
||||
),
|
||||
render_add_relationship(
|
||||
name,
|
||||
parent_table,
|
||||
parent_column,
|
||||
child_table,
|
||||
child_column,
|
||||
on_delete,
|
||||
on_update,
|
||||
),
|
||||
]
|
||||
let mut lines: Vec<String> = new_columns
|
||||
.iter()
|
||||
.map(|(col, ty)| format!("ALTER TABLE {child_table} ADD COLUMN {col} {}", ty.keyword()))
|
||||
.collect();
|
||||
lines.push(render_add_relationship(
|
||||
name,
|
||||
parent_table,
|
||||
parent_columns,
|
||||
child_table,
|
||||
child_columns,
|
||||
on_delete,
|
||||
on_update,
|
||||
));
|
||||
lines
|
||||
}
|
||||
|
||||
/// Append the `NOT NULL` / `UNIQUE` / `DEFAULT` / `CHECK` column-constraint
|
||||
@@ -953,9 +960,9 @@ mod tests {
|
||||
let sql = render_add_relationship(
|
||||
"Orders_CustId_to_Customers_id",
|
||||
"Customers",
|
||||
"id",
|
||||
&["id".to_string()],
|
||||
"Orders",
|
||||
"CustId",
|
||||
&["CustId".to_string()],
|
||||
ReferentialAction::NoAction,
|
||||
ReferentialAction::NoAction,
|
||||
);
|
||||
@@ -971,9 +978,9 @@ mod tests {
|
||||
let sql = render_add_relationship(
|
||||
"places",
|
||||
"Customers",
|
||||
"id",
|
||||
&["id".to_string()],
|
||||
"Orders",
|
||||
"CustId",
|
||||
&["CustId".to_string()],
|
||||
ReferentialAction::Cascade,
|
||||
ReferentialAction::SetNull,
|
||||
);
|
||||
@@ -1029,14 +1036,14 @@ mod tests {
|
||||
let lines = render_add_relationship_create_fk(
|
||||
"Customers_id_to_Orders_CustId",
|
||||
"Customers",
|
||||
"id",
|
||||
&["id".to_string()],
|
||||
"Orders",
|
||||
"CustId",
|
||||
&["CustId".to_string()],
|
||||
ReferentialAction::Cascade,
|
||||
ReferentialAction::NoAction,
|
||||
// Parent PK is `serial` → child FK column is `int`
|
||||
// (`Type::fk_target_type` strips auto-gen semantics; ADR-0011).
|
||||
crate::dsl::types::Type::Int,
|
||||
&[("CustId".to_string(), crate::dsl::types::Type::Int)],
|
||||
);
|
||||
assert_eq!(
|
||||
lines.as_slice(),
|
||||
@@ -1055,12 +1062,12 @@ mod tests {
|
||||
let lines = render_add_relationship_create_fk(
|
||||
"Items_code_to_Lines_code",
|
||||
"Items",
|
||||
"code",
|
||||
&["code".to_string()],
|
||||
"Lines",
|
||||
"code",
|
||||
&["code".to_string()],
|
||||
ReferentialAction::NoAction,
|
||||
ReferentialAction::NoAction,
|
||||
crate::dsl::types::Type::Text,
|
||||
&[("code".to_string(), crate::dsl::types::Type::Text)],
|
||||
);
|
||||
assert_eq!(lines[0], "ALTER TABLE Lines ADD COLUMN code text");
|
||||
// No referential clauses when both default.
|
||||
|
||||
Reference in New Issue
Block a user