Skip to content

Commit b7ddbfb

Browse files
committed
allow to isolate distinct transactor instances
1 parent 803f2a4 commit b7ddbfb

13 files changed

Lines changed: 310 additions & 63 deletions

File tree

pgx/transactor.go

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,22 @@ import (
44
"context"
55
"fmt"
66

7-
"github.com/jackc/pgx/v5"
87
"github.com/jackc/pgx/v5/pgxpool"
98
)
109

11-
func NewTransactor(db *pgx.Conn) (*Transactor, DBGetter) {
10+
func NewTransactor(db pgxDB) (*Transactor, DBGetter) {
11+
txKey := &transactorKey{}
12+
1213
pgxTxGetter := func(ctx context.Context) pgxDB {
13-
if tx := txFromContext(ctx); tx != nil {
14+
if tx := txFromContext(ctx, txKey); tx != nil {
1415
return tx
1516
}
1617

1718
return db
1819
}
1920

2021
dbGetter := func(ctx context.Context) DB {
21-
if tx := txFromContext(ctx); tx != nil {
22+
if tx := txFromContext(ctx, txKey); tx != nil {
2223
return tx
2324
}
2425

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

2829
return &Transactor{
2930
pgxTxGetter,
31+
txKey,
3032
}, dbGetter
3133
}
3234

35+
// Deprecated: use [NewTransactor] instead.
3336
func NewTransactorFromPool(pool *pgxpool.Pool) (*Transactor, DBGetter) {
34-
pgxTxGetter := func(ctx context.Context) pgxDB {
35-
if tx := txFromContext(ctx); tx != nil {
36-
return tx
37-
}
38-
39-
return pool
40-
}
41-
42-
dbGetter := func(ctx context.Context) DB {
43-
if tx := txFromContext(ctx); tx != nil {
44-
return tx
45-
}
46-
47-
return pool
48-
}
49-
50-
return &Transactor{
51-
pgxTxGetter,
52-
}, dbGetter
37+
return NewTransactor(pool)
5338
}
5439

5540
type (
@@ -58,6 +43,7 @@ type (
5843

5944
type Transactor struct {
6045
pgxTxGetter
46+
txKey *transactorKey
6147
}
6248

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

74-
txCtx := txToContext(ctx, tx)
60+
txCtx := txToContext(ctx, t.txKey, tx)
7561

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

73+
func (t *Transactor) IsWithinTransaction(ctx context.Context) bool {
74+
return ctx.Value(t.txKey) != nil
75+
}
76+
77+
// Deprecated: use [Transactor.IsWithinTransaction] instead.
78+
// This function can give the wrong result if multiple transactor instances are used.
8779
func IsWithinTransaction(ctx context.Context) bool {
88-
return ctx.Value(transactorKey{}) != nil
80+
return ctx.Value(transactorMarker{}) != nil
8981
}

pgx/types.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,20 @@ var (
3737
)
3838

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

46-
func txToContext(ctx context.Context, tx pgx.Tx) context.Context {
47-
return context.WithValue(ctx, transactorKey{}, tx)
48+
func txToContext(ctx context.Context, key *transactorKey, tx pgx.Tx) context.Context {
49+
return context.WithValue(context.WithValue(ctx, key, tx), transactorMarker{}, struct{}{})
4850
}
4951

50-
func txFromContext(ctx context.Context) pgx.Tx {
51-
if tx, ok := ctx.Value(transactorKey{}).(pgx.Tx); ok {
52+
func txFromContext(ctx context.Context, key *transactorKey) pgx.Tx {
53+
if tx, ok := ctx.Value(key).(pgx.Tx); ok {
5254
return tx
5355
}
5456

sqlx/transactor.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@ import (
88
)
99

1010
func NewTransactor(db *sqlx.DB, nestedTransactionStrategy nestedTransactionsStrategy) (*Transactor, DBGetter) {
11+
txKey := &transactorKey{}
12+
1113
sqlDBGetter := func(ctx context.Context) sqlxDB {
12-
if tx := txFromContext(ctx); tx != nil {
14+
if tx := txFromContext(ctx, txKey); tx != nil {
1315
return tx
1416
}
1517

1618
return db
1719
}
1820

1921
dbGetter := func(ctx context.Context) DB {
20-
if tx := txFromContext(ctx); tx != nil {
22+
if tx := txFromContext(ctx, txKey); tx != nil {
2123
return tx
2224
}
2325

@@ -27,6 +29,7 @@ func NewTransactor(db *sqlx.DB, nestedTransactionStrategy nestedTransactionsStra
2729
return &Transactor{
2830
sqlDBGetter,
2931
nestedTransactionStrategy,
32+
txKey,
3033
}, dbGetter
3134
}
3235

@@ -38,6 +41,7 @@ type (
3841
type Transactor struct {
3942
sqlxDBGetter
4043
nestedTransactionsStrategy
44+
txKey *transactorKey
4145
}
4246

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

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

72+
func (t *Transactor) IsWithinTransaction(ctx context.Context) bool {
73+
return ctx.Value(t.txKey) != nil
74+
}
75+
76+
// Deprecated: use [Transactor.IsWithinTransaction] instead.
77+
// This function can give the wrong result if multiple transactor instances are used.
6878
func IsWithinTransaction(ctx context.Context) bool {
69-
return ctx.Value(transactorKey{}) != nil
79+
return ctx.Value(transactorMarker{}) != nil
7080
}

sqlx/types.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,18 +65,20 @@ var (
6565
)
6666

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

74-
func txToContext(ctx context.Context, tx sqlxDB) context.Context {
75-
return context.WithValue(ctx, transactorKey{}, tx)
76+
func txToContext(ctx context.Context, key *transactorKey, tx sqlxDB) context.Context {
77+
return context.WithValue(context.WithValue(ctx, key, tx), transactorMarker{}, struct{}{})
7678
}
7779

78-
func txFromContext(ctx context.Context) sqlxDB {
79-
if tx, ok := ctx.Value(transactorKey{}).(sqlxDB); ok {
80+
func txFromContext(ctx context.Context, key *transactorKey) sqlxDB {
81+
if tx, ok := ctx.Value(key).(sqlxDB); ok {
8082
return tx
8183
}
8284

stdlib/fake_transactor.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,20 @@ package stdlib
33
import (
44
"context"
55
"database/sql"
6-
7-
"github.com/Thiht/transactor"
86
)
97

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

20-
type fakeTransactor struct{}
18+
type FakeTransactor struct{}
2119

22-
func (fakeTransactor) WithinTransaction(ctx context.Context, txFunc func(context.Context) error) error {
20+
func (FakeTransactor) WithinTransaction(ctx context.Context, txFunc func(context.Context) error) error {
2321
return txFunc(ctx)
2422
}

stdlib/transactor.go

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,31 @@ import (
44
"context"
55
"database/sql"
66
"fmt"
7-
8-
"github.com/Thiht/transactor"
97
)
108

11-
func NewTransactor(db *sql.DB, nestedTransactionStrategy nestedTransactionsStrategy) (transactor.Transactor, DBGetter) {
9+
func NewTransactor(db *sql.DB, nestedTransactionStrategy nestedTransactionsStrategy) (*Transactor, DBGetter) {
10+
txKey := &transactorKey{}
11+
1212
sqlDBGetter := func(ctx context.Context) sqlDB {
13-
if tx := txFromContext(ctx); tx != nil {
13+
if tx := txFromContext(ctx, txKey); tx != nil {
1414
return tx
1515
}
1616

1717
return db
1818
}
1919

2020
dbGetter := func(ctx context.Context) DB {
21-
if tx := txFromContext(ctx); tx != nil {
21+
if tx := txFromContext(ctx, txKey); tx != nil {
2222
return tx
2323
}
2424

2525
return db
2626
}
2727

28-
return &stdlibTransactor{
28+
return &Transactor{
2929
sqlDBGetter,
3030
nestedTransactionStrategy,
31+
txKey,
3132
}, dbGetter
3233
}
3334

@@ -36,12 +37,13 @@ type (
3637
nestedTransactionsStrategy func(sqlDB, *sql.Tx) (sqlDB, sqlTx)
3738
)
3839

39-
type stdlibTransactor struct {
40+
type Transactor struct {
4041
sqlDBGetter
4142
nestedTransactionsStrategy
43+
txKey *transactorKey
4244
}
4345

44-
func (t *stdlibTransactor) WithinTransaction(ctx context.Context, txFunc func(context.Context) error) error {
46+
func (t *Transactor) WithinTransaction(ctx context.Context, txFunc func(context.Context) error) error {
4547
currentDB := t.sqlDBGetter(ctx)
4648

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

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

71+
func (t *Transactor) IsWithinTransaction(ctx context.Context) bool {
72+
return ctx.Value(t.txKey) != nil
73+
}
74+
75+
// Deprecated: use [Transactor.IsWithinTransaction] instead.
76+
// This function can give the wrong result if multiple transactor instances are used.
6977
func IsWithinTransaction(ctx context.Context) bool {
70-
return ctx.Value(transactorKey{}) != nil
78+
return ctx.Value(transactorMarker{}) != nil
7179
}

stdlib/types.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,20 @@ var (
3636
)
3737

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

45-
func txToContext(ctx context.Context, tx sqlDB) context.Context {
46-
return context.WithValue(ctx, transactorKey{}, tx)
47+
func txToContext(ctx context.Context, key *transactorKey, tx sqlDB) context.Context {
48+
return context.WithValue(context.WithValue(ctx, key, tx), transactorMarker{}, struct{}{})
4749
}
4850

49-
func txFromContext(ctx context.Context) sqlDB {
50-
if tx, ok := ctx.Value(transactorKey{}).(sqlDB); ok {
51+
func txFromContext(ctx context.Context, key *transactorKey) sqlDB {
52+
if tx, ok := ctx.Value(key).(sqlDB); ok {
5153
return tx
5254
}
5355

tests/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ require (
1111
github.com/jackc/pgx/v5 v5.7.6
1212
github.com/jmoiron/sqlx v1.4.0
1313
github.com/microsoft/go-mssqldb v1.9.3
14+
github.com/pashagolub/pgxmock/v4 v4.9.0
1415
github.com/sijms/go-ora/v2 v2.9.0
1516
github.com/stretchr/testify v1.11.1
1617
github.com/testcontainers/testcontainers-go v0.39.0

tests/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
138138
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
139139
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
140140
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
141+
github.com/pashagolub/pgxmock/v4 v4.9.0 h1:itlO8nrVRnzkdMBXLs8pWUyyB2PC3Gku0WGIj/gGl7I=
142+
github.com/pashagolub/pgxmock/v4 v4.9.0/go.mod h1:9L57pC193h2aKRHVyiiE817avasIPZnPwPlw3JczWvM=
141143
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
142144
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
143145
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=

0 commit comments

Comments
 (0)