80 lines
2.4 KiB
PL/PgSQL
80 lines
2.4 KiB
PL/PgSQL
-- ============================================================
|
|
-- Migration 014: Row-Based Entry Lock Table
|
|
-- Date: 2026-02-18
|
|
-- Purpose: Replace session-based advisory locks with durable row locks.
|
|
-- ============================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS entry_locks (
|
|
profile_id uuid NOT NULL,
|
|
symbol text NOT NULL,
|
|
symbol_normalized text GENERATED ALWAYS AS (lower(symbol)) STORED NOT NULL,
|
|
owner text NOT NULL,
|
|
acquired_at timestamptz NOT NULL DEFAULT now(),
|
|
expires_at timestamptz NOT NULL,
|
|
PRIMARY KEY (profile_id, symbol_normalized)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS entry_locks_expires_idx ON entry_locks (expires_at);
|
|
|
|
ALTER TABLE entry_locks ENABLE ROW LEVEL SECURITY;
|
|
|
|
DROP POLICY IF EXISTS "Allow entry lock owner" ON entry_locks;
|
|
CREATE POLICY "Allow entry lock owner" ON entry_locks
|
|
FOR ALL
|
|
USING (auth.uid() = profile_id)
|
|
WITH CHECK (auth.uid() = profile_id);
|
|
|
|
DROP POLICY IF EXISTS "Allow service role entry lock access" ON entry_locks;
|
|
CREATE POLICY "Allow service role entry lock access" ON entry_locks
|
|
FOR ALL
|
|
USING (auth.role() = 'service_role')
|
|
WITH CHECK (auth.role() = 'service_role');
|
|
|
|
CREATE OR REPLACE FUNCTION fn_try_acquire_entry_lock_row(
|
|
p_profile_id uuid,
|
|
p_symbol text,
|
|
p_owner text,
|
|
p_ttl_seconds integer DEFAULT 30
|
|
)
|
|
RETURNS boolean AS
|
|
$$
|
|
DECLARE
|
|
now_ts timestamptz := now();
|
|
ttl integer := GREATEST(COALESCE(p_ttl_seconds, 30), 1);
|
|
expires timestamptz := now_ts + make_interval(secs := ttl);
|
|
BEGIN
|
|
INSERT INTO entry_locks (profile_id, symbol, owner, acquired_at, expires_at)
|
|
VALUES (p_profile_id, p_symbol, p_owner, now_ts, expires)
|
|
ON CONFLICT (profile_id, symbol_normalized)
|
|
WHERE entry_locks.expires_at <= now_ts
|
|
DO UPDATE SET
|
|
owner = EXCLUDED.owner,
|
|
acquired_at = EXCLUDED.acquired_at,
|
|
expires_at = EXCLUDED.expires_at;
|
|
RETURN TRUE;
|
|
EXCEPTION WHEN unique_violation THEN
|
|
RETURN FALSE;
|
|
END;
|
|
$$
|
|
LANGUAGE plpgsql VOLATILE;
|
|
|
|
CREATE OR REPLACE FUNCTION fn_release_entry_lock_row(
|
|
p_profile_id uuid,
|
|
p_symbol text,
|
|
p_owner text
|
|
)
|
|
RETURNS boolean AS
|
|
$$
|
|
DECLARE
|
|
deleted_count integer;
|
|
BEGIN
|
|
DELETE FROM entry_locks
|
|
WHERE profile_id = p_profile_id
|
|
AND symbol_normalized = lower(p_symbol)
|
|
AND owner = p_owner;
|
|
GET DIAGNOSTICS deleted_count = ROW_COUNT;
|
|
RETURN deleted_count > 0;
|
|
END;
|
|
$$
|
|
LANGUAGE plpgsql VOLATILE;
|