Skip to content

Commit 7dd9d6f

Browse files
committed
Support for database/sql in migrations + framework for multi-driver River
Here, add a new minimal driver called `riverdriver/riversql` that supports Go's built-in `database/sql` package, but only for purposes of migrations. The idea here is to fully complete #57 by providing a way of making `rivermigrate` interoperable with Go migration frameworks that support Go-based migrations like Goose, which provides hooks for `*sql.Tx` [1] rather than pgx. `riverdriver/riversql` is not a full driver and is only meant to be used with `rivermigrate`. We document this clearly in a number of places. To make a multi-driver world possible with River, we have to start the work of building a platform that does more than `riverpgxv5`'s "cheat" workaround. This works by having each driver implement specific database operations like `MigrationGetAll`, which target their wrapped database package of choice. This is accomplished by having each driver bundle in its own sqlc that targets its package. So `riverpgxv5` has an `sqlc.yaml` that targets `pgx/v5`, while `riversql` has one that targets `database/sql`. There's some `sqlc.yaml` duplication involved here, but luckily both drivers can share a `river_migration.sql` file that contains all queries involved, so you only need to change one place. `river_migration.sql` also migrates entirely out of the main `./internal/dbsqlc`. The idea here is that eventually `./internal/dbsqlc` will disappear completely, usurped entirely by driver-specific versions. As this is done, all references to `pgx` will disappear from the top-level package. There are some complications here to figure out like `LISTEN`/`NOTIFY` though, and I'm not clear whether `database/sql` could ever become a fully functional driver as it might be missing some needed functionality (e.g. subtransactions are still not supported after talking about them for ten f*ing years [2]. However, even if it's not, the system would let us support other fully functional packages or future major versions of pgx (or even past ones like `pgx/v4` if there's demand). `river/riverdriver` becomes a package as it now has types in it that need to be referenced by driver implementations, and this would otherwise not be possible without introducing a circular dependency. Notably, this development branch has to use some `go.mod` `replace` directives to demonstrate that it works correctly. If we go this direction, we'll need to break it into chunks to release it without them: 1. Break out changes to `river/riverdriver`. Tag and release it. 2. Break out changes to `riverdriver/river*` drivers. Have them target the release in (1), comment out `replace`s, then tag and release them. 3. Target the remaining River changes to the releases in (1) and (2), comment out `replace`s, then tag and release the top-level driver. Unfortunately future deep incisions to drivers will require similar gymnastics, but I don't think there's a way around it (we already have this process except it's currently two steps instead of three). The hope is that these will change relatively rarely, so it won't be too painful. [1] https://github.com/pressly/goose#go-migrations [2] golang/go#7898
1 parent 13ecbd4 commit 7dd9d6f

36 files changed

+1139
-143
lines changed

.github/workflows/ci.yml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ jobs:
7272
env:
7373
TEST_DATABASE_URL: postgres://postgres:[email protected]:5432/river_testdb?sslmode=disable
7474

75+
- name: Test riverdatabasesql
76+
working-directory: ./riverdriver/riverdatabasesql
77+
run: go test -race ./...
78+
7579
- name: Test riverpgxv5
7680
working-directory: ./riverdriver/riverpgxv5
7781
run: go test -race ./...
@@ -121,6 +125,15 @@ jobs:
121125
contents: read
122126
# allow read access to pull request. Use with `only-new-issues` option.
123127
pull-requests: read
128+
129+
strategy:
130+
matrix:
131+
submodule:
132+
- .
133+
- riverdriver
134+
- riverdriver/riverdatabasesql
135+
- riverdriver/riverpgxv5
136+
124137
steps:
125138
- uses: actions/setup-go@v4
126139
with:
@@ -137,6 +150,7 @@ jobs:
137150
only-new-issues: true
138151

139152
version: v1.55.2
153+
working-directory: ${{ matrix.submodule }}
140154

141155
producer_sample:
142156
runs-on: ubuntu-latest
@@ -204,7 +218,6 @@ jobs:
204218
sqlc-version: "1.22.0"
205219

206220
- name: Run sqlc diff
207-
working-directory: ./internal/dbsqlc
208221
run: |
209222
echo "Please make sure that all sqlc changes are checked in!"
210-
sqlc diff
223+
make verify

.golangci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ linters-settings:
6464
- Default
6565
- Prefix(github.com/riverqueue)
6666

67+
gomoddirectives:
68+
replace-local: true
69+
6770
gosec:
6871
excludes:
6972
- G404 # use of non-crypto random; overly broad for our use case

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Added `rivermigrate/riverdatabasesql` driver to enable River Go migrations through Go's built in `database/sql` package.
13+
1014
## [0.0.12] - 2023-12-02
1115

1216
### Added

Makefile

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,16 @@ generate: generate/sqlc
44

55
.PHONY: generate/sqlc
66
generate/sqlc:
7-
cd internal/dbsqlc && sqlc generate
7+
cd internal/dbsqlc && sqlc generate
8+
cd riverdriver/riverdatabasesql/internal/dbsqlc && sqlc generate
9+
cd riverdriver/riverpgxv5/internal/dbsqlc && sqlc generate
10+
11+
.PHONY: verify
12+
verify:
13+
verify: verify/sqlc
14+
15+
.PHONY: verify/sqlc
16+
verify/sqlc:
17+
cd internal/dbsqlc && sqlc diff
18+
cd riverdriver/riverdatabasesql/internal/dbsqlc && sqlc diff
19+
cd riverdriver/riverpgxv5/internal/dbsqlc && sqlc diff

