-- Improve task_number generation to eliminate race conditions. -- Maintain a separate counter table so concurrent inserts can safely bump the -- sequence using an atomic upsert. This migration retains the existing -- trigger name but replaces its body, and creates the helper table. -- 1. Create counter table CREATE TABLE IF NOT EXISTS task_number_counters ( year_month text PRIMARY KEY, counter bigint NOT NULL ); -- 2. Initialize counters from existing tasks INSERT INTO task_number_counters(year_month, counter) SELECT s.prefix, s.cnt FROM ( SELECT to_char(created_at::timestamp, 'YYYY-MM-') AS prefix, max((substring(task_number FROM 8))::int) AS cnt FROM tasks GROUP BY prefix ) s ON CONFLICT (year_month) DO UPDATE SET counter = EXCLUDED.counter; -- 3. Replace trigger function with atomic counter logic CREATE OR REPLACE FUNCTION tasks_set_task_number() RETURNS trigger AS $$ DECLARE prefix text; seq bigint; BEGIN IF NEW.task_number IS NOT NULL THEN RETURN NEW; END IF; prefix := to_char(now(), 'YYYY-MM-'); INSERT INTO task_number_counters(year_month, counter) VALUES (prefix, 1) ON CONFLICT (year_month) DO UPDATE SET counter = task_number_counters.counter + 1 RETURNING counter INTO seq; NEW.task_number := prefix || lpad(seq::text, 5, '0'); RETURN NEW; END; $$ LANGUAGE plpgsql; -- 4. Recreate trigger (drop/recreate to ensure correct function is bound) DROP TRIGGER IF EXISTS tasks_set_task_number_before_insert ON tasks; CREATE TRIGGER tasks_set_task_number_before_insert BEFORE INSERT ON tasks FOR EACH ROW EXECUTE FUNCTION tasks_set_task_number();