feat(pgsql-test): deferredConstraints — run commit-time deferred constraint checks under rollback isolation - #1859
Merged
pyramation merged 1 commit intoSep 23, 2026
Conversation
…ed constraint checks under rollback isolation
Contributor
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
|
Review complete. No blocking issues — approved ✅; 1 nitpick below. 🧹 Nitpicks (1) — 🟢 1 low
This PR introduces an opt-in deferred-constraints checking mode for the pgsql-test harness: a new
Reviewed commit: 2ebafde |
pyramation
deleted the
devin/1790168515-pgsql-test-deferred-constraints
branch
September 23, 2026 23:31
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Rollback-based isolation (
BEGIN; SAVEPOINT … ROLLBACK TO SAVEPOINT; COMMIT) never commits, soDEFERRABLE INITIALLY DEFERREDconstraints (FK / UNIQUE / EXCLUDE /CONSTRAINT TRIGGER) — which Postgres only checks at COMMIT — never fire. A test can leave a dangling deferred FK and pass. This is inherent to every rollback harness (Rails, Django, Ecto…); this PR adds an opt-in way to close the hole without changing semantics inside the test.New option
db.deferredConstraints: 'off' | 'check' | 'immediate'(PgTestConnectionOptions, envDB_DEFERRED_CONSTRAINTS), flowing throughgetConnectionsto bothpganddbclients — and therefore tographile-test/graphql-test/graphql-server-testfor free.'off'(default) — unchanged behaviour. Fully backwards compatible.'check'—afterEach()issuesSET CONSTRAINTS ALL IMMEDIATEas the last statement before the rollback. Per the Postgres docs, switching DEFERRED→IMMEDIATE runs exactly the checks COMMIT would have run on the outstanding modifications, so deferral still works inside the test body but a pending violation fails the test. Caveats handled:25P02), the check is skipped so the original error isn't masked;[pgsql-test] deferred constraint violated at end of test (a real COMMIT would have failed here)and keeps the PGdetail/constraint name.'immediate'—beforeEach()(andpublish()) issueSET CONSTRAINTS ALL IMMEDIATE, so violations fail on the offending statement. Documented trade-off: disables deferral the code under test may rely on (child-before-parent, unique swaps).New public
db.checkConstraints()lets a test assert enforcement explicitly (await expect(db.checkConstraints()).rejects.toThrow(...)); once it throws the pending events are consumed andafterEachis clean.README gains a "Deferred constraints under rollback isolation" section covering the three modes, their trade-offs, and what rollback isolation still does not cover (NOTIFY, cross-session visibility →
publish()).Tests (
postgres-test.deferred-constraints.test.ts, 12 cases) cover all three modes incl. aborted-tx path,publish()re-application,checkConstraints()consumption, and the default being'off'.Follow-up decision: whether to flip the default to
'check'in a minor release.Link to Devin session: https://app.devin.ai/sessions/b842729ae6934f2093de8a43018c36fb
Open in Devin Desktop: https://app.devin.ai/desktop/session/b842729ae6934f2093de8a43018c36fb?variant=devin
Requested by: @pyramation