-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
pgx/v5 wrapper with convenience methods
- Loading branch information
Showing
9 changed files
with
334 additions
and
1 deletion.
There are no files selected for viewing
This file contains 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 |
---|---|---|
|
@@ -13,3 +13,4 @@ | |
|
||
# Dependency directories (remove the comment below to include it) | ||
# vendor/ | ||
.idea |
This file contains 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,17 @@ | ||
[linters] | ||
disable-all=true | ||
enable = [ | ||
"errcheck", | ||
"gosimple", | ||
"govet", | ||
"ineffassign", | ||
"staticcheck", | ||
"typecheck", | ||
"unused", | ||
"forbidigo", | ||
"goimports", | ||
"gosec", | ||
"importas", | ||
"nilnil", | ||
"unconvert", | ||
] |
This file contains 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,4 @@ | ||
.PHONY: lint | ||
lint: | ||
# Linting... | ||
golangci-lint run |
This file contains 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 |
---|---|---|
@@ -1,2 +1,45 @@ | ||
# pgsq | ||
Wrapper that binds pgx, pgscan, & squirrel together | ||
## About | ||
Wrapper for [pgx/v5](https://github.com/jackc/pgx/) pool. | ||
|
||
[scany/v2](https://github.com/georgysavva/scany/) is used to provide _Select_ & _Get_ convenience methods. | ||
|
||
All basic (i.e. not raw) methods take _sqlizer_ interface as a query, which is provided by | ||
[squirrel](https://github.com/Masterminds/squirrel) query builder. | ||
|
||
|
||
## Usage example | ||
Actual data-access methods should take _pgsq.Queryable_ as an argument–this way _CreateEntity_ can be called using a connection | ||
pool or a transaction depending on logical requirements. | ||
|
||
```go | ||
package database | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/vaardan/pgsq" | ||
"github.com/Masterminds/squirrel" | ||
) | ||
|
||
|
||
// CreateEntity creates new entity with the given name and returns its ID. | ||
func CreateEntity(ctx context.Context, q pgsq.Queryable, name string) (int, error) { | ||
query := squirrel.StatementBuilder. | ||
PlaceholderFormat(squirrel.Dollar). | ||
Insert("entity_table"). | ||
Columns("name"). | ||
Values(name). | ||
Suffix("returning id") | ||
|
||
var id int | ||
err := q.Get(ctx, &id, query) | ||
if err != nil { | ||
return 0, fmt.Errorf("insert entity: %w", err) | ||
} | ||
|
||
return id, nil | ||
} | ||
|
||
``` |
This file contains 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,58 @@ | ||
package pgsq | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/georgysavva/scany/v2/pgxscan" | ||
"github.com/jackc/pgx/v5/pgconn" | ||
) | ||
|
||
// Queryable interface containing operations necessary to query database. | ||
// Both Pool and Tx implement it. | ||
type Queryable interface { | ||
// Exec executes the builder query. | ||
Exec(ctx context.Context, query sqlizer) (pgconn.CommandTag, error) | ||
|
||
// ExecRaw executes the raw query. | ||
ExecRaw(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error) | ||
|
||
// Get queries a single row. Returns pgx.ErrNoRows, if there are no rows satisfying the builder query. | ||
Get(ctx context.Context, dst any, sqlizer sqlizer) error | ||
|
||
// GetRaw queries a single row. Returns pgx.ErrNoRows, if there are no rows satisfying the raw query. | ||
GetRaw(ctx context.Context, dst any, sql string, args ...any) error | ||
|
||
// Select queries multiple rows. Returns nil, if there are no rows satisfying the builder query. | ||
Select(ctx context.Context, dst any, query sqlizer) error | ||
|
||
// SelectRaw queries multiple rows. Returns nil, if there are no rows satisfying the raw query. | ||
SelectRaw(ctx context.Context, dst any, sql string, args ...any) error | ||
} | ||
|
||
func execFn(ctx context.Context, q execer, query sqlizer) (pgconn.CommandTag, error) { | ||
sql, args, err := query.ToSql() | ||
if err != nil { | ||
return pgconn.CommandTag{}, fmt.Errorf("to sql: %w", err) | ||
} | ||
|
||
return q.Exec(ctx, sql, args...) | ||
} | ||
|
||
func selectFn(ctx context.Context, q pgxscan.Querier, dst any, query sqlizer) error { | ||
sql, args, err := query.ToSql() | ||
if err != nil { | ||
return fmt.Errorf("to sql: %w", err) | ||
} | ||
|
||
return pgxscan.Select(ctx, q, dst, sql, args...) | ||
} | ||
|
||
func getFn(ctx context.Context, q pgxscan.Querier, dst any, query sqlizer) error { | ||
sql, args, err := query.ToSql() | ||
if err != nil { | ||
return fmt.Errorf("to sql: %w", err) | ||
} | ||
|
||
return pgxscan.Get(ctx, q, dst, sql, args...) | ||
} |
This file contains 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,17 @@ | ||
module github.com/vaardan/pgsq | ||
|
||
go 1.19 | ||
|
||
require ( | ||
github.com/georgysavva/scany/v2 v2.0.0 | ||
github.com/jackc/pgx/v5 v5.3.1 | ||
) | ||
|
||
require ( | ||
github.com/jackc/pgpassfile v1.0.0 // indirect | ||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect | ||
github.com/jackc/puddle/v2 v2.2.0 // indirect | ||
golang.org/x/crypto v0.6.0 // indirect | ||
golang.org/x/sync v0.1.0 // indirect | ||
golang.org/x/text v0.7.0 // indirect | ||
) |
This file contains 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,33 @@ | ||
github.com/cockroachdb/cockroach-go/v2 v2.2.0 h1:/5znzg5n373N/3ESjHF5SMLxiW4RKB05Ql//KWfeTFs= | ||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
github.com/georgysavva/scany/v2 v2.0.0 h1:RGXqxDv4row7/FYoK8MRXAZXqoWF/NM+NP0q50k3DKU= | ||
github.com/georgysavva/scany/v2 v2.0.0/go.mod h1:sigOdh+0qb/+aOs3TVhehVT10p8qJL7K/Zhyz8vWo38= | ||
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= | ||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= | ||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= | ||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= | ||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= | ||
github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU= | ||
github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= | ||
github.com/jackc/puddle/v2 v2.2.0 h1:RdcDk92EJBuBS55nQMMYFXTxwstHug4jkhT5pq8VxPk= | ||
github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= | ||
github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E= | ||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= | ||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | ||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= | ||
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= | ||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= | ||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= | ||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= | ||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= | ||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= |
This file contains 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,81 @@ | ||
package pgsq | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/georgysavva/scany/v2/pgxscan" | ||
"github.com/jackc/pgx/v5" | ||
"github.com/jackc/pgx/v5/pgconn" | ||
"github.com/jackc/pgx/v5/pgxpool" | ||
) | ||
|
||
// Pool contains operations necessary to query with the database. | ||
type Pool interface { | ||
Queryable | ||
BeginTx(ctx context.Context, txOptions *pgx.TxOptions) (Tx, error) | ||
} | ||
|
||
type sqlizer interface { | ||
ToSql() (sql string, args []any, err error) | ||
} | ||
|
||
type execer interface { | ||
Exec(ctx context.Context, sql string, arguments ...any) (pgconn.CommandTag, error) | ||
} | ||
|
||
// NewPool creates new Pool. | ||
func NewPool(pool *pgxpool.Pool) Pool { | ||
return &poolWrapper{pool: pool} | ||
} | ||
|
||
// poolWrapper pgx pool wrapper. | ||
type poolWrapper struct { | ||
pool *pgxpool.Pool | ||
} | ||
|
||
// BeginTx starts a transaction. | ||
// Commit or Rollback must be called on the returned transaction to finalize the transaction block. | ||
func (p *poolWrapper) BeginTx(ctx context.Context, txOptions *pgx.TxOptions) (Tx, error) { | ||
var txOpts pgx.TxOptions | ||
if txOptions != nil { | ||
txOpts = *txOptions | ||
} | ||
|
||
tx, err := p.pool.BeginTx(ctx, txOpts) | ||
if err != nil { | ||
return nil, fmt.Errorf("begin tx: %w", err) | ||
} | ||
|
||
return &txWrapper{tx: tx}, nil | ||
} | ||
|
||
// Exec executes the builder query. | ||
func (p *poolWrapper) Exec(ctx context.Context, query sqlizer) (pgconn.CommandTag, error) { | ||
return execFn(ctx, p.pool, query) | ||
} | ||
|
||
// ExecRaw executes the raw query. | ||
func (p *poolWrapper) ExecRaw(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error) { | ||
return p.pool.Exec(ctx, sql, args...) | ||
} | ||
|
||
// Get queries a single row. Returns pgx.ErrNoRows, if there are no rows satisfying the builder query. | ||
func (p *poolWrapper) Get(ctx context.Context, dst any, query sqlizer) error { | ||
return getFn(ctx, p.pool, dst, query) | ||
} | ||
|
||
// GetRaw queries a single row. Returns pgx.ErrNoRows, if there are no rows satisfying the raw query. | ||
func (p *poolWrapper) GetRaw(ctx context.Context, dst any, sql string, args ...any) error { | ||
return pgxscan.Get(ctx, p.pool, dst, sql, args...) | ||
} | ||
|
||
// Select queries multiple rows. Returns nil, if there are no rows satisfying the builder query. | ||
func (p *poolWrapper) Select(ctx context.Context, dst any, query sqlizer) error { | ||
return selectFn(ctx, p.pool, dst, query) | ||
} | ||
|
||
// SelectRaw queries multiple rows. Returns nil, if there are no rows satisfying the raw query. | ||
func (p *poolWrapper) SelectRaw(ctx context.Context, dst any, sql string, args ...any) error { | ||
return pgxscan.Select(ctx, p.pool, dst, sql, args...) | ||
} |
This file contains 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,79 @@ | ||
package pgsq | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/georgysavva/scany/v2/pgxscan" | ||
"github.com/jackc/pgx/v5" | ||
"github.com/jackc/pgx/v5/pgconn" | ||
) | ||
|
||
// Tx transaction interface. | ||
type Tx interface { | ||
Queryable | ||
|
||
// Commit commits transaction. | ||
// Will return an error where errors.Is(pgx.ErrTxClosed) is true if the Tx is already closed, but is | ||
// otherwise safe to call multiple times. If the commit fails with a rollback status (e.g. the transaction was already | ||
// in a broken state) then an error where errors.Is(ErrTxCommitRollback) is true will be returned. | ||
Commit(ctx context.Context) error | ||
|
||
// Rollback cancels transaction. | ||
// Will return an error where errors.Is(pgx.ErrTxClosed) is true if the Tx is already | ||
// closed, but is otherwise safe to call multiple times. Hence, a defer tx.Rollback() is safe even if tx.Commit() will | ||
// be called first in a non-error condition. Any other failure of a real transaction will result in the connection | ||
// being closed. | ||
Rollback(ctx context.Context) error | ||
} | ||
|
||
// Tx transaction wrapper. | ||
type txWrapper struct { | ||
tx pgx.Tx | ||
} | ||
|
||
// Commit commits transaction. | ||
// Will return an error where errors.Is(pgx.ErrTxClosed) is true if the Tx is already closed, but is | ||
// otherwise safe to call multiple times. If the commit fails with a rollback status (e.g. the transaction was already | ||
// in a broken state) then an error where errors.Is(ErrTxCommitRollback) is true will be returned. | ||
func (t *txWrapper) Commit(ctx context.Context) error { | ||
return t.tx.Commit(ctx) | ||
} | ||
|
||
// Rollback cancels transaction. | ||
// Will return an error where errors.Is(pgx.ErrTxClosed) is true if the Tx is already | ||
// closed, but is otherwise safe to call multiple times. Hence, a defer tx.Rollback() is safe even if tx.Commit() will | ||
// be called first in a non-error condition. Any other failure of a real transaction will result in the connection | ||
// being closed. | ||
func (t *txWrapper) Rollback(ctx context.Context) error { | ||
return t.tx.Rollback(ctx) | ||
} | ||
|
||
// Exec executes the builder query. | ||
func (t *txWrapper) Exec(ctx context.Context, query sqlizer) (pgconn.CommandTag, error) { | ||
return execFn(ctx, t.tx, query) | ||
} | ||
|
||
// ExecRaw executes the raw query. | ||
func (t *txWrapper) ExecRaw(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error) { | ||
return t.tx.Exec(ctx, sql, args...) | ||
} | ||
|
||
// Get queries a single row. Returns pgx.ErrNoRows, if there are no rows satisfying the builder query. | ||
func (t *txWrapper) Get(ctx context.Context, dst any, query sqlizer) error { | ||
return getFn(ctx, t.tx, dst, query) | ||
} | ||
|
||
// GetRaw queries a single row. Returns pgx.ErrNoRows, if there are no rows satisfying the raw query. | ||
func (t *txWrapper) GetRaw(ctx context.Context, dst any, sql string, args ...any) error { | ||
return pgxscan.Get(ctx, t.tx, dst, sql, args...) | ||
} | ||
|
||
// Select queries multiple rows. Returns nil, if there are no rows satisfying the builder query. | ||
func (t *txWrapper) Select(ctx context.Context, dst any, query sqlizer) error { | ||
return selectFn(ctx, t.tx, dst, query) | ||
} | ||
|
||
// SelectRaw queries multiple rows. Returns nil, if there are no rows satisfying the raw query. | ||
func (t *txWrapper) SelectRaw(ctx context.Context, dst any, sql string, args ...any) error { | ||
return pgxscan.Select(ctx, t.tx, dst, sql, args...) | ||
} |