fix(CF-816): Self-healing session number sequence to prevent drift
session_sequences table fell behind when sessions were inserted with explicit session_number (e.g., retro imports), causing duplicate key violations on next auto-assigned number. Function now syncs forward by checking MAX(session_number) before assigning. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
57
migrations/032_fix_session_sequence_drift.sql
Normal file
57
migrations/032_fix_session_sequence_drift.sql
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
-- Migration 032: Fix session_sequences drift (CF-816)
|
||||||
|
-- Problem: Retro-imported sessions with explicit session_number bypass the trigger,
|
||||||
|
-- leaving session_sequences.next_number behind the actual MAX(session_number).
|
||||||
|
-- Next auto-assigned number then collides with the unique index.
|
||||||
|
-- Fix: Make get_next_session_number() self-healing by always checking actual max.
|
||||||
|
|
||||||
|
-- Step 1: Replace the function with a self-healing version
|
||||||
|
CREATE OR REPLACE FUNCTION get_next_session_number(p_project TEXT)
|
||||||
|
RETURNS INTEGER AS $$
|
||||||
|
DECLARE
|
||||||
|
v_seq_number INTEGER;
|
||||||
|
v_max_number INTEGER;
|
||||||
|
v_number INTEGER;
|
||||||
|
BEGIN
|
||||||
|
-- Insert project if doesn't exist
|
||||||
|
INSERT INTO projects (key, name) VALUES (p_project, p_project)
|
||||||
|
ON CONFLICT (key) DO NOTHING;
|
||||||
|
|
||||||
|
-- Insert sequence if doesn't exist
|
||||||
|
INSERT INTO session_sequences (project, next_number)
|
||||||
|
VALUES (p_project, 1)
|
||||||
|
ON CONFLICT (project) DO NOTHING;
|
||||||
|
|
||||||
|
-- Get the actual max session_number for this project (handles external inserts)
|
||||||
|
SELECT COALESCE(MAX(session_number), 0) INTO v_max_number
|
||||||
|
FROM sessions
|
||||||
|
WHERE project = p_project;
|
||||||
|
|
||||||
|
-- Sync sequence forward if it fell behind (self-healing)
|
||||||
|
UPDATE session_sequences
|
||||||
|
SET next_number = GREATEST(next_number, v_max_number + 1),
|
||||||
|
last_updated = NOW()
|
||||||
|
WHERE project = p_project;
|
||||||
|
|
||||||
|
-- Now atomically increment and return
|
||||||
|
UPDATE session_sequences
|
||||||
|
SET next_number = next_number + 1,
|
||||||
|
last_updated = NOW()
|
||||||
|
WHERE project = p_project
|
||||||
|
RETURNING next_number - 1 INTO v_number;
|
||||||
|
|
||||||
|
RETURN v_number;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
-- Step 2: Sync all existing sequences to match actual data
|
||||||
|
UPDATE session_sequences sq
|
||||||
|
SET next_number = GREATEST(sq.next_number, sub.actual_max + 1),
|
||||||
|
last_updated = NOW()
|
||||||
|
FROM (
|
||||||
|
SELECT project, COALESCE(MAX(session_number), 0) AS actual_max
|
||||||
|
FROM sessions
|
||||||
|
WHERE project IS NOT NULL
|
||||||
|
GROUP BY project
|
||||||
|
) sub
|
||||||
|
WHERE sq.project = sub.project
|
||||||
|
AND sq.next_number <= sub.actual_max;
|
||||||
Reference in New Issue
Block a user