50 lines
1.8 KiB
PL/PgSQL
50 lines
1.8 KiB
PL/PgSQL
-- ============================================================
|
|
-- Migration 013: Distributed Entry Locking
|
|
-- Date: 2026-02-18
|
|
-- Purpose: Add deterministic distributed locking + lifecycle guard for ENTRY flows.
|
|
-- ============================================================
|
|
|
|
-- Determine a stable 64-bit key per profile+symbol pair.
|
|
CREATE OR REPLACE FUNCTION fn_entry_lock_key(p_profile uuid, p_symbol text)
|
|
RETURNS bigint AS $$
|
|
DECLARE
|
|
profile_hash bigint;
|
|
symbol_hash bigint;
|
|
BEGIN
|
|
profile_hash := abs(hashtext(coalesce(p_profile::text, ''))::bigint) % 2147483647;
|
|
symbol_hash := abs(hashtext(coalesce(lower(p_symbol), ''))::bigint) % 4294967295;
|
|
RETURN ((profile_hash << 32) + symbol_hash) % 9223372036854775807;
|
|
END;
|
|
$$ LANGUAGE plpgsql IMMUTABLE;
|
|
|
|
-- Try to obtain the advisory lock without blocking.
|
|
CREATE OR REPLACE FUNCTION fn_try_acquire_entry_lock(p_profile uuid, p_symbol text)
|
|
RETURNS boolean AS $$
|
|
BEGIN
|
|
RETURN pg_try_advisory_lock(fn_entry_lock_key(p_profile, p_symbol));
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Release the advisory lock.
|
|
CREATE OR REPLACE FUNCTION fn_release_entry_lock(p_profile uuid, p_symbol text)
|
|
RETURNS boolean AS $$
|
|
BEGIN
|
|
RETURN pg_advisory_unlock(fn_entry_lock_key(p_profile, p_symbol));
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Check for an existing ENTRY/OPEN lifecycle for the same profile+symbol.
|
|
CREATE OR REPLACE FUNCTION fn_is_entry_in_progress(p_profile uuid, p_symbol text)
|
|
RETURNS boolean AS $$
|
|
BEGIN
|
|
RETURN EXISTS (
|
|
SELECT 1
|
|
FROM trade_lifecycle tl
|
|
JOIN positions p ON p.profile_id = tl.profile_id AND p.trade_id = tl.trade_id
|
|
WHERE tl.profile_id = p_profile
|
|
AND lower(p.symbol) = lower(coalesce(p_symbol, ''))
|
|
AND tl.current_stage IN ('ENTRY', 'OPEN')
|
|
);
|
|
END;
|
|
$$ LANGUAGE plpgsql STABLE;
|