Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 17 additions & 25 deletions pgx/transactor.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,22 @@ import (
"context"
"fmt"

"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
)

func NewTransactor(db *pgx.Conn) (*Transactor, DBGetter) {
func NewTransactor(db pgxDB) (*Transactor, DBGetter) {
txKey := &transactorKey{}

pgxTxGetter := func(ctx context.Context) pgxDB {
if tx := txFromContext(ctx); tx != nil {
if tx := txFromContext(ctx, txKey); tx != nil {
return tx
}

return db
}

dbGetter := func(ctx context.Context) DB {
if tx := txFromContext(ctx); tx != nil {
if tx := txFromContext(ctx, txKey); tx != nil {
return tx
}

Expand All @@ -27,29 +28,13 @@ func NewTransactor(db *pgx.Conn) (*Transactor, DBGetter) {

return &Transactor{
pgxTxGetter,
txKey,
}, dbGetter
}

// Deprecated: use [NewTransactor] instead.
func NewTransactorFromPool(pool *pgxpool.Pool) (*Transactor, DBGetter) {
pgxTxGetter := func(ctx context.Context) pgxDB {
if tx := txFromContext(ctx); tx != nil {
return tx
}

return pool
}

dbGetter := func(ctx context.Context) DB {
if tx := txFromContext(ctx); tx != nil {
return tx
}

return pool
}

return &Transactor{
pgxTxGetter,
}, dbGetter
return NewTransactor(pool)
}

type (
Expand All @@ -58,6 +43,7 @@ type (

type Transactor struct {
pgxTxGetter
txKey *transactorKey
}

func (t *Transactor) WithinTransaction(ctx context.Context, txFunc func(context.Context) error) error {
Expand All @@ -71,7 +57,7 @@ func (t *Transactor) WithinTransaction(ctx context.Context, txFunc func(context.
_ = tx.Rollback(ctx) // If rollback fails, there's nothing to do, the transaction will expire by itself
}()

txCtx := txToContext(ctx, tx)
txCtx := txToContext(ctx, t.txKey, tx)

if err := txFunc(txCtx); err != nil {
return err
Expand All @@ -84,6 +70,12 @@ func (t *Transactor) WithinTransaction(ctx context.Context, txFunc func(context.
return nil
}

func (t *Transactor) IsWithinTransaction(ctx context.Context) bool {
return ctx.Value(t.txKey) != nil
}

// Deprecated: use [Transactor.IsWithinTransaction] instead.
// This function can give the wrong result if multiple transactor instances are used.
func IsWithinTransaction(ctx context.Context) bool {
return ctx.Value(transactorKey{}) != nil
return ctx.Value(transactorMarker{}) != nil
}
12 changes: 7 additions & 5 deletions pgx/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,20 @@ var (
)

type (
transactorKey struct{}
transactorKey struct{ _ *struct{} }
// Deprecated: transactorMarker is used in addition to transactorKey to keep the legacy IsWithinTransaction function.
transactorMarker struct{}
// DBGetter is used to get the current DB handler from the context.
// It returns the current transaction if there is one, otherwise it will return the original DB.
DBGetter func(context.Context) DB
)

func txToContext(ctx context.Context, tx pgx.Tx) context.Context {
return context.WithValue(ctx, transactorKey{}, tx)
func txToContext(ctx context.Context, key *transactorKey, tx pgx.Tx) context.Context {
return context.WithValue(context.WithValue(ctx, key, tx), transactorMarker{}, struct{}{})
}

func txFromContext(ctx context.Context) pgx.Tx {
if tx, ok := ctx.Value(transactorKey{}).(pgx.Tx); ok {
func txFromContext(ctx context.Context, key *transactorKey) pgx.Tx {
if tx, ok := ctx.Value(key).(pgx.Tx); ok {
return tx
}

Expand Down
18 changes: 14 additions & 4 deletions sqlx/transactor.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,18 @@ import (
)

func NewTransactor(db *sqlx.DB, nestedTransactionStrategy nestedTransactionsStrategy) (*Transactor, DBGetter) {
txKey := &transactorKey{}

sqlDBGetter := func(ctx context.Context) sqlxDB {
if tx := txFromContext(ctx); tx != nil {
if tx := txFromContext(ctx, txKey); tx != nil {
return tx
}

return db
}

dbGetter := func(ctx context.Context) DB {
if tx := txFromContext(ctx); tx != nil {
if tx := txFromContext(ctx, txKey); tx != nil {
return tx
}

Expand All @@ -27,6 +29,7 @@ func NewTransactor(db *sqlx.DB, nestedTransactionStrategy nestedTransactionsStra
return &Transactor{
sqlDBGetter,
nestedTransactionStrategy,
txKey,
}, dbGetter
}

Expand All @@ -38,6 +41,7 @@ type (
type Transactor struct {
sqlxDBGetter
nestedTransactionsStrategy
txKey *transactorKey
}

func (t *Transactor) WithinTransaction(ctx context.Context, txFunc func(context.Context) error) error {
Expand All @@ -52,7 +56,7 @@ func (t *Transactor) WithinTransaction(ctx context.Context, txFunc func(context.
defer func() {
_ = currentTX.Rollback() // If rollback fails, there's nothing to do, the transaction will expire by itself
}()
txCtx := txToContext(ctx, newDB)
txCtx := txToContext(ctx, t.txKey, newDB)

if err := txFunc(txCtx); err != nil {
return err
Expand All @@ -65,6 +69,12 @@ func (t *Transactor) WithinTransaction(ctx context.Context, txFunc func(context.
return nil
}

func (t *Transactor) IsWithinTransaction(ctx context.Context) bool {
return ctx.Value(t.txKey) != nil
}

// Deprecated: use [Transactor.IsWithinTransaction] instead.
// This function can give the wrong result if multiple transactor instances are used.
func IsWithinTransaction(ctx context.Context) bool {
return ctx.Value(transactorKey{}) != nil
return ctx.Value(transactorMarker{}) != nil
}
12 changes: 7 additions & 5 deletions sqlx/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,18 +65,20 @@ var (
)

type (
transactorKey struct{}
transactorKey struct{ _ *struct{} }
// Deprecated: transactorMarker is used in addition to transactorKey to keep the legacy IsWithinTransaction function.
transactorMarker struct{}
// DBGetter is used to get the current DB handler from the context.
// It returns the current transaction if there is one, otherwise it will return the original DB.
DBGetter func(context.Context) DB
)

func txToContext(ctx context.Context, tx sqlxDB) context.Context {
return context.WithValue(ctx, transactorKey{}, tx)
func txToContext(ctx context.Context, key *transactorKey, tx sqlxDB) context.Context {
return context.WithValue(context.WithValue(ctx, key, tx), transactorMarker{}, struct{}{})
}

func txFromContext(ctx context.Context) sqlxDB {
if tx, ok := ctx.Value(transactorKey{}).(sqlxDB); ok {
func txFromContext(ctx context.Context, key *transactorKey) sqlxDB {
if tx, ok := ctx.Value(key).(sqlxDB); ok {
return tx
}

Expand Down
10 changes: 4 additions & 6 deletions stdlib/fake_transactor.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,20 @@ package stdlib
import (
"context"
"database/sql"

"github.com/Thiht/transactor"
)

// NewFakeTransactor initializes a Transactor and DBGetter that do nothing:
// - the Transactor just executes its callback and returns the error,
// - the DBGetter just returns the DB handler.
// They can be used in tests where the transaction system itself doesn't need to be tested.
func NewFakeTransactor(db *sql.DB) (transactor.Transactor, DBGetter) {
return fakeTransactor{}, func(_ context.Context) DB {
func NewFakeTransactor(db *sql.DB) (FakeTransactor, DBGetter) {
return FakeTransactor{}, func(_ context.Context) DB {
return db
}
}

type fakeTransactor struct{}
type FakeTransactor struct{}

func (fakeTransactor) WithinTransaction(ctx context.Context, txFunc func(context.Context) error) error {
func (FakeTransactor) WithinTransaction(ctx context.Context, txFunc func(context.Context) error) error {
return txFunc(ctx)
}
28 changes: 18 additions & 10 deletions stdlib/transactor.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,31 @@ import (
"context"
"database/sql"
"fmt"

"github.com/Thiht/transactor"
)

func NewTransactor(db *sql.DB, nestedTransactionStrategy nestedTransactionsStrategy) (transactor.Transactor, DBGetter) {
func NewTransactor(db *sql.DB, nestedTransactionStrategy nestedTransactionsStrategy) (*Transactor, DBGetter) {
txKey := &transactorKey{}

sqlDBGetter := func(ctx context.Context) sqlDB {
if tx := txFromContext(ctx); tx != nil {
if tx := txFromContext(ctx, txKey); tx != nil {
return tx
}

return db
}

dbGetter := func(ctx context.Context) DB {
if tx := txFromContext(ctx); tx != nil {
if tx := txFromContext(ctx, txKey); tx != nil {
return tx
}

return db
}

return &stdlibTransactor{
return &Transactor{
sqlDBGetter,
nestedTransactionStrategy,
txKey,
}, dbGetter
}

Expand All @@ -36,12 +37,13 @@ type (
nestedTransactionsStrategy func(sqlDB, *sql.Tx) (sqlDB, sqlTx)
)

type stdlibTransactor struct {
type Transactor struct {
sqlDBGetter
nestedTransactionsStrategy
txKey *transactorKey
}

func (t *stdlibTransactor) WithinTransaction(ctx context.Context, txFunc func(context.Context) error) error {
func (t *Transactor) WithinTransaction(ctx context.Context, txFunc func(context.Context) error) error {
currentDB := t.sqlDBGetter(ctx)

tx, err := currentDB.BeginTx(ctx, nil)
Expand All @@ -53,7 +55,7 @@ func (t *stdlibTransactor) WithinTransaction(ctx context.Context, txFunc func(co
defer func() {
_ = currentTX.Rollback() // If rollback fails, there's nothing to do, the transaction will expire by itself
}()
txCtx := txToContext(ctx, newDB)
txCtx := txToContext(ctx, t.txKey, newDB)

if err := txFunc(txCtx); err != nil {
return err
Expand All @@ -66,6 +68,12 @@ func (t *stdlibTransactor) WithinTransaction(ctx context.Context, txFunc func(co
return nil
}

func (t *Transactor) IsWithinTransaction(ctx context.Context) bool {
return ctx.Value(t.txKey) != nil
}

// Deprecated: use [Transactor.IsWithinTransaction] instead.
// This function can give the wrong result if multiple transactor instances are used.
func IsWithinTransaction(ctx context.Context) bool {
return ctx.Value(transactorKey{}) != nil
return ctx.Value(transactorMarker{}) != nil
}
12 changes: 7 additions & 5 deletions stdlib/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,20 @@ var (
)

type (
transactorKey struct{}
transactorKey struct{ _ *struct{} }
// Deprecated: transactorMarker is used in addition to transactorKey to keep the legacy IsWithinTransaction function.
transactorMarker struct{}
// DBGetter is used to get the current DB handler from the context.
// It returns the current transaction if there is one, otherwise it will return the original DB.
DBGetter func(context.Context) DB
)

func txToContext(ctx context.Context, tx sqlDB) context.Context {
return context.WithValue(ctx, transactorKey{}, tx)
func txToContext(ctx context.Context, key *transactorKey, tx sqlDB) context.Context {
return context.WithValue(context.WithValue(ctx, key, tx), transactorMarker{}, struct{}{})
}

func txFromContext(ctx context.Context) sqlDB {
if tx, ok := ctx.Value(transactorKey{}).(sqlDB); ok {
func txFromContext(ctx context.Context, key *transactorKey) sqlDB {
if tx, ok := ctx.Value(key).(sqlDB); ok {
return tx
}

Expand Down
1 change: 1 addition & 0 deletions tests/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/jackc/pgx/v5 v5.7.6
github.com/jmoiron/sqlx v1.4.0
github.com/microsoft/go-mssqldb v1.9.3
github.com/pashagolub/pgxmock/v4 v4.9.0
github.com/sijms/go-ora/v2 v2.9.0
github.com/stretchr/testify v1.11.1
github.com/testcontainers/testcontainers-go v0.39.0
Expand Down
2 changes: 2 additions & 0 deletions tests/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/pashagolub/pgxmock/v4 v4.9.0 h1:itlO8nrVRnzkdMBXLs8pWUyyB2PC3Gku0WGIj/gGl7I=
github.com/pashagolub/pgxmock/v4 v4.9.0/go.mod h1:9L57pC193h2aKRHVyiiE817avasIPZnPwPlw3JczWvM=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
Expand Down
Loading
Loading