schema / migration / 016-booking-exclusion-index-hygiene

016 - Anti double-booking + index hygiene + ksrdb users

  • Tanggal: 2026-08-11
  • DB: mpcdb + ksrdb
  • Ringkas: Exclusion constraint anti double-booking (btree_gist); CHECK stok >= 0; index bookings(court_id, start_time); drop 2 index redundant; add FK indexes yang tersisa; rename ksrdb.owner_usersusers.

ALTER mpcdb (jalankan di DB server)

-- =========================================================
-- 1) Anti double-booking: DB-level exclusion constraint
-- =========================================================
CREATE EXTENSION IF NOT EXISTS btree_gist;

ALTER TABLE bookings DROP CONSTRAINT IF EXISTS bookings_no_overlap;
ALTER TABLE bookings ADD CONSTRAINT bookings_no_overlap
  EXCLUDE USING gist (
    court_id WITH =,
    tsrange(start_time, end_time) WITH &&
  )
  WHERE (status NOT IN ('Cancelled', 'Canceled') AND deleted_at IS NULL);

-- =========================================================
-- 2) Stock can never go negative (backstop for atomic decrement)
-- =========================================================
ALTER TABLE products DROP CONSTRAINT IF EXISTS products_stock_nonneg;
ALTER TABLE products ADD CONSTRAINT products_stock_nonneg CHECK (stock_quantity >= 0);

-- =========================================================
-- 3) Availability / conflict-check composite index
-- =========================================================
CREATE INDEX IF NOT EXISTS idx_bookings_court_time ON bookings(court_id, start_time);

-- =========================================================
-- 4) Drop redundant indexes
-- =========================================================
-- uni_coaches_user_id adalah unique CONSTRAINT (bukan index lepas)
ALTER TABLE staff_coaches DROP CONSTRAINT IF EXISTS uni_coaches_user_id; -- covered by partial unique idx_staff_coaches_staff_id
DROP INDEX IF EXISTS idx_favorites_customer_id;    -- covered by idx_customer_target(customer_id, ...)

-- =========================================================
-- 5) Remaining FK indexes
-- =========================================================
CREATE INDEX IF NOT EXISTS idx_notifications_customer_id ON notifications(customer_id);
CREATE INDEX IF NOT EXISTS idx_subscriptions_customer_id ON subscriptions(customer_id);
CREATE INDEX IF NOT EXISTS idx_payment_events_transaction_id ON payment_events(transaction_id);
CREATE INDEX IF NOT EXISTS idx_stock_ledger_product_id ON stock_ledger(product_id);
CREATE INDEX IF NOT EXISTS idx_voucher_redemptions_transaction_id ON voucher_redemptions(transaction_id);
CREATE INDEX IF NOT EXISTS idx_voucher_redemptions_voucher_id ON voucher_redemptions(voucher_id);

ANALYZE bookings;
ANALYZE products;

ALTER ksrdb (jalankan di DB server)

ALTER TABLE owner_users RENAME TO users;
ALTER INDEX IF EXISTS owner_users_email_key RENAME TO users_email_key;
ALTER INDEX IF EXISTS owner_users_pkey RENAME TO users_pkey;

Rollback

-- mpcdb
ALTER TABLE bookings DROP CONSTRAINT IF EXISTS bookings_no_overlap;
ALTER TABLE products DROP CONSTRAINT IF EXISTS products_stock_nonneg;
DROP INDEX IF EXISTS idx_bookings_court_time;
-- ksrdb
ALTER TABLE users RENAME TO owner_users;
ALTER INDEX IF EXISTS users_email_key RENAME TO owner_users_email_key;
ALTER INDEX IF EXISTS users_pkey RENAME TO owner_users_pkey;

Catatan

  • Constraint bookings_no_overlap membuat DB menolak dua booking overlap di court sama, apapun kondisi race di aplikasi. Booking Cancelled/soft-deleted tidak memblok slot.
  • mpc-be menangkap error constraint ini → response "slot taken" (lihat be-hardening di spec 020).
  • products_stock_nonneg = jaring pengaman; decrement stok di kode tetap pakai conditional update.
  • Rename owner_usersusers: Prisma mpc-owner & ksr-sync ganti @@map saja; model tetap OwnerUser.