52 lines
1.6 KiB
PL/PgSQL
52 lines
1.6 KiB
PL/PgSQL
-- 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();
|