fix(schema): refresh library_type_name on library change; make trigger block re-runnable
schema.sql is re-executed in full on every application startup, so every statement in it must be idempotent. Two related problems introduced with the cross-library move feature, plus their fix: Problem 1: media_items.library_type_name was only populated by a BEFORE INSERT trigger. When the scanner repoints an existing row to a different library (cross-library move detection), the denormalized library_type_name went stale, mislabeling the item's type for validation and display. Fix: add a second trigger, set_library_type_name_on_library_change, that fires BEFORE UPDATE OF library_id and re-runs the same population function. Problem 2 (outage): the new trigger EXECUTE FUNCTIONs set_library_type_name(), but the pre-existing idempotency dance drops that function on every startup before recreating it. On the second and later boots, DROP FUNCTION failed with SQLSTATE 2BP01 (function still depended on by the trigger created by the previous boot), aborting the whole schema transaction and crash-looping the container. Fix: drop BOTH triggers before dropping the function, and create both after it. The ordering now survives any number of restarts on any database state. Adds TestSchemaInitializationIsIdempotent, which replays the production database.Initialize twice against the same database so this class of "second startup" regression fails in CI instead of in production. Note the test uses a dedicated pgxpool: Initialize holds the advisory lock on one connection while executing the schema on another, which deadlocks against the shared test pool's MaxConns=1.
This commit is contained in:
@@ -259,8 +259,11 @@ CREATE INDEX IF NOT EXISTS idx_media_items_archived ON media_items (archived_at)
|
||||
CREATE INDEX IF NOT EXISTS idx_media_items_tags_search ON media_items USING GIN (tags_search);
|
||||
CREATE INDEX IF NOT EXISTS idx_media_items_contributors_search ON media_items USING GIN (contributors_search);
|
||||
|
||||
-- Drop existing trigger if exists
|
||||
-- Drop existing triggers if exists. Both must be dropped BEFORE the function:
|
||||
-- dropping a function that triggers still reference fails (SQLSTATE 2BP01),
|
||||
-- which would abort the schema transaction on every startup after the first.
|
||||
DROP TRIGGER IF EXISTS set_library_type_name_on_insert ON media_items;
|
||||
DROP TRIGGER IF EXISTS set_library_type_name_on_library_change ON media_items;
|
||||
-- Drop existing function if exists
|
||||
DROP FUNCTION IF EXISTS set_library_type_name();
|
||||
|
||||
@@ -279,6 +282,14 @@ CREATE TRIGGER set_library_type_name_on_insert
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION set_library_type_name();
|
||||
|
||||
-- Keep library_type_name truthful when an item is repointed to another
|
||||
-- library (cross-library move detection reuses the same function; the row
|
||||
-- keeps its ID while its library changes).
|
||||
CREATE TRIGGER set_library_type_name_on_library_change
|
||||
BEFORE UPDATE OF library_id ON media_items
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION set_library_type_name();
|
||||
|
||||
-- Add GIN indexes for pg_trgm fuzzy search performance
|
||||
CREATE INDEX IF NOT EXISTS idx_media_items_author_trgm
|
||||
ON media_items USING GIN (author gin_trgm_ops);
|
||||
|
||||
Reference in New Issue
Block a user