027_usage_billing_consistency.sql 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
  1. -- 027_usage_billing_consistency.sql
  2. -- Ensure usage_logs idempotency (request_id, api_key_id) and add reconciliation infrastructure.
  3. -- -----------------------------------------------------------------------------
  4. -- 1) Normalize legacy request_id values
  5. -- -----------------------------------------------------------------------------
  6. -- Historically request_id may be inserted as empty string. Convert it to NULL so
  7. -- the upcoming unique index does not break on repeated "" values.
  8. UPDATE usage_logs
  9. SET request_id = NULL
  10. WHERE request_id = '';
  11. -- If duplicates already exist for the same (request_id, api_key_id), keep the
  12. -- first row and NULL-out request_id for the rest so the unique index can be
  13. -- created without deleting historical logs.
  14. WITH ranked AS (
  15. SELECT
  16. id,
  17. ROW_NUMBER() OVER (PARTITION BY api_key_id, request_id ORDER BY id) AS rn
  18. FROM usage_logs
  19. WHERE request_id IS NOT NULL
  20. )
  21. UPDATE usage_logs ul
  22. SET request_id = NULL
  23. FROM ranked r
  24. WHERE ul.id = r.id
  25. AND r.rn > 1;
  26. -- -----------------------------------------------------------------------------
  27. -- 2) Idempotency constraint for usage_logs
  28. -- -----------------------------------------------------------------------------
  29. CREATE UNIQUE INDEX IF NOT EXISTS idx_usage_logs_request_id_api_key_unique
  30. ON usage_logs (request_id, api_key_id);
  31. -- -----------------------------------------------------------------------------
  32. -- 3) Reconciliation infrastructure: billing ledger for usage charges
  33. -- -----------------------------------------------------------------------------
  34. CREATE TABLE IF NOT EXISTS billing_usage_entries (
  35. id BIGSERIAL PRIMARY KEY,
  36. usage_log_id BIGINT NOT NULL REFERENCES usage_logs(id) ON DELETE CASCADE,
  37. user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  38. api_key_id BIGINT NOT NULL REFERENCES api_keys(id) ON DELETE CASCADE,
  39. subscription_id BIGINT REFERENCES user_subscriptions(id) ON DELETE SET NULL,
  40. billing_type SMALLINT NOT NULL,
  41. applied BOOLEAN NOT NULL DEFAULT TRUE,
  42. delta_usd DECIMAL(20, 10) NOT NULL DEFAULT 0,
  43. created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
  44. );
  45. CREATE UNIQUE INDEX IF NOT EXISTS billing_usage_entries_usage_log_id_unique
  46. ON billing_usage_entries (usage_log_id);
  47. CREATE INDEX IF NOT EXISTS idx_billing_usage_entries_user_time
  48. ON billing_usage_entries (user_id, created_at);
  49. CREATE INDEX IF NOT EXISTS idx_billing_usage_entries_created_at
  50. ON billing_usage_entries (created_at);