mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-10-28 07:41:16 +00:00
Check-then-insert left a race: two concurrent requests for the same slot could both confirm. Migration 00014 adds three partial btree_gist exclusion constraints mirroring resourcesCollide semantics (same staff, same location, both-unassigned), excluding cancelled rows and class bookings. SQLSTATE 23P01 maps to ErrBookingConflict at all three write paths so a lost race returns a clean 409. Validated on a local Postgres 17 container: constraint matrix at SQL level, repo-level 23P01 detection, concurrent burst yielding exactly one confirmed booking. Authored By: TDvorak <[email protected]> Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
38 lines
1.6 KiB
SQL
38 lines
1.6 KiB
SQL
-- +goose Up
|
|
-- Hard guarantee against double-booking. The service layer checks for
|
|
-- collisions before inserting, but check-then-insert is racy under concurrent
|
|
-- requests; these exclusion constraints enforce the same rules atomically.
|
|
CREATE EXTENSION IF NOT EXISTS btree_gist;
|
|
|
|
-- Two appointments may not overlap for the same staff member.
|
|
ALTER TABLE bookings
|
|
ADD CONSTRAINT bookings_staff_no_overlap
|
|
EXCLUDE USING gist (
|
|
tenant_id WITH =,
|
|
staff_id WITH =,
|
|
tstzrange(starts_at, ends_at) WITH &&
|
|
) WHERE (booking_mode = 'appointment' AND status <> 'cancelled' AND staff_id IS NOT NULL);
|
|
|
|
-- Two appointments may not overlap in the same location.
|
|
ALTER TABLE bookings
|
|
ADD CONSTRAINT bookings_location_no_overlap
|
|
EXCLUDE USING gist (
|
|
tenant_id WITH =,
|
|
location_id WITH =,
|
|
tstzrange(starts_at, ends_at) WITH &&
|
|
) WHERE (booking_mode = 'appointment' AND status <> 'cancelled' AND location_id IS NOT NULL);
|
|
|
|
-- Appointments with no staff or location assignment occupy the tenant's
|
|
-- single implicit resource (mirrors resourcesCollide in the service layer).
|
|
ALTER TABLE bookings
|
|
ADD CONSTRAINT bookings_unassigned_no_overlap
|
|
EXCLUDE USING gist (
|
|
tenant_id WITH =,
|
|
tstzrange(starts_at, ends_at) WITH &&
|
|
) WHERE (booking_mode = 'appointment' AND status <> 'cancelled' AND staff_id IS NULL AND location_id IS NULL);
|
|
|
|
-- +goose Down
|
|
ALTER TABLE bookings DROP CONSTRAINT IF EXISTS bookings_unassigned_no_overlap;
|
|
ALTER TABLE bookings DROP CONSTRAINT IF EXISTS bookings_location_no_overlap;
|
|
ALTER TABLE bookings DROP CONSTRAINT IF EXISTS bookings_staff_no_overlap;
|