monacousa-portal/supabase/migrations/018_atomic_member_id_genera...

67 lines
2.2 KiB
MySQL
Raw Normal View History

-- Fix Race Condition in Member ID Generation
-- ============================================
-- Problem: Current implementation uses MAX() or COUNT() which allows duplicate IDs
-- if two registrations happen simultaneously.
--
-- Solution: Use PostgreSQL sequence for atomic ID generation.
-- Step 1: Create sequence for member IDs
-- Find the highest existing member number to set starting point
DO $$
DECLARE
max_num INTEGER;
BEGIN
-- Extract numeric part from existing member_ids (handles both MUSA-XXXX and MUSA-YYYY-XXXX formats)
SELECT COALESCE(
MAX(
CAST(
SUBSTRING(
member_id FROM '[0-9]+$' -- Get trailing digits
) AS INTEGER
)
),
0
) INTO max_num
FROM public.members;
-- Create sequence starting from next available number
EXECUTE format('CREATE SEQUENCE IF NOT EXISTS member_id_seq START WITH %s', max_num + 1);
END $$;
-- Step 2: Replace trigger function to use sequence
CREATE OR REPLACE FUNCTION generate_member_id()
RETURNS TRIGGER AS $$
DECLARE
next_num INTEGER;
current_year TEXT;
BEGIN
-- Only generate if member_id is not already set
IF NEW.member_id IS NOT NULL THEN
RETURN NEW;
END IF;
-- Get next number from sequence (atomic operation)
next_num := NEXTVAL('member_id_seq');
current_year := EXTRACT(YEAR FROM CURRENT_DATE)::TEXT;
-- Format: MUSA-YYYY-XXXX (matches application format)
NEW.member_id := 'MUSA-' || current_year || '-' || LPAD(next_num::TEXT, 4, '0');
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Step 3: Ensure trigger exists (should already exist from 001_initial_schema.sql)
DROP TRIGGER IF EXISTS set_member_id ON public.members;
CREATE TRIGGER set_member_id
BEFORE INSERT ON public.members
FOR EACH ROW
WHEN (NEW.member_id IS NULL)
EXECUTE FUNCTION generate_member_id();
-- Step 4: Add index on member_id for faster lookups (if not exists)
CREATE INDEX IF NOT EXISTS idx_members_member_id ON public.members(member_id);
COMMENT ON SEQUENCE member_id_seq IS 'Atomic sequence for generating unique member IDs. Used by generate_member_id() trigger.';
COMMENT ON FUNCTION generate_member_id() IS 'Atomically generates member IDs using NEXTVAL(member_id_seq) to prevent race conditions.';