fix(equipment): DB-enforced reservation no-overlap (D1, P0)

reserveEquipment was check-then-insert with no DB guard: two concurrent
requests both passed the in-memory overlap check and double-booked a
machine. Migration 042 adds btree_gist + a GiST EXCLUDE constraint
(equipment_id =, tstzrange && ) WHERE status='reserved', making the DB
the arbiter; the action maps the 23P01 violation to the existing
friendly overlap message. App-side check kept as a fast pre-check.
Schema + DB maps updated.
This commit is contained in:
Moheeb Zara
2026-05-18 18:26:19 -07:00
parent 247e966446
commit a513ad0fab
5 changed files with 49 additions and 1 deletions
+23
View File
@@ -0,0 +1,23 @@
-- =============================================================================
-- 042: Equipment reservation overlap — database-enforced (P0 fix)
-- =============================================================================
-- reserveEquipment() did a check-then-insert with no DB guard: two concurrent
-- requests both pass the in-memory overlap check and both insert, double-
-- booking a physical machine. A GiST exclusion constraint makes the database
-- the arbiter. Only 'reserved' rows conflict (cancelled/completed do not).
-- btree_gist provides the `uuid WITH =` operator class for the composite.
--
-- Idempotent: DROP CONSTRAINT IF EXISTS then ADD (ADD CONSTRAINT has no
-- IF NOT EXISTS form).
-- =============================================================================
CREATE EXTENSION IF NOT EXISTS btree_gist;
ALTER TABLE public.equipment_reservations
DROP CONSTRAINT IF EXISTS equipment_reservations_no_overlap;
ALTER TABLE public.equipment_reservations
ADD CONSTRAINT equipment_reservations_no_overlap
EXCLUDE USING gist (
equipment_id WITH =,
tstzrange(starts_at, ends_at, '[)') WITH &&
) WHERE (status = 'reserved');