67 lines
2.2 KiB
PL/PgSQL
67 lines
2.2 KiB
PL/PgSQL
-- 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.';
|