-- Overlap prevention for reservations using btree_gist exclusion constraint create extension if not exists btree_gist; alter table public.reservations add constraint no_overlapping_reservations exclude using gist ( boat_id with =, tstzrange(start_time, end_time, '[)') with && ); -- Function: check member has required certs for a boat create or replace function public.member_has_cert_for_boat(p_user_id uuid, p_boat_id uuid) returns boolean language sql stable security definer set search_path = public as $$ select case when array_length(b.required_certs, 1) is null or array_length(b.required_certs, 1) = 0 then true else coalesce( (select m.certifications @> b.required_certs from public.members m where m.user_id = p_user_id), false ) end from public.boats b where b.id = p_boat_id; $$; -- Trigger: enforce cert check on reservation insert create or replace function public.enforce_reservation_cert_check() returns trigger language plpgsql security definer set search_path = public as $$ begin if not coalesce(public.member_has_cert_for_boat(new.user_id, new.boat_id), false) then raise exception 'Member does not have required certifications for this boat'; end if; return new; end; $$; create trigger check_reservation_certs before insert on public.reservations for each row execute function public.enforce_reservation_cert_check(); grant execute on function public.member_has_cert_for_boat(uuid, uuid) to authenticated;