fix(db): add CHECK constraint for userId XOR guestToken invariant (#23) #26
|
|
@ -0,0 +1,17 @@
|
|||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
||||
-- Enforce the Order entity invariant at the database level: exactly one of
|
||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
||||
-- (user_id, guest_token) must be set — never both, never neither.
|
||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
||||
--
|
||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
||||
-- The Order Javadoc states "Either userId or guestToken is set; never both,
|
||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
||||
-- never neither", but previously only the @PrePersist lifecycle callback
|
||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
||||
-- enforced this in Java. A stray INSERT (admin tooling, manual SQL) could
|
||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
||||
-- violate it silently.
|
||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
||||
--
|
||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
||||
-- The CHECK expression (user_id IS NULL) <> (guest_token IS NULL) evaluates:
|
||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
||||
-- TRUE when exactly one column is NULL (the other is set) — allowed
|
||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
||||
-- FALSE when both are NULL or both are set — rejected
|
||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
||||
--
|
||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
||||
-- Standard SQL CHECK constraint, supported by both H2 (tests/dev) and
|
||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
||||
-- PostgreSQL (prod).
|
||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
||||
ALTER TABLE orders
|
||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
||||
ADD CONSTRAINT chk_user_or_guest
|
||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
||||
CHECK ((user_id IS NULL) <> (guest_token IS NULL));
|
||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
||||
💡 Suggestion (non-blocking): worth a
@DataJpaTestthat asserts this constraint rejects invalid rows — persist an Order with bothuserIdandguestTokenset, expecting aDataIntegrityViolationExceptionreferencingchk_user_or_guest. The@PrePersistinOrder.onCreate()already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.