Files
buzz/schema/functions/attach_community_write_fence.sql
ngoodger-block f33ebaafa1 refactor(schema): stop auto-fencing new tables in desired-state bootstrap (#8620)
### Why?

This is a cleanup that brings the schema bootstrap in line with how the
codebase works today, as asked for by
[`docs/database.md`](dc25198d53/docs/database.md (L103-L124))
(#8273). Writes are now blocked during community deletion by application
admission (`begin_community_write_transaction`), and that doc's Rule 1
says new tables must not add triggers. But
`schema/community_write_fence.sql` still looped over every table with a
`community_id` column and attached the old fence trigger to it. That
meant a new table got a trigger on a fresh database and none on a
migrated one, so the two schema paths disagreed and new tenant tables
were blocked. The doc says this bootstrap change must land "before the
feature, or as the feature's first PR."

### What?

Deletes the blanket fence loop and its `\i` from `schema/schema.sql`.
Existing legacy tables keep their fences through their explicit
`schema/triggers/` files, so no current table loses its fence and
runtime behavior is unchanged. Adds a test that a new `community_id`
table stays unfenced while `events` and `users` stay fenced, and updates
`docs/database.md` and `ARCHITECTURE.md` to match. It stands on its own;
the media-sharing PRs (#8451, #8444) are the first to rely on it.

buzz-review-completed

---------

Signed-off-by: Nikolaj Goodger <ngoodger@squareup.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-28 09:26:41 +11:00

46 lines
1.6 KiB
PL/PgSQL

-- Attach the legacy universal fence to one community-scoped relation. Only
-- tables fenced before fence retirement use it; new tenant tables rely on
-- application admission and must not call it (docs/database.md Rule 1).
CREATE FUNCTION attach_community_write_fence(target REGCLASS) RETURNS VOID
LANGUAGE plpgsql AS $$
DECLARE
relation_name NAME;
BEGIN
SELECT c.relname
INTO relation_name
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.oid = target
AND n.nspname = current_schema()
AND c.relkind IN ('r', 'p')
AND NOT c.relispartition;
IF NOT FOUND THEN
RAISE EXCEPTION 'community write fence target % is not a table in the current schema', target
USING ERRCODE = 'wrong_object_type';
END IF;
IF community_write_fence_excluded_table(relation_name) THEN
RETURN;
END IF;
IF NOT EXISTS (
SELECT 1 FROM pg_attribute
WHERE attrelid = target AND attname = 'community_id' AND NOT attisdropped
) THEN
RAISE EXCEPTION 'community write fence target % has no community_id', target
USING ERRCODE = 'undefined_column';
END IF;
IF NOT EXISTS (
SELECT 1 FROM pg_trigger
WHERE tgrelid = target
AND tgname = 'community_write_fence_' || relation_name
AND NOT tgisinternal
) THEN
EXECUTE format(
'CREATE TRIGGER %I BEFORE INSERT OR UPDATE OR DELETE ON %s '
'FOR EACH ROW EXECUTE FUNCTION enforce_community_write_fence()',
'community_write_fence_' || relation_name,
target
);
END IF;
END
$$;