Skip to content

Commit f8cbc8c

Browse files
committed
Switch to pgxpool.
This commit modifies this package to use pgxpool instead of the regular standard library for the various improvements that pgxpool provides. Since we're only targeting postgres I think it makes sense to use a well-used low-level postgres driver. Signed-off-by: David Bond <[email protected]>
1 parent afb6eb8 commit f8cbc8c

File tree

5 files changed

+54
-35
lines changed

5 files changed

+54
-35
lines changed

database.go

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,49 +2,51 @@ package pgfsm
22

33
import (
44
"context"
5-
"database/sql"
65
_ "embed"
76
"errors"
87
"log/slog"
98
"strings"
9+
10+
"github.com/jackc/pgx/v5"
11+
"github.com/jackc/pgx/v5/pgxpool"
1012
)
1113

12-
func transaction(ctx context.Context, db *sql.DB, fn func(ctx context.Context, tx *sql.Tx) error) error {
13-
tx, err := db.BeginTx(ctx, &sql.TxOptions{})
14+
func transaction(ctx context.Context, db *pgxpool.Pool, fn func(ctx context.Context, tx pgx.Tx) error) error {
15+
tx, err := db.Begin(ctx)
1416
if err != nil {
1517
return err
1618
}
1719

1820
if err = fn(ctx, tx); err != nil {
19-
txErr := tx.Rollback()
20-
if errors.Is(txErr, sql.ErrTxDone) {
21+
txErr := tx.Rollback(ctx)
22+
if errors.Is(txErr, pgx.ErrTxClosed) {
2123
return err
2224
}
2325

2426
return errors.Join(err, txErr)
2527
}
2628

27-
err = tx.Commit()
28-
if errors.Is(err, sql.ErrTxDone) {
29+
err = tx.Commit(ctx)
30+
if errors.Is(err, pgx.ErrTxClosed) {
2931
return nil
3032
}
3133

3234
return err
3335
}
3436

35-
func insert(ctx context.Context, tx *sql.Tx, encoder Encoding, cmd Command) error {
37+
func insert(ctx context.Context, tx pgx.Tx, encoder Encoding, cmd Command) error {
3638
data, err := encoder.Encode(cmd)
3739
if err != nil {
3840
return err
3941
}
4042

4143
const q = `INSERT INTO pgfsm.command (kind, data) VALUES ($1, $2)`
4244

43-
_, err = tx.ExecContext(ctx, q, cmd.Kind(), data)
45+
_, err = tx.Exec(ctx, q, cmd.Kind(), data)
4446
return err
4547
}
4648

47-
func next(ctx context.Context, tx *sql.Tx) (int64, string, []byte, error) {
49+
func next(ctx context.Context, tx pgx.Tx) (int64, string, []byte, error) {
4850
const q = `
4951
SELECT id, kind, data FROM pgfsm.command
5052
ORDER BY id ASC
@@ -58,24 +60,24 @@ func next(ctx context.Context, tx *sql.Tx) (int64, string, []byte, error) {
5860
data []byte
5961
)
6062

61-
if err := tx.QueryRowContext(ctx, q).Scan(&id, &kind, &data); err != nil {
63+
if err := tx.QueryRow(ctx, q).Scan(&id, &kind, &data); err != nil {
6264
return 0, "", []byte{}, err
6365
}
6466

6567
return id, kind, data, nil
6668
}
6769

68-
func remove(ctx context.Context, tx *sql.Tx, id int64) error {
70+
func remove(ctx context.Context, tx pgx.Tx, id int64) error {
6971
const q = `DELETE FROM pgfsm.command WHERE id = $1`
7072

71-
_, err := tx.ExecContext(ctx, q, id)
73+
_, err := tx.Exec(ctx, q, id)
7274
return err
7375
}
7476

7577
//go:embed migrate.sql
7678
var migration string
7779

78-
func migrateUp(ctx context.Context, db *sql.DB, logger *slog.Logger) error {
80+
func migrateUp(ctx context.Context, db *pgxpool.Pool, logger *slog.Logger) error {
7981
logger.DebugContext(ctx, "performing migrations")
8082

8183
statements := strings.Split(migration, ";")
@@ -89,7 +91,7 @@ func migrateUp(ctx context.Context, db *sql.DB, logger *slog.Logger) error {
8991
With(slog.String("statement", statement)).
9092
DebugContext(ctx, "executing statement")
9193

92-
if _, err := db.ExecContext(ctx, statement); err != nil {
94+
if _, err := db.Exec(ctx, statement); err != nil {
9395
return err
9496
}
9597
}

go.mod

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

55
require (
6-
github.com/lib/pq v1.10.9
6+
github.com/jackc/pgx/v5 v5.7.2
77
github.com/stretchr/testify v1.10.0
88
golang.org/x/sync v0.12.0
99
)
1010

1111
require (
1212
github.com/davecgh/go-spew v1.1.1 // indirect
13+
github.com/jackc/pgpassfile v1.0.0 // indirect
14+
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
15+
github.com/jackc/puddle/v2 v2.2.2 // indirect
1316
github.com/kr/pretty v0.3.1 // indirect
1417
github.com/pmezard/go-difflib v1.0.0 // indirect
1518
github.com/rogpeppe/go-internal v1.13.1 // indirect
16-
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
19+
golang.org/x/crypto v0.31.0 // indirect
20+
golang.org/x/text v0.21.0 // indirect
1721
gopkg.in/yaml.v3 v3.0.1 // indirect
1822
)

go.sum

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,39 @@
11
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
2+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
23
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
34
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4-
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
5+
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
6+
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
7+
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
8+
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
9+
github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI=
10+
github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
11+
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
12+
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
513
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
614
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
7-
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
8-
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
915
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
1016
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
11-
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
12-
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
1317
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
1418
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
1519
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
1620
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
1721
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
1822
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
23+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
24+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
25+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
1926
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
2027
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
28+
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
29+
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
2130
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
2231
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
32+
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
33+
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
2334
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
2435
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
2536
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
37+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
2638
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
2739
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

pgfsm.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ package pgfsm
1616

1717
import (
1818
"context"
19-
"database/sql"
2019
"errors"
2120
"fmt"
2221
"log/slog"
2322
"time"
23+
24+
"github.com/jackc/pgx/v5"
25+
"github.com/jackc/pgx/v5/pgxpool"
2426
)
2527

2628
type (
@@ -31,7 +33,7 @@ type (
3133
// Command implementations should be registered prior to calling FSM.Read using the RegisterCommand function to ensure
3234
// that commands can be processed.
3335
FSM struct {
34-
db *sql.DB
36+
db *pgxpool.Pool
3537
commandFactories map[string]func() any
3638
options options
3739
}
@@ -64,14 +66,14 @@ func (e UnknownCommandError) Error() string {
6466
return fmt.Sprintf("unknown command type %q", e.Kind)
6567
}
6668

67-
// New returns a new instance of the FSM type that will read commands from the provided sql.DB instance. This function
69+
// New returns a new instance of the FSM type that will read commands from the provided pgxpool.Pool instance. This function
6870
// will perform database migrations to ensure that the required tables exist within the database. These database objects
6971
// will reside in their own schema named pgfsm. The user connecting to the database for this function will require
7072
// the necessary permissions to create database objects.
7173
//
7274
// You can also provide zero or more Option functions to modify the behaviour of the FSM. Please see the Option type
7375
// for specifics.
74-
func New(ctx context.Context, db *sql.DB, options ...Option) (*FSM, error) {
76+
func New(ctx context.Context, db *pgxpool.Pool, options ...Option) (*FSM, error) {
7577
opts := defaultOptions()
7678
for _, o := range options {
7779
o(&opts)
@@ -107,7 +109,7 @@ func RegisterCommand[T Command](fsm *FSM) {
107109
// Write a Command to the FSM. This Command will be encoded using the Encoding implementation and stored within the
108110
// database, where it can then be read and the relevant Handler invoked.
109111
func (fsm *FSM) Write(ctx context.Context, cmd Command) error {
110-
return transaction(ctx, fsm.db, func(ctx context.Context, tx *sql.Tx) error {
112+
return transaction(ctx, fsm.db, func(ctx context.Context, tx pgx.Tx) error {
111113
switch command := cmd.(type) {
112114
case batchCommand:
113115
for _, cmd = range command {
@@ -174,10 +176,10 @@ var (
174176
func (fsm *FSM) next(ctx context.Context, h Handler) error {
175177
fsm.options.logger.DebugContext(ctx, "checking for new commands")
176178

177-
return transaction(ctx, fsm.db, func(ctx context.Context, tx *sql.Tx) error {
179+
return transaction(ctx, fsm.db, func(ctx context.Context, tx pgx.Tx) error {
178180
id, kind, data, err := next(ctx, tx)
179181
switch {
180-
case errors.Is(err, sql.ErrNoRows):
182+
case errors.Is(err, pgx.ErrNoRows):
181183
return errNoCommands
182184
case err != nil:
183185
return err

pgfsm_test.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@ package pgfsm_test
22

33
import (
44
"context"
5-
"database/sql"
65
"errors"
76
"log/slog"
87
"net/url"
98
"os"
109
"testing"
1110
"time"
1211

13-
_ "github.com/lib/pq"
12+
"github.com/jackc/pgx/v5/pgxpool"
1413
"github.com/stretchr/testify/assert"
1514
"github.com/stretchr/testify/require"
1615
"golang.org/x/sync/errgroup"
@@ -113,7 +112,7 @@ func TestFSM_ReadWrite(t *testing.T) {
113112
assert.True(t, handledC)
114113
}
115114

116-
func testDB(t *testing.T) *sql.DB {
115+
func testDB(t *testing.T) *pgxpool.Pool {
117116
t.Helper()
118117

119118
u := &url.URL{
@@ -124,14 +123,14 @@ func testDB(t *testing.T) *sql.DB {
124123
RawQuery: "sslmode=disable",
125124
}
126125

127-
db, err := sql.Open("postgres", u.String())
126+
db, err := pgxpool.New(t.Context(), u.String())
128127
require.NoError(t, err)
129128

130129
t.Cleanup(func() {
131-
assert.NoError(t, db.Close())
130+
db.Close()
132131
})
133132

134-
require.NoError(t, db.PingContext(t.Context()))
133+
require.NoError(t, db.Ping(t.Context()))
135134

136135
return db
137136
}

0 commit comments

Comments
 (0)