fix(db): swap conversation_keys natural PK for synthetic row_id

Migration 20260515000002 failed on prod because dropping NOT NULL on
recipient_device_id was rejected (column is part of the natural primary
key). This fix-up drops the PK, adds a synthetic row_id BIGSERIAL PK,
re-applies the NOT NULL drop, and re-runs the indexes/policies that
were skipped after the failure.
This commit is contained in:
byGalax
2026-05-15 23:25:49 +02:00
parent f7c60945d0
commit 6a9a0bb804
@@ -0,0 +1,89 @@
-- Fix-up for failed 20260515000002: dropping NOT NULL on a PK column requires
-- dropping the PK first. Replace the natural PK (conversation_id,
-- recipient_device_id, key_version) with a synthetic row_id and re-establish
-- uniqueness via two partial unique indexes — one per recipient mode.
--
-- Also re-runs the policies + indexes from 20260515000002 that were skipped
-- when migration 2 errored out at the ALTER COLUMN step.
alter table public.conversation_keys
drop constraint if exists conversation_keys_pkey;
alter table public.conversation_keys
add column if not exists row_id bigserial primary key;
alter table public.conversation_keys
alter column recipient_device_id drop not null,
alter column sender_device_id drop not null;
-- Partial unique constraints: device-mode and user-mode rows can coexist for
-- the same (conv, version) but each mode is unique on its own.
create unique index if not exists conversation_keys_device_recipient_uniq
on public.conversation_keys(conversation_id, recipient_device_id, key_version)
where recipient_device_id is not null;
create unique index if not exists conversation_keys_user_recipient_uniq
on public.conversation_keys(conversation_id, recipient_user_id, key_version)
where recipient_user_id is not null;
create index if not exists conversation_keys_recipient_user_idx
on public.conversation_keys(recipient_user_id);
-- Re-apply the policies from migration 20260515000002 (those didn't run
-- because the failing ALTER stopped the file).
drop policy if exists conversation_keys_select_owner on public.conversation_keys;
create policy conversation_keys_select_owner
on public.conversation_keys
for select
to authenticated
using (
recipient_user_id = auth.uid()
or exists (
select 1
from public.devices d
where d.id = recipient_device_id
and d.user_id = auth.uid()
)
);
drop policy if exists conversation_keys_insert_member on public.conversation_keys;
create policy conversation_keys_insert_member
on public.conversation_keys
for insert
to authenticated
with check (
exists (
select 1
from public.conversation_members m
where m.conversation_id = conversation_keys.conversation_id
and m.user_id = auth.uid()
and m.accepted = true
)
and (
sender_user_id = auth.uid()
or exists (
select 1
from public.devices d
where d.id = sender_device_id
and d.user_id = auth.uid()
)
)
and (
(recipient_user_id is not null and exists (
select 1
from public.conversation_members m
where m.conversation_id = conversation_keys.conversation_id
and m.user_id = recipient_user_id
and m.accepted = true
))
or (recipient_device_id is not null and exists (
select 1
from public.devices d
join public.conversation_members m
on m.user_id = d.user_id
and m.conversation_id = conversation_keys.conversation_id
where d.id = recipient_device_id
and m.accepted = true
))
)
);