Modeling Polymorphic Relations in Postgres 15 with bi-directional (cyclic) FK constraints
1
vote
1
answer
2304
views
I am drawn to this design like a moth to a flame. I've read rumblings that cyclic foreign keys are a nightmare. Generally, I can see why they should be avoided. In this particular case, however, I don't see why it would be so awful. Got some inspiration from this article, but combined the reverse-belongs-to and exclusive-belongs-to designs into one... monstrous creation?
Can you tell me why (or if) this is a bad idea? Like what headaches am I going to get into with this and are they worth it?
Anyway, I want to create a polymorphic relationship between many entities and one table. I want to make something like this:
-- poly ids are uuids and we assume they will not collide, period
create domain poly_id uuid;
create table foo ([...table columns], poly_id not null default gen_random_uuid())
create table bar ([...table columns], poly_id not null default gen_random_uuid())
create table baz ([...table columns], poly_id not null default gen_random_uuid())
create type poly_t as enum ('foo', 'bar', 'baz')
create table poly_obj (
-- poly_id is always the poly_id of the one set reference column
poly_id poly_id not null
generated always as ( coalesce("foo", "bar", "baz") ),
poly_t poly_t not null,
"foo" poly_id null references foo (poly_id) check ( "foo" is null or poly_t = 'foo' ),
"bar" poly_id null references bar (poly_id) check ( "bar" is null or poly_t = 'bar' ),
"baz" poly_id null references baz (poly_id) check ( "baz" is null or poly_t = 'baz' )
-- only one fk to child table can be set
check (
(
("foo" is not null)::integer +
("bar" is not null)::integer +
("baz" is not null)::integer
) = 1
)
)
create unique index on poly_obj ("foo") where "foo" is not null;
create unique index on poly_obj ("bar") where "bar" is not null;
create unique index on poly_obj ("baz") where "baz" is not null;
alter table foo add foreign key (poly_id) references poly_obj (poly_id)
alter table bar add foreign key (poly_id) references poly_obj (poly_id)
alter table baz add foreign key (poly_id) references poly_obj (poly_id)
-- more pseudocodey than the rest
create trigger ___ after insert on foo for each row execute
insert into poly_obj (poly_t, foo) select ('foo', poly_id) from new;
create trigger ___ after insert on bar for each row execute
insert into poly_obj (poly_t, bar) select ('bar', poly_id) from new;
create trigger ___ after insert on baz for each row execute
insert into poly_obj (poly_t, baz) select ('baz', poly_id) from new;
Asked by untitled90
(11 rep)
Aug 9, 2023, 05:01 PM
Last activity: Aug 16, 2024, 03:00 PM
Last activity: Aug 16, 2024, 03:00 PM