Skip to content

Commit 71ba45f

Browse files
committed
Simplify migrations
This commit modifies how migrations occur within the package. Instead of a bunch of dependencies on golang-migrate everything can just be done with a single embedded sql file that creates a new schema and table, using idempotent statements like IF NOT EXISTS where applicable. Signed-off-by: David Bond <[email protected]>
1 parent 292479b commit 71ba45f

File tree

8 files changed

+37
-127
lines changed

8 files changed

+37
-127
lines changed

Makefile

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,2 @@
1-
migrate:
2-
go tool migrate create -dir migrations -ext sql $(name)
3-
41
test:
52
go test -race ./...

database.go

Lines changed: 24 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,10 @@ package pgfsm
33
import (
44
"context"
55
"database/sql"
6-
"embed"
6+
_ "embed"
77
"errors"
8-
"fmt"
9-
10-
"github.com/golang-migrate/migrate/v4"
11-
mpq "github.com/golang-migrate/migrate/v4/database/postgres"
12-
"github.com/golang-migrate/migrate/v4/source/iofs"
13-
)
14-
15-
var (
16-
//go:embed migrations/*.sql
17-
migrations embed.FS
8+
"log/slog"
9+
"strings"
1810
)
1911

2012
func transaction(ctx context.Context, db *sql.DB, fn func(ctx context.Context, tx *sql.Tx) error) error {
@@ -46,15 +38,15 @@ func insert(ctx context.Context, tx *sql.Tx, encoder Encoding, cmd Command) erro
4638
return err
4739
}
4840

49-
const q = `INSERT INTO pgfsm_command (kind, data) VALUES ($1, $2)`
41+
const q = `INSERT INTO pgfsm.command (kind, data) VALUES ($1, $2)`
5042

5143
_, err = tx.ExecContext(ctx, q, cmd.Kind(), data)
5244
return err
5345
}
5446

