-- 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.';