owpengram-server/deploy/migrations/20260714003055_star_gift_lifecycle.up.sql
2026-07-21 00:18:12 +03:00

425 lines
21 KiB
PL/PgSQL

-- Complete collectible Star Gift lifecycle: ownership state, transfer/resale, purchase
-- offers, crafting, auctions, notification preferences and the explicit TON boundary.
ALTER TABLE public.peer_star_gifts
DROP CONSTRAINT IF EXISTS peer_star_gifts_terminal_state_check,
ADD COLUMN lifecycle_status text DEFAULT 'active' NOT NULL,
ADD COLUMN transfer_stars bigint DEFAULT 0 NOT NULL,
ADD COLUMN prepaid_upgrade_hash text DEFAULT '' NOT NULL,
ADD COLUMN gift_num integer DEFAULT 0 NOT NULL,
ADD COLUMN can_export_at integer DEFAULT 0 NOT NULL,
ADD COLUMN can_transfer_at integer DEFAULT 0 NOT NULL,
ADD COLUMN can_resell_at integer DEFAULT 0 NOT NULL,
ADD COLUMN drop_original_details_stars bigint DEFAULT 0 NOT NULL,
ADD COLUMN can_craft_at integer DEFAULT 0 NOT NULL;
UPDATE public.peer_star_gifts SET lifecycle_status='converted' WHERE converted;
ALTER TABLE public.peer_star_gifts
ADD CONSTRAINT peer_star_gifts_lifecycle_check CHECK (
lifecycle_status IN ('active', 'converted', 'burned', 'exported') AND
transfer_stars >= 0 AND gift_num >= 0 AND can_export_at >= 0 AND can_transfer_at >= 0 AND
can_resell_at >= 0 AND drop_original_details_stars >= 0 AND can_craft_at >= 0 AND
((lifecycle_status='converted' AND converted AND unique_gift_id IS NULL) OR
(lifecycle_status='active' AND NOT converted) OR
(lifecycle_status IN ('burned','exported') AND NOT converted AND unique_gift_id IS NOT NULL))
);
CREATE UNIQUE INDEX peer_star_gifts_prepaid_upgrade_hash_uniq
ON public.peer_star_gifts(prepaid_upgrade_hash) WHERE prepaid_upgrade_hash<>'';
ALTER TABLE public.unique_star_gifts
ALTER COLUMN owner_peer_type DROP NOT NULL,
ALTER COLUMN owner_peer_id DROP NOT NULL,
DROP CONSTRAINT IF EXISTS unique_star_gift_owner_check,
ADD COLUMN require_premium boolean DEFAULT false NOT NULL,
ADD COLUMN resale_ton_only boolean DEFAULT false NOT NULL,
ADD COLUMN theme_available boolean DEFAULT false NOT NULL,
ADD COLUMN burned boolean DEFAULT false NOT NULL,
ADD COLUMN crafted boolean DEFAULT false NOT NULL,
ADD COLUMN original_owner_peer_type text,
ADD COLUMN original_owner_peer_id bigint,
ADD COLUMN owner_name text DEFAULT '' NOT NULL,
ADD COLUMN owner_address text DEFAULT '' NOT NULL,
ADD COLUMN gift_address text DEFAULT '' NOT NULL,
ADD COLUMN released_by_peer_type text,
ADD COLUMN released_by_peer_id bigint,
ADD COLUMN value_amount bigint DEFAULT 0 NOT NULL,
ADD COLUMN value_currency text DEFAULT '' NOT NULL,
ADD COLUMN value_usd_amount bigint DEFAULT 0 NOT NULL,
ADD COLUMN theme_peer_type text,
ADD COLUMN theme_peer_id bigint,
ADD COLUMN host_peer_type text,
ADD COLUMN host_peer_id bigint,
ADD COLUMN offer_min_stars integer DEFAULT 0 NOT NULL,
ADD COLUMN craft_chance_permille integer DEFAULT 0 NOT NULL,
ADD COLUMN last_sale_date integer DEFAULT 0 NOT NULL,
ADD COLUMN last_sale_currency text DEFAULT '' NOT NULL,
ADD COLUMN last_sale_amount bigint DEFAULT 0 NOT NULL,
ADD CONSTRAINT unique_star_gift_owner_check CHECK (
(owner_peer_type IN ('user','channel') AND owner_peer_id > 0 AND owner_address='') OR
(owner_peer_type IS NULL AND owner_peer_id IS NULL AND owner_address<>'')
),
ADD CONSTRAINT unique_star_gift_released_by_check CHECK (
(released_by_peer_type IS NULL AND released_by_peer_id IS NULL) OR
(released_by_peer_type IN ('user','channel') AND released_by_peer_id > 0)
),
ADD CONSTRAINT unique_star_gift_theme_peer_check CHECK (
(theme_peer_type IS NULL AND theme_peer_id IS NULL) OR
(theme_peer_type IN ('user','channel') AND theme_peer_id > 0)
),
ADD CONSTRAINT unique_star_gift_host_peer_check CHECK (
(host_peer_type IS NULL AND host_peer_id IS NULL) OR
(host_peer_type IN ('user','channel') AND host_peer_id > 0)
),
ADD CONSTRAINT unique_star_gift_value_check CHECK (
value_amount >= 0 AND value_usd_amount >= 0 AND offer_min_stars >= 0 AND
craft_chance_permille BETWEEN 0 AND 1000 AND last_sale_date >= 0 AND last_sale_amount >= 0 AND
((value_currency='' AND value_amount=0) OR value_currency<>'') AND
((last_sale_currency='' AND last_sale_amount=0 AND last_sale_date=0) OR
(last_sale_currency IN ('XTR','TON') AND last_sale_amount>0 AND last_sale_date>0)) AND
(NOT burned OR owner_address='') AND
((owner_address='' AND gift_address='') OR (owner_address<>'' AND gift_address<>''))
);
UPDATE public.unique_star_gifts u
SET original_owner_peer_type=p.owner_peer_type, original_owner_peer_id=p.owner_peer_id
FROM public.peer_star_gifts p WHERE p.id=u.source_saved_gift_id;
ALTER TABLE public.unique_star_gifts
ALTER COLUMN original_owner_peer_type SET NOT NULL,
ALTER COLUMN original_owner_peer_id SET NOT NULL,
ADD CONSTRAINT unique_star_gift_original_owner_check CHECK (
original_owner_peer_type IN ('user','channel') AND original_owner_peer_id>0
);
CREATE TABLE public.star_gift_listings (
unique_gift_id bigint PRIMARY KEY REFERENCES public.unique_star_gifts(id) ON DELETE RESTRICT,
seller_peer_type text NOT NULL,
seller_peer_id bigint NOT NULL,
currency text NOT NULL,
amount bigint NOT NULL,
listed_at integer NOT NULL,
updated_at integer NOT NULL,
version bigint DEFAULT 1 NOT NULL,
CONSTRAINT star_gift_listing_seller_check CHECK (seller_peer_type IN ('user','channel') AND seller_peer_id>0),
CONSTRAINT star_gift_listing_amount_check CHECK (currency IN ('XTR','TON') AND amount>0 AND listed_at>0 AND updated_at>=listed_at)
);
CREATE INDEX star_gift_listings_gift_price_idx ON public.star_gift_listings(currency, amount, unique_gift_id);
CREATE INDEX star_gift_listings_updated_idx ON public.star_gift_listings(updated_at DESC, unique_gift_id DESC);
CREATE TABLE public.star_gift_sales (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
unique_gift_id bigint NOT NULL REFERENCES public.unique_star_gifts(id) ON DELETE RESTRICT,
seller_peer_type text NOT NULL,
seller_peer_id bigint NOT NULL,
buyer_peer_type text NOT NULL,
buyer_peer_id bigint NOT NULL,
currency text NOT NULL,
amount bigint NOT NULL,
commission_amount bigint DEFAULT 0 NOT NULL,
sold_at integer NOT NULL,
command_key text NOT NULL,
CONSTRAINT star_gift_sales_command_uniq UNIQUE(command_key),
CONSTRAINT star_gift_sales_peer_check CHECK (
seller_peer_type IN ('user','channel') AND seller_peer_id>0 AND
buyer_peer_type IN ('user','channel') AND buyer_peer_id>0),
CONSTRAINT star_gift_sales_amount_check CHECK (
currency IN ('XTR','TON') AND amount>0 AND commission_amount>=0 AND commission_amount<=amount AND sold_at>0)
);
CREATE INDEX star_gift_sales_unique_date_idx ON public.star_gift_sales(unique_gift_id, sold_at DESC);
CREATE TABLE public.star_gift_offers (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
buyer_user_id bigint NOT NULL,
owner_peer_type text NOT NULL,
owner_peer_id bigint NOT NULL,
unique_gift_id bigint NOT NULL REFERENCES public.unique_star_gifts(id) ON DELETE RESTRICT,
currency text NOT NULL,
amount bigint NOT NULL,
random_id bigint NOT NULL,
offer_msg_id integer DEFAULT 0 NOT NULL,
buyer_msg_id integer DEFAULT 0 NOT NULL,
status text DEFAULT 'pending' NOT NULL,
created_at integer NOT NULL,
expires_at integer NOT NULL,
resolved_at integer DEFAULT 0 NOT NULL,
balance_after bigint DEFAULT 0 NOT NULL,
expiry_notified boolean DEFAULT false NOT NULL,
CONSTRAINT star_gift_offer_random_uniq UNIQUE(buyer_user_id, random_id),
CONSTRAINT star_gift_offer_owner_msg_uniq UNIQUE(owner_peer_type, owner_peer_id, offer_msg_id),
CONSTRAINT star_gift_offer_peer_check CHECK (buyer_user_id>0 AND owner_peer_type IN ('user','channel') AND owner_peer_id>0),
CONSTRAINT star_gift_offer_amount_check CHECK (currency IN ('XTR','TON') AND amount>0),
CONSTRAINT star_gift_offer_status_check CHECK (status IN ('pending','accepted','declined','expired','cancelled')),
CONSTRAINT star_gift_offer_time_check CHECK (created_at>0 AND expires_at>created_at AND resolved_at>=0 AND
((status='pending' AND resolved_at=0) OR (status<>'pending' AND resolved_at>=created_at)))
);
CREATE INDEX star_gift_offers_pending_expiry_idx ON public.star_gift_offers(expires_at, id) WHERE status='pending';
CREATE INDEX star_gift_offers_unique_pending_idx ON public.star_gift_offers(unique_gift_id, id) WHERE status='pending';
CREATE TABLE public.star_gift_transfer_commands (
actor_user_id bigint NOT NULL,
command_key text NOT NULL,
unique_gift_id bigint NOT NULL REFERENCES public.unique_star_gifts(id) ON DELETE RESTRICT,
from_peer_type text NOT NULL,
from_peer_id bigint NOT NULL,
to_peer_type text NOT NULL,
to_peer_id bigint NOT NULL,
charge_stars bigint DEFAULT 0 NOT NULL,
balance_after bigint DEFAULT 0 NOT NULL,
created_at integer NOT NULL,
CONSTRAINT star_gift_transfer_commands_pkey PRIMARY KEY(actor_user_id, command_key),
CONSTRAINT star_gift_transfer_command_peer_check CHECK (
actor_user_id>0 AND from_peer_type IN ('user','channel') AND from_peer_id>0 AND
to_peer_type IN ('user','channel') AND to_peer_id>0 AND charge_stars>=0 AND created_at>0)
);
CREATE TABLE public.star_gift_purchase_commands (
buyer_user_id bigint NOT NULL,
command_key text NOT NULL,
gift_id bigint NOT NULL REFERENCES public.star_gift_catalog(gift_id) ON DELETE RESTRICT,
recipient_peer_type text NOT NULL,
recipient_peer_id bigint NOT NULL,
saved_gift_id bigint NOT NULL REFERENCES public.peer_star_gifts(id) ON DELETE RESTRICT,
form_id bigint NOT NULL,
charge_stars bigint NOT NULL,
balance_after bigint NOT NULL,
created_at integer NOT NULL,
CONSTRAINT star_gift_purchase_commands_pkey PRIMARY KEY(buyer_user_id,command_key),
CONSTRAINT star_gift_purchase_commands_form_uniq UNIQUE(buyer_user_id,form_id),
CONSTRAINT star_gift_purchase_command_shape_check CHECK (
buyer_user_id>0 AND recipient_peer_type IN ('user','channel') AND recipient_peer_id>0 AND
form_id>0 AND charge_stars>0 AND balance_after>=0 AND created_at>0)
);
CREATE TABLE public.star_gift_prepaid_upgrade_commands (
payer_user_id bigint NOT NULL,
command_key text NOT NULL,
saved_gift_id bigint NOT NULL REFERENCES public.peer_star_gifts(id) ON DELETE RESTRICT,
form_id bigint NOT NULL,
charge_stars bigint NOT NULL,
balance_after bigint NOT NULL,
created_at integer NOT NULL,
CONSTRAINT star_gift_prepaid_upgrade_commands_pkey PRIMARY KEY(payer_user_id, command_key),
CONSTRAINT star_gift_prepaid_upgrade_commands_form_uniq UNIQUE(payer_user_id, form_id),
CONSTRAINT star_gift_prepaid_upgrade_command_shape_check CHECK (
payer_user_id>0 AND form_id>0 AND charge_stars>0 AND balance_after>=0 AND created_at>0)
);
CREATE TABLE public.star_gift_drop_details_commands (
user_id bigint NOT NULL,
command_key text NOT NULL,
saved_gift_id bigint NOT NULL REFERENCES public.peer_star_gifts(id) ON DELETE RESTRICT,
unique_gift_id bigint NOT NULL REFERENCES public.unique_star_gifts(id) ON DELETE RESTRICT,
form_id bigint NOT NULL,
charge_stars bigint NOT NULL,
balance_after bigint NOT NULL,
created_at integer NOT NULL,
CONSTRAINT star_gift_drop_details_commands_pkey PRIMARY KEY(user_id, command_key),
CONSTRAINT star_gift_drop_details_commands_form_uniq UNIQUE(user_id, form_id),
CONSTRAINT star_gift_drop_details_command_shape_check CHECK (
user_id>0 AND form_id>0 AND charge_stars>0 AND balance_after>=0 AND created_at>0)
);
CREATE TABLE public.star_gift_craft_commands (
user_id bigint NOT NULL,
command_key text NOT NULL,
input_unique_gift_ids bigint[] NOT NULL,
gift_id bigint NOT NULL REFERENCES public.star_gift_catalog(gift_id) ON DELETE RESTRICT,
success boolean NOT NULL,
result_unique_gift_id bigint REFERENCES public.unique_star_gifts(id) ON DELETE RESTRICT,
chance_permille integer NOT NULL,
created_at integer NOT NULL,
CONSTRAINT star_gift_craft_commands_pkey PRIMARY KEY(user_id, command_key),
CONSTRAINT star_gift_craft_shape_check CHECK (
user_id>0 AND cardinality(input_unique_gift_ids) BETWEEN 1 AND 4 AND
chance_permille BETWEEN 0 AND 1000 AND created_at>0 AND
((success AND result_unique_gift_id IS NOT NULL) OR (NOT success AND result_unique_gift_id IS NULL)))
);
CREATE TABLE public.star_gift_notification_settings (
user_id bigint NOT NULL,
channel_id bigint NOT NULL,
enabled boolean DEFAULT true NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT star_gift_notification_settings_pkey PRIMARY KEY(user_id, channel_id),
CONSTRAINT star_gift_notification_settings_peer_check CHECK (user_id>0 AND channel_id>0)
);
CREATE TABLE public.star_gift_withdrawal_requests (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
unique_gift_id bigint NOT NULL REFERENCES public.unique_star_gifts(id) ON DELETE RESTRICT,
owner_user_id bigint NOT NULL,
provider text NOT NULL,
provider_request_id text NOT NULL,
url text NOT NULL,
status text DEFAULT 'pending' NOT NULL,
created_at integer NOT NULL,
expires_at integer NOT NULL,
completed_at integer DEFAULT 0 NOT NULL,
CONSTRAINT star_gift_withdrawal_request_unique UNIQUE(unique_gift_id),
CONSTRAINT star_gift_withdrawal_provider_request_uniq UNIQUE(provider, provider_request_id),
CONSTRAINT star_gift_withdrawal_shape_check CHECK (
owner_user_id>0 AND provider<>'' AND provider_request_id<>'' AND url<>'' AND
status IN ('pending','completed','failed') AND created_at>0 AND expires_at>created_at AND completed_at>=0)
);
CREATE TABLE public.star_gift_auctions (
gift_id bigint PRIMARY KEY REFERENCES public.star_gift_catalog(gift_id) ON DELETE RESTRICT,
slug text NOT NULL UNIQUE,
version integer DEFAULT 1 NOT NULL,
start_date integer NOT NULL,
end_date integer NOT NULL,
round_duration integer NOT NULL,
gifts_per_round integer NOT NULL,
total_rounds integer NOT NULL,
current_round integer DEFAULT 0 NOT NULL,
next_round_at integer NOT NULL,
last_gift_num integer DEFAULT 0 NOT NULL,
gifts_left integer NOT NULL,
min_bid_amount bigint NOT NULL,
status text DEFAULT 'pending' NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT star_gift_auction_shape_check CHECK (
version>0 AND start_date>0 AND end_date>start_date AND round_duration>0 AND
gifts_per_round>0 AND total_rounds>0 AND current_round BETWEEN 0 AND total_rounds AND
next_round_at>=start_date AND last_gift_num>=0 AND gifts_left>=0 AND min_bid_amount>0 AND
status IN ('pending','active','completed','cancelled'))
);
CREATE TABLE public.star_gift_auction_bids (
gift_id bigint NOT NULL REFERENCES public.star_gift_auctions(gift_id) ON DELETE RESTRICT,
bidder_user_id bigint NOT NULL,
recipient_peer_type text NOT NULL,
recipient_peer_id bigint NOT NULL,
amount bigint NOT NULL,
bid_date integer NOT NULL,
hide_name boolean DEFAULT false NOT NULL,
message text DEFAULT '' NOT NULL,
returned boolean DEFAULT false NOT NULL,
acquired_count integer DEFAULT 0 NOT NULL,
active boolean DEFAULT true NOT NULL,
version bigint DEFAULT 1 NOT NULL,
CONSTRAINT star_gift_auction_bids_pkey PRIMARY KEY(gift_id, bidder_user_id),
CONSTRAINT star_gift_auction_bid_peer_check CHECK (
bidder_user_id>0 AND recipient_peer_type IN ('user','channel') AND recipient_peer_id>0),
CONSTRAINT star_gift_auction_bid_amount_check CHECK (amount>0 AND bid_date>0 AND acquired_count>=0 AND version>0)
);
CREATE INDEX star_gift_auction_bids_rank_idx ON public.star_gift_auction_bids(gift_id, amount DESC, bid_date, bidder_user_id) WHERE active;
CREATE TABLE public.star_gift_auction_bid_payments (
user_id bigint NOT NULL,
form_id bigint NOT NULL,
gift_id bigint NOT NULL REFERENCES public.star_gift_auctions(gift_id) ON DELETE RESTRICT,
bid_amount bigint NOT NULL,
balance_after bigint NOT NULL,
created_at integer NOT NULL,
CONSTRAINT star_gift_auction_bid_payments_pkey PRIMARY KEY(user_id, form_id),
CONSTRAINT star_gift_auction_bid_payment_shape_check CHECK (
user_id>0 AND form_id>0 AND bid_amount>0 AND balance_after>=0 AND created_at>0)
);
CREATE TABLE public.star_gift_auction_acquired (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
gift_id bigint NOT NULL REFERENCES public.star_gift_auctions(gift_id) ON DELETE RESTRICT,
bidder_user_id bigint NOT NULL,
recipient_peer_type text NOT NULL,
recipient_peer_id bigint NOT NULL,
saved_gift_id bigint REFERENCES public.peer_star_gifts(id) ON DELETE RESTRICT,
bid_amount bigint NOT NULL,
round integer NOT NULL,
pos integer NOT NULL,
gift_num integer,
acquired_at integer NOT NULL,
hide_name boolean DEFAULT false NOT NULL,
message text DEFAULT '' NOT NULL,
CONSTRAINT star_gift_auction_acquired_round_pos_uniq UNIQUE(gift_id, round, pos),
CONSTRAINT star_gift_auction_acquired_shape_check CHECK (
bidder_user_id>0 AND recipient_peer_type IN ('user','channel') AND recipient_peer_id>0 AND
bid_amount>0 AND round>0 AND pos>0 AND acquired_at>0 AND (gift_num IS NULL OR gift_num>0))
);
CREATE INDEX star_gift_auction_acquired_user_idx ON public.star_gift_auction_acquired(bidder_user_id, gift_id, id);
CREATE TABLE public.ton_balances (
user_id bigint PRIMARY KEY,
balance_nanoton bigint DEFAULT 0 NOT NULL,
granted boolean DEFAULT false NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT ton_balances_check CHECK (user_id>0 AND balance_nanoton>=0)
);
CREATE TABLE public.ton_transactions (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
user_id bigint NOT NULL,
amount_nanoton bigint NOT NULL,
reason text NOT NULL,
peer_type text,
peer_id bigint,
gift_id bigint,
date integer NOT NULL,
CONSTRAINT ton_transaction_amount_check CHECK (user_id>0 AND amount_nanoton<>0 AND date>0),
CONSTRAINT ton_transaction_peer_check CHECK (
(peer_type IS NULL AND peer_id IS NULL) OR (peer_type IN ('user','channel') AND peer_id>0))
);
CREATE INDEX ton_transactions_user_idx ON public.ton_transactions(user_id, id DESC);
CREATE FUNCTION public.telesrv_guard_star_gift_listing() RETURNS trigger LANGUAGE plpgsql AS $$
DECLARE
gift_owner_type text;
gift_owner_id bigint;
gift_burned boolean;
BEGIN
SELECT owner_peer_type, owner_peer_id, burned
INTO gift_owner_type, gift_owner_id, gift_burned
FROM public.unique_star_gifts WHERE id=NEW.unique_gift_id FOR SHARE;
IF gift_burned OR gift_owner_type IS DISTINCT FROM NEW.seller_peer_type OR gift_owner_id IS DISTINCT FROM NEW.seller_peer_id THEN
RAISE EXCEPTION 'star gift listing owner/state mismatch';
END IF;
RETURN NEW;
END;
$$;
CREATE TRIGGER star_gift_listing_guard BEFORE INSERT OR UPDATE ON public.star_gift_listings
FOR EACH ROW EXECUTE FUNCTION public.telesrv_guard_star_gift_listing();
CREATE FUNCTION public.telesrv_check_unique_star_gift_owner() RETURNS trigger LANGUAGE plpgsql AS $$
DECLARE
unique_id bigint;
gift_owner_type text;
gift_owner_id bigint;
gift_owner_address text;
gift_burned boolean;
saved_status text;
saved_owner_type text;
saved_owner_id bigint;
BEGIN
IF TG_TABLE_NAME = 'unique_star_gifts' THEN
unique_id := COALESCE(NEW.id, OLD.id);
ELSE
unique_id := COALESCE(NEW.unique_gift_id, OLD.unique_gift_id);
END IF;
IF unique_id IS NULL THEN RETURN NULL; END IF;
SELECT owner_peer_type, owner_peer_id, owner_address, burned
INTO gift_owner_type, gift_owner_id, gift_owner_address, gift_burned
FROM public.unique_star_gifts WHERE id=unique_id;
IF NOT FOUND THEN RETURN NULL; END IF;
SELECT lifecycle_status, owner_peer_type, owner_peer_id
INTO saved_status, saved_owner_type, saved_owner_id
FROM public.peer_star_gifts WHERE unique_gift_id=unique_id;
IF NOT FOUND THEN RAISE EXCEPTION 'unique star gift missing saved aggregate'; END IF;
IF gift_burned THEN
IF saved_status <> 'burned' THEN RAISE EXCEPTION 'burned unique star gift has live saved aggregate'; END IF;
ELSIF gift_owner_address <> '' THEN
IF saved_status <> 'exported' THEN RAISE EXCEPTION 'exported unique star gift has non-exported saved aggregate'; END IF;
ELSIF saved_status <> 'active' OR gift_owner_type IS DISTINCT FROM saved_owner_type OR gift_owner_id IS DISTINCT FROM saved_owner_id THEN
RAISE EXCEPTION 'unique star gift owner mismatch';
END IF;
RETURN NULL;
END;
$$;
CREATE CONSTRAINT TRIGGER unique_star_gift_owner_guard
AFTER INSERT OR UPDATE ON public.unique_star_gifts DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW EXECUTE FUNCTION public.telesrv_check_unique_star_gift_owner();
CREATE CONSTRAINT TRIGGER peer_unique_star_gift_owner_guard
AFTER INSERT OR UPDATE ON public.peer_star_gifts DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW WHEN (NEW.unique_gift_id IS NOT NULL)
EXECUTE FUNCTION public.telesrv_check_unique_star_gift_owner();