5547
func next(ctx context.Context, tx *sql.Tx) (int64, string, []byte, error) {
5648
const q = `
57-
SELECT id, kind, data FROM pgfsm_command
49+
SELECT id, kind, data FROM pgfsm.command
5850
ORDER BY id ASC
5951
FOR UPDATE SKIP LOCKED
6052
LIMIT 1
@@ -74,45 +66,33 @@ func next(ctx context.Context, tx *sql.Tx) (int64, string, []byte, error) {
7466
}
7567

7668
func remove(ctx context.Context, tx *sql.Tx, id int64) error {
77-
const q = `DELETE FROM pgfsm_command WHERE id = $1`
69+
const q = `DELETE FROM pgfsm.command WHERE id = $1`
7870

7971
_, err := tx.ExecContext(ctx, q, id)
8072
return err
8173
}
8274

83-
func migrateUp(db *sql.DB) error {
84-
source, err := iofs.New(migrations, "migrations")
85-
if err != nil {
86-
fmt.Println("source", err)
87-
return err
88-
}
75+
//go:embed migrate.sql
76+
var migration string
8977

90-
const (
91-
migrationsTable = "pgfsm_migration"
92-
)
78+
func migrateUp(ctx context.Context, db *sql.DB, logger *slog.Logger) error {
79+
logger.DebugContext(ctx, "performing migrations")
9380

94-
destination, err := mpq.WithInstance(db, &mpq.Config{
95-
MigrationsTable: migrationsTable,
96-
})
97-
if err != nil {
98-
fmt.Println("dest", err)
99-
return err
100-
}
81+
statements := strings.Split(migration, ";")
10182

102-
migration, err := migrate.NewWithInstance("iofs", source, "pgx", destination)
103-
if err != nil {
104-
fmt.Println("instance", err)
105-
return err
106-
}
83+
for _, statement := range statements {
84+
if strings.TrimSpace(statement) == "" {
85+
continue
86+
}
10787

108-
err = migration.Up()
109-
switch {
110-
case errors.Is(err, migrate.ErrNoChange):
111-
return nil
112-
case err != nil:
113-
fmt.Println("up", err)
114-
return err
115-
default:
116-
return nil
88+
logger.
89+
With(slog.String("statement", statement)).
90+
DebugContext(ctx, "executing statement")
91+
92+
if _, err := db.ExecContext(ctx, statement); err != nil {
93+
return err
94+
}
11795
}
96+
97+
return nil
11898
}

go.mod

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,16 @@ module github.com/davidsbond/pgfsm
33
go 1.24.0
44

55
require (
6-
github.com/golang-migrate/migrate/v4 v4.18.2
76
github.com/lib/pq v1.10.9
87
github.com/stretchr/testify v1.10.0
98
golang.org/x/sync v0.11.0
109
)
1110

1211
require (
1312
github.com/davecgh/go-spew v1.1.1 // indirect
14-
github.com/hashicorp/errwrap v1.1.0 // indirect
15-
github.com/hashicorp/go-multierror v1.1.1 // indirect
1613
github.com/kr/pretty v0.3.1 // indirect
1714
github.com/pmezard/go-difflib v1.0.0 // indirect
1815
github.com/rogpeppe/go-internal v1.13.1 // indirect
19-
go.opentelemetry.io/otel/metric v1.34.0 // indirect
20-
go.opentelemetry.io/otel/trace v1.34.0 // indirect
21-
go.uber.org/atomic v1.7.0 // indirect
22-
golang.org/x/sys v0.29.0 // indirect
2316
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
2417
gopkg.in/yaml.v3 v3.0.1 // indirect
2518
)

go.sum

Lines changed: 0 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,6 @@
1-
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
2-
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
3-
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
4-
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
51
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
6-
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
72
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
83
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
9-
github.com/dhui/dktest v0.4.4 h1:+I4s6JRE1yGuqflzwqG+aIaMdgXIorCf5P98JnaAWa8=
10-
github.com/dhui/dktest v0.4.4/go.mod h1:4+22R4lgsdAXrDyaH4Nqx2JEz2hLp49MqQmm9HLCQhM=
11-
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
12-
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
13-
github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4=
14-
github.com/docker/docker v27.2.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
15-
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
16-
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
17-
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
18-
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
19-
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
20-
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
21-
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
22-
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
23-
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
24-
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
25-
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
26-
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
27-
github.com/golang-migrate/migrate/v4 v4.18.2 h1:2VSCMz7x7mjyTXx3m2zPokOY82LTRgxK1yQYKo6wWQ8=
28-
github.com/golang-migrate/migrate/v4 v4.18.2/go.mod h1:2CM6tJvn2kqPXwnXO/d3rAQYiyoIm180VsO8PRX6Rpk=
29-
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
30-
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
31-
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
32-
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
33-
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
344
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
355
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
366
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -40,44 +10,16 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
4010
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
4111
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
4212
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
43-
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
44-
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
45-
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
46-
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
47-
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
48-
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
49-
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
50-
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
51-
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
52-
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
5313
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
54-
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
55-
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
5614
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
5715
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5816
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
5917
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
6018
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
61-
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
62-
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
6319
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
6420
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
65-
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
66-
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
67-
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk=
68-
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8=
69-
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
70-
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
71-
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
72-
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
73-
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
74-
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
75-
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
76-
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
7721
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
7822
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
79-
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
80-
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
8123
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
8224
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
8325
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

migrations/20250219233253_commands.up.sql renamed to migrate.sql

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
BEGIN;
22

3-
CREATE TABLE IF NOT EXISTS pgfsm_command
3+
CREATE SCHEMA IF NOT EXISTS pgfsm;
4+
5+
CREATE TABLE IF NOT EXISTS pgfsm.command
46
(
57
id BIGSERIAL NOT NULL PRIMARY KEY,
68
kind TEXT NOT NULL,

migrations/20250219233253_commands.down.sql

Lines changed: 0 additions & 5 deletions
This file was deleted.

pgfsm.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,21 +64,22 @@ func (e UnknownCommandError) Error() string {
6464
}
6565

6666
// New returns a new instance of the FSM type that will read commands from the provided sql.DB instance. This function
67-
// will perform database migrations to ensure that the required tables exist within the database. This table is named
68-
// pgfsm_migration which hopefully will never conflict with an existing table within your chosen database.
67+
// will perform database migrations to ensure that the required tables exist within the database. These database objects
68+
// will reside in their own schema named pgfsm. The user connecting to the database for this function will require
69+
// the necessary permissions to create database objects.
6970
//
7071
// You can also provide zero or more Option functions to modify the behaviour of the FSM. Please see the Option type
7172
// for specifics.
72-
func New(db *sql.DB, options ...Option) (*FSM, error) {
73-
if err := migrateUp(db); err != nil {
74-
return nil, err
75-
}
76-
73+
func New(ctx context.Context, db *sql.DB, options ...Option) (*FSM, error) {
7774
opts := defaultOptions()
7875
for _, o := range options {
7976
o(&opts)
8077
}
8178

79+
if err := migrateUp(ctx, db, opts.logger); err != nil {
80+
return nil, err
81+
}
82+
8283
return &FSM{
8384
db: db,
8485
options: opts,
@@ -179,7 +180,7 @@ func (fsm *FSM) next(ctx context.Context, h Handler) error {
179180

180181
cmd := factory()
181182
if err = fsm.options.encoder.Decode(data, cmd); err != nil {
182-
return fmt.Errorf("failed to decode command %q: %w", kind, err)
183+
return fmt.Errorf("failed to decode command %d: %w", id, err)
183184
}
184185

185186
log.InfoContext(ctx, "handling command")

pgfsm_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func TestFSM_ReadWrite(t *testing.T) {
5151

5252
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))
5353

54-
fsm, err := pgfsm.New(db,
54+
fsm, err := pgfsm.New(t.Context(), db,
5555
pgfsm.SkipUnknownCommands(),
5656
pgfsm.UseEncoding(&pgfsm.GOB{}),
5757
pgfsm.UseLogger(logger),

0 commit comments

Comments
 (0)