-
Notifications
You must be signed in to change notification settings - Fork 7
PostgreSQL support: a dialect-aware handle, a PostgreSQL migration set, and the test suite on both engines #29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
distronode-com
wants to merge
17
commits into
Calnode:main
Choose a base branch
from
distronode-com:pr/postgres
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
f685ced
db: dialect-aware Open plus a rebinding DB/Tx wrapper
distronode-com dead65e
db: PostgreSQL migration set, generated once from the SQLite one
distronode-com 65099c3
db: compare the migrated schemas across both engines
distronode-com dfd7bd7
db: thread *db.DB / *db.Tx through every call site
distronode-com 62dc81e
db: port the SQL that only SQLite accepts
distronode-com e675520
booking: hold an advisory lock on the host across the overlap check
distronode-com c1478c1
test,docs,ci: run the suite against PostgreSQL
distronode-com 78dab82
db: classify constraint violations by code, not by English message
distronode-com 4b86071
db: the last four things only SQLite accepted
distronode-com 9c7792e
db: classify constraint violations by SQLite's codes too, not its prose
distronode-com 26f17b6
docs: record the SQLite error-code correction on the branch log
distronode-com cf972cf
db: pin every TEXT timestamp column to COLLATE "C"
distronode-com a1aa94b
handler: verify RETURNING position on both engines
distronode-com dce3568
db: delete the bare Open, readiness goes through the handle
distronode-com 5297bbf
db,config: make the Postgres pool size configurable
distronode-com d18bfab
docs: record Boundary 7 on the branch log
distronode-com 5c9e5c8
chore: drop the fork's branch log from the pull request
distronode-com File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
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
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
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
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
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| package booking_test | ||
|
|
||
| import ( | ||
| "context" | ||
| "errors" | ||
| "sync" | ||
| "testing" | ||
| "time" | ||
|
|
||
| "github.com/calnode/calnode/internal/booking" | ||
| "github.com/calnode/calnode/internal/dbtest" | ||
| ) | ||
|
|
||
| // TestCreate_concurrentPartialOverlap_postgres is the test for the guarantee | ||
| // pg_advisory_xact_lock replaces. | ||
| // | ||
| // The two slots deliberately OVERLAP without SHARING a start time. That is the case | ||
| // no index catches: idx_bookings_no_double is UNIQUE(host_id, start_at), so 10:00 | ||
| // and 10:15 are two distinct keys and both inserts satisfy it. The only thing | ||
| // standing between them is hostBusy's read, and on a multi-connection pool two | ||
| // transactions can both take that read before either writes. If the lock is not | ||
| // held, this test double-books. | ||
| // | ||
| // It is skipped on SQLite, where the race is not reachable: db.SetMaxOpenConns(1) | ||
| // means the second transaction cannot begin until the first has committed. | ||
| func TestCreate_concurrentPartialOverlap_postgres(t *testing.T) { | ||
| database := dbtest.RequirePostgres(t) | ||
| svc := booking.New(database) | ||
| hostID := seedHost(t, database) | ||
| etID := seedEventType(t, database, hostID) | ||
|
|
||
| // Enough rounds that a lost race is very unlikely to go unseen. One round is | ||
| // not evidence: two goroutines miss each other's window often enough that an | ||
| // unlocked build passes a single round most of the time. | ||
| const rounds = 40 | ||
|
|
||
| for round := 0; round < rounds; round++ { | ||
| // A fresh, non-overlapping window per round, so a round is independent of | ||
| // every earlier round's surviving booking. | ||
| base := slot(0, 0).Add(time.Duration(round) * time.Hour) | ||
| first := [2]time.Time{base, base.Add(30 * time.Minute)} | ||
| second := [2]time.Time{base.Add(15 * time.Minute), base.Add(45 * time.Minute)} | ||
|
|
||
| var wg sync.WaitGroup | ||
| errs := make([]error, 2) | ||
| start := make(chan struct{}) | ||
| for i, window := range [2][2]time.Time{first, second} { | ||
| wg.Add(1) | ||
| go func(i int, from, to time.Time) { | ||
| defer wg.Done() | ||
| <-start // line both up so the transactions truly overlap | ||
| _, errs[i] = svc.Create(context.Background(), booking.CreateParams{ | ||
| EventTypeID: etID, | ||
| HostIDs: []string{hostID}, | ||
| StartAt: from, | ||
| EndAt: to, | ||
| Organizer: booking.Attendee{ | ||
| Name: "Alice", | ||
| Email: "alice@example.com", | ||
| }, | ||
| }) | ||
| }(i, window[0], window[1]) | ||
| } | ||
| close(start) | ||
| wg.Wait() | ||
|
|
||
| var created int | ||
| for i, err := range errs { | ||
| switch { | ||
| case err == nil: | ||
| created++ | ||
| case errors.Is(err, booking.ErrDoubleBooked): | ||
| // the expected loser | ||
| default: | ||
| t.Fatalf("round %d: booking %d: unexpected error: %v", round, i, err) | ||
| } | ||
| } | ||
| if created != 1 { | ||
| t.Fatalf("round %d: %d of 2 overlapping bookings were created; want exactly 1 (errors: %v, %v)", | ||
| round, created, errs[0], errs[1]) | ||
| } | ||
| } | ||
|
|
||
| // And the database agrees: one booking per round, never two in a window. | ||
| var n int | ||
| if err := database.QueryRow( | ||
| `SELECT COUNT(*) FROM bookings WHERE host_id = ? AND status != 'cancelled'`, hostID).Scan(&n); err != nil { | ||
| t.Fatalf("count bookings: %v", err) | ||
| } | ||
| if n != rounds { | ||
| t.Errorf("bookings for host = %d; want %d (one per round)", n, rounds) | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Postgres is documented here, but
LITESTREAM_REPLICA_URLtwo rows down is still unqualified “recommended,” and the image entrypoint always runs Litestream against/data/calnode.db. Please spell out that Litestream is SQLite-only and thatpostgres://deployments need native Postgres backups (or skip Litestream when the URL scheme is postgres).Technical details