docs/development.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ queries. After changing an sqlc `.sql` file, generate Go with:
3232
```shell
3333
git checkout master && git pull --rebase
3434
VERSION=v0.0.x
35+
git tag riverdriver/VERSION -m "release riverdriver/VERSION"
3536
git tag riverdriver/riverpgxv5/$VERSION -m "release riverdriver/riverpgxv5/$VERSION"
37+
git tag riverdriver/riverdatabasesql/$VERSION -m "release riverdriver/riverdatabasesql/$VERSION"
3638
git tag $VERSION
3739
git push --tags
3840
```

go.mod

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
module github.com/riverqueue/river
22

3-
go 1.21.0
3+
go 1.21.4
44

5-
// replace github.com/riverqueue/river/riverdriver/riverpgxv5 => ./riverdriver/riverpgxv5
5+
replace github.com/riverqueue/river/riverdriver => ./riverdriver
6+
7+
replace github.com/riverqueue/river/riverdriver/riverpgxv5 => ./riverdriver/riverpgxv5
8+
9+
replace github.com/riverqueue/river/riverdriver/riverdatabasesql => ./riverdriver/riverdatabasesql
610

711
require (
812
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa
913
github.com/jackc/pgx/v5 v5.5.0
1014
github.com/jackc/puddle/v2 v2.2.1
1115
github.com/oklog/ulid/v2 v2.1.0
16+
github.com/riverqueue/river/riverdriver v0.0.0-00010101000000-000000000000
1217
github.com/riverqueue/river/riverdriver/riverpgxv5 v0.0.12
18+
github.com/riverqueue/river/riverdriver/riverdatabasesql v0.0.0-00010101000000-000000000000
1319
github.com/robfig/cron/v3 v3.0.1
1420
github.com/spf13/cobra v1.8.0
1521
github.com/stretchr/testify v1.8.4
@@ -23,6 +29,7 @@ require (
2329
github.com/inconshreveable/mousetrap v1.1.0 // indirect
2430
github.com/jackc/pgpassfile v1.0.0 // indirect
2531
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
32+
github.com/lib/pq v1.10.9 // indirect
2633
github.com/pmezard/go-difflib v1.0.0 // indirect
2734
github.com/spf13/pflag v1.0.5 // indirect
2835
golang.org/x/crypto v0.15.0 // indirect

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
1818
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
1919
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
2020
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
21+
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
22+
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
2123
github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU=
2224
github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
2325
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
2426
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
2527
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
26-
github.com/riverqueue/river/riverdriver/riverpgxv5 v0.0.12 h1:mcDBnqwzEXY9WDOwbkd8xmFdSr/H6oHb1F3NCNCmLDY=
27-
github.com/riverqueue/river/riverdriver/riverpgxv5 v0.0.12/go.mod h1:k6hsPkW9Fl3qURzyLHbvxUCqWDpit0WrZ3oEaKezD3E=
2828
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
2929
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
3030
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=

internal/dbsqlc/models.go

Lines changed: 0 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/dbsqlc/sqlc.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@ sql:
44
queries:
55
- river_job.sql
66
- river_leader.sql
7-
- river_migration.sql
87
schema:
98
- river_job.sql
109
- river_leader.sql
11-
- river_migration.sql
1210
gen:
1311
go:
1412
package: "dbsqlc"

internal/riverinternaltest/riverinternaltest.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"log"
1010
"log/slog"
11+
"net/url"
1112
"os"
1213
"runtime"
1314
"sync"
@@ -52,19 +53,39 @@ func BaseServiceArchetype(tb testing.TB) *baseservice.Archetype {
5253
}
5354

5455
func DatabaseConfig(databaseName string) *pgxpool.Config {
55-
databaseURL := valutil.ValOrDefault(os.Getenv("TEST_DATABASE_URL"), "postgres:///river_testdb?sslmode=disable")
56-
57-
config, err := pgxpool.ParseConfig(databaseURL)
56+
config, err := pgxpool.ParseConfig(DatabaseURL(databaseName))
5857
if err != nil {
5958
panic(fmt.Sprintf("error parsing database URL: %v", err))
6059
}
6160
config.MaxConns = dbPoolMaxConns
6261
config.ConnConfig.ConnectTimeout = 10 * time.Second
63-
config.ConnConfig.Database = databaseName
6462
config.ConnConfig.RuntimeParams["timezone"] = "UTC"
6563
return config
6664
}
6765

66+
// DatabaseURL gets a test database URL from TEST_DATABASE_URL or falls back on
67+
// a default pointing to `river_testdb`. If databaseName is set, it replaces the
68+
// database in the URL, although the host and other parameters are preserved.
69+
//
70+
// Most of the time DatabaseConfig should be used instead of this function, but
71+
// it may be useful in non-pgx situations like for examples showing the use of
72+
// `database/sql`.
73+
func DatabaseURL(databaseName string) string {
74+
u, err := url.Parse(valutil.ValOrDefault(
75+
os.Getenv("TEST_DATABASE_URL"),
76+
"postgres://localhost/river_testdb?sslmode=disable"),
77+
)
78+
if err != nil {
79+
panic(err)
80+
}
81+
82+
if databaseName != "" {
83+
u.Path = databaseName
84+
}
85+
86+
return u.String()
87+
}
88+
6889
// DiscardContinuously drains continuously out of the given channel and discards
6990
// anything that comes out of it. Returns a stop function that should be invoked
7091
// to stop draining. Stop must be invoked before tests finish to stop an

0 commit comments

Comments
 (0)