From 759012f6962943d0835f701a6c537d93e8d301d9 Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 27 Apr 2026 14:02:27 +0200 Subject: [PATCH] feat(stammbaum): add V54 migration for family network Adds persons.family_member flag and person_relationships table with ON DELETE CASCADE on both FKs, no_self_rel check, unique_rel composite, indexes on both person columns, and partial unique index for symmetric SIBLING_OF pairs (LEAST/GREATEST trick). Refs #358. Co-Authored-By: Claude Sonnet 4.6 --- .../db/migration/V54__add_family_network.sql | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 backend/src/main/resources/db/migration/V54__add_family_network.sql diff --git a/backend/src/main/resources/db/migration/V54__add_family_network.sql b/backend/src/main/resources/db/migration/V54__add_family_network.sql new file mode 100644 index 00000000..0de01f91 --- /dev/null +++ b/backend/src/main/resources/db/migration/V54__add_family_network.sql @@ -0,0 +1,30 @@ +-- Family network: marks a Person as a tree node and stores typed relationships +-- between two persons. The tree page (/stammbaum) only shows persons with +-- family_member = TRUE. Symmetric types (SPOUSE_OF, SIBLING_OF) are stored once; +-- the partial unique index keeps SIBLING_OF pairs from being duplicated in the +-- reverse direction. + +ALTER TABLE persons + ADD COLUMN family_member BOOLEAN NOT NULL DEFAULT FALSE; + +CREATE TABLE person_relationships ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + person_id UUID NOT NULL REFERENCES persons(id) ON DELETE CASCADE, + related_person_id UUID NOT NULL REFERENCES persons(id) ON DELETE CASCADE, + relation_type VARCHAR(30) NOT NULL, + from_year INTEGER, + to_year INTEGER, + notes VARCHAR(2000), + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + CONSTRAINT no_self_rel CHECK (person_id <> related_person_id), + CONSTRAINT unique_rel UNIQUE (person_id, related_person_id, relation_type) +); + +CREATE INDEX idx_person_rel_person_id ON person_relationships(person_id); +CREATE INDEX idx_person_rel_related_person_id ON person_relationships(related_person_id); + +-- Symmetric SIBLING_OF: enforce only one row per unordered pair. +CREATE UNIQUE INDEX unique_sibling_pair ON person_relationships ( + LEAST(person_id, related_person_id), + GREATEST(person_id, related_person_id) +) WHERE relation_type = 'SIBLING_OF';