Skip to content

Commit 1581cc7

Browse files
committed
added Connection.TransactionNo
1 parent a774e44 commit 1581cc7

10 files changed

+119
-35
lines changed

config.go

+37-10
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,43 @@ import (
1111
// Config for a connection.
1212
// For tips see https://www.alexedwards.net/blog/configuring-sqldb
1313
type Config struct {
14-
Driver string `json:"driver"`
15-
Host string `json:"host"`
16-
Port uint16 `json:"port,omitempty"`
17-
User string `json:"user,omitempty"`
18-
Password string `json:"password,omitempty"`
19-
Database string `json:"database"`
20-
Extra map[string]string `json:"misc,omitempty"`
21-
MaxOpenConns int `json:"maxOpenConns,omitempty"`
22-
MaxIdleConns int `json:"maxIdleConns,omitempty"`
23-
ConnMaxLifetime time.Duration `json:"connMaxLifetime,omitempty"`
14+
Driver string `json:"driver"`
15+
Host string `json:"host"`
16+
Port uint16 `json:"port,omitempty"`
17+
User string `json:"user,omitempty"`
18+
Password string `json:"password,omitempty"`
19+
Database string `json:"database"`
20+
Extra map[string]string `json:"misc,omitempty"`
21+
22+
// MaxOpenConns sets the maximum number of open connections to the database.
23+
//
24+
// If MaxIdleConns is greater than 0 and the new MaxOpenConns is less than
25+
// MaxIdleConns, then MaxIdleConns will be reduced to match the new
26+
// MaxOpenConns limit.
27+
//
28+
// If MaxOpenConns <= 0, then there is no limit on the number of open connections.
29+
// The default is 0 (unlimited).
30+
MaxOpenConns int `json:"maxOpenConns,omitempty"`
31+
32+
// MaxIdleConns sets the maximum number of connections in the idle
33+
// connection pool.
34+
//
35+
// If MaxOpenConns is greater than 0 but less than the new MaxIdleConns,
36+
// then the new MaxIdleConns will be reduced to match the MaxOpenConns limit.
37+
//
38+
// If MaxIdleConns <= 0, no idle connections are retained.
39+
//
40+
// The default max idle connections is currently 2. This may change in
41+
// a future release.
42+
MaxIdleConns int `json:"maxIdleConns,omitempty"`
43+
44+
// ConnMaxLifetime sets the maximum amount of time a connection may be reused.
45+
//
46+
// Expired connections may be closed lazily before reuse.
47+
//
48+
// If ConnMaxLifetime <= 0, connections are not closed due to a connection's age.
49+
ConnMaxLifetime time.Duration `json:"connMaxLifetime,omitempty"`
50+
2451
DefaultIsolationLevel sql.IsolationLevel `json:"-"`
2552
Err error `json:"-"`
2653
}

connection.go

+12-2
Original file line numberDiff line numberDiff line change
@@ -129,15 +129,25 @@ type Connection interface {
129129
// IsTransaction returns if the connection is a transaction
130130
IsTransaction() bool
131131

132+
// TransactionNo returns the globally unique number of the transaction
133+
// or zero if the connection is not a transaction.
134+
// Implementations should use the package function NextTransactionNo
135+
// to aquire a new number in a threadsafe way.
136+
TransactionNo() uint64
137+
132138
// TransactionOptions returns the sql.TxOptions of the
133139
// current transaction and true as second result value,
134140
// or false if the connection is not a transaction.
135141
TransactionOptions() (*sql.TxOptions, bool)
136142

137143
// Begin a new transaction.
138-
// If the connection is already a transaction, a brand
144+
// If the connection is already a transaction then a brand
139145
// new transaction will begin on the parent's connection.
140-
Begin(opts *sql.TxOptions) (Connection, error)
146+
// The passed no will be returnd from the transaction's
147+
// Connection.TransactionNo method.
148+
// Implementations should use the package function NextTransactionNo
149+
// to aquire a new number in a threadsafe way.
150+
Begin(opts *sql.TxOptions, no uint64) (Connection, error)
141151

142152
// Commit the current transaction.
143153
// Returns ErrNotWithinTransaction if the connection

errors.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -169,11 +169,15 @@ func (e connectionWithError) IsTransaction() bool {
169169
return false
170170
}
171171

172+
func (e connectionWithError) TransactionNo() uint64 {
173+
return 0
174+
}
175+
172176
func (ce connectionWithError) TransactionOptions() (*sql.TxOptions, bool) {
173177
return nil, false
174178
}
175179

176-
func (e connectionWithError) Begin(opts *sql.TxOptions) (Connection, error) {
180+
func (e connectionWithError) Begin(opts *sql.TxOptions, no uint64) (Connection, error) {
177181
return nil, e.err
178182
}
179183

impl/connection.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -156,16 +156,20 @@ func (conn *connection) IsTransaction() bool {
156156
return false
157157
}
158158

159+
func (conn *connection) TransactionNo() uint64 {
160+
return 0
161+
}
162+
159163
func (conn *connection) TransactionOptions() (*sql.TxOptions, bool) {
160164
return nil, false
161165
}
162166

163-
func (conn *connection) Begin(opts *sql.TxOptions) (sqldb.Connection, error) {
167+
func (conn *connection) Begin(opts *sql.TxOptions, no uint64) (sqldb.Connection, error) {
164168
tx, err := conn.db.BeginTx(conn.ctx, opts)
165169
if err != nil {
166170
return nil, err
167171
}
168-
return newTransaction(conn, tx, opts), nil
172+
return newTransaction(conn, tx, opts, no), nil
169173
}
170174

171175
func (conn *connection) Commit() error {

impl/transaction.go

+10-4
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,16 @@ type transaction struct {
1515
parent *connection
1616
tx *sql.Tx
1717
opts *sql.TxOptions
18+
no uint64
1819
structFieldNamer sqldb.StructFieldMapper
1920
}
2021

21-
func newTransaction(parent *connection, tx *sql.Tx, opts *sql.TxOptions) *transaction {
22+
func newTransaction(parent *connection, tx *sql.Tx, opts *sql.TxOptions, no uint64) *transaction {
2223
return &transaction{
2324
parent: parent,
2425
tx: tx,
2526
opts: opts,
27+
no: no,
2628
structFieldNamer: parent.structFieldNamer,
2729
}
2830
}
@@ -40,7 +42,7 @@ func (conn *transaction) WithContext(ctx context.Context) sqldb.Connection {
4042
}
4143
parent := conn.parent.clone()
4244
parent.ctx = ctx
43-
return newTransaction(parent, conn.tx, conn.opts)
45+
return newTransaction(parent, conn.tx, conn.opts, conn.no)
4446
}
4547

4648
func (conn *transaction) WithStructFieldMapper(namer sqldb.StructFieldMapper) sqldb.Connection {
@@ -136,16 +138,20 @@ func (conn *transaction) IsTransaction() bool {
136138
return true
137139
}
138140

141+
func (conn *transaction) TransactionNo() uint64 {
142+
return conn.no
143+
}
144+
139145
func (conn *transaction) TransactionOptions() (*sql.TxOptions, bool) {
140146
return conn.opts, true
141147
}
142148

143-
func (conn *transaction) Begin(opts *sql.TxOptions) (sqldb.Connection, error) {
149+
func (conn *transaction) Begin(opts *sql.TxOptions, no uint64) (sqldb.Connection, error) {
144150
tx, err := conn.parent.db.BeginTx(conn.parent.ctx, opts)
145151
if err != nil {
146152
return nil, err
147153
}
148-
return newTransaction(conn.parent, tx, opts), nil
154+
return newTransaction(conn.parent, tx, opts, no), nil
149155
}
150156

151157
func (conn *transaction) Commit() error {

mockconn/connection.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -165,15 +165,19 @@ func (conn *connection) IsTransaction() bool {
165165
return false
166166
}
167167

168+
func (conn *connection) TransactionNo() uint64 {
169+
return 0
170+
}
171+
168172
func (conn *connection) TransactionOptions() (*sql.TxOptions, bool) {
169173
return nil, false
170174
}
171175

172-
func (conn *connection) Begin(opts *sql.TxOptions) (sqldb.Connection, error) {
176+
func (conn *connection) Begin(opts *sql.TxOptions, no uint64) (sqldb.Connection, error) {
173177
if conn.queryWriter != nil {
174178
fmt.Fprint(conn.queryWriter, "BEGIN")
175179
}
176-
return transaction{conn, opts}, nil
180+
return transaction{conn, opts, no}, nil
177181
}
178182

179183
func (conn *connection) Commit() error {

mockconn/transaction.go

+9-3
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ import (
55
"database/sql"
66
"fmt"
77

8-
sqldb "github.com/domonda/go-sqldb"
8+
"github.com/domonda/go-sqldb"
99
)
1010

1111
type transaction struct {
1212
*connection
1313
opts *sql.TxOptions
14+
no uint64
1415
}
1516

1617
func (conn transaction) Context() context.Context { return conn.connection.ctx }
@@ -22,22 +23,27 @@ func (conn transaction) WithContext(ctx context.Context) sqldb.Connection {
2223
return transaction{
2324
connection: conn.connection.WithContext(ctx).(*connection),
2425
opts: conn.opts,
26+
no: conn.no,
2527
}
2628
}
2729

2830
func (conn transaction) IsTransaction() bool {
2931
return true
3032
}
3133

34+
func (conn transaction) TransactionNo() uint64 {
35+
return conn.no
36+
}
37+
3238
func (conn transaction) TransactionOptions() (*sql.TxOptions, bool) {
3339
return conn.opts, true
3440
}
3541

36-
func (conn transaction) Begin(opts *sql.TxOptions) (sqldb.Connection, error) {
42+
func (conn transaction) Begin(opts *sql.TxOptions, no uint64) (sqldb.Connection, error) {
3743
if conn.queryWriter != nil {
3844
fmt.Fprint(conn.queryWriter, "BEGIN")
3945
}
40-
return transaction{conn.connection, opts}, nil
46+
return transaction{conn.connection, opts, no}, nil
4147
}
4248

4349
func (conn transaction) Commit() error {

pqconn/connection.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -177,16 +177,20 @@ func (conn *connection) IsTransaction() bool {
177177
return false
178178
}
179179

180+
func (conn *connection) TransactionNo() uint64 {
181+
return 0
182+
}
183+
180184
func (conn *connection) TransactionOptions() (*sql.TxOptions, bool) {
181185
return nil, false
182186
}
183187

184-
func (conn *connection) Begin(opts *sql.TxOptions) (sqldb.Connection, error) {
188+
func (conn *connection) Begin(opts *sql.TxOptions, no uint64) (sqldb.Connection, error) {
185189
tx, err := conn.db.BeginTx(conn.ctx, opts)
186190
if err != nil {
187191
return nil, err
188192
}
189-
return newTransaction(conn, tx, opts), nil
193+
return newTransaction(conn, tx, opts, no), nil
190194
}
191195

192196
func (conn *connection) Commit() error {

pqconn/transaction.go

+10-4
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,16 @@ type transaction struct {
1515
parent *connection
1616
tx *sql.Tx
1717
opts *sql.TxOptions
18+
no uint64
1819
structFieldNamer sqldb.StructFieldMapper
1920
}
2021

21-
func newTransaction(parent *connection, tx *sql.Tx, opts *sql.TxOptions) *transaction {
22+
func newTransaction(parent *connection, tx *sql.Tx, opts *sql.TxOptions, no uint64) *transaction {
2223
return &transaction{
2324
parent: parent,
2425
tx: tx,
2526
opts: opts,
27+
no: no,
2628
structFieldNamer: parent.structFieldNamer,
2729
}
2830
}
@@ -40,7 +42,7 @@ func (conn *transaction) WithContext(ctx context.Context) sqldb.Connection {
4042
}
4143
parent := conn.parent.clone()
4244
parent.ctx = ctx
43-
return newTransaction(parent, conn.tx, conn.opts)
45+
return newTransaction(parent, conn.tx, conn.opts, conn.no)
4446
}
4547

4648
func (conn *transaction) WithStructFieldMapper(namer sqldb.StructFieldMapper) sqldb.Connection {
@@ -136,16 +138,20 @@ func (conn *transaction) IsTransaction() bool {
136138
return true
137139
}
138140

141+
func (conn *transaction) TransactionNo() uint64 {
142+
return conn.no
143+
}
144+
139145
func (conn *transaction) TransactionOptions() (*sql.TxOptions, bool) {
140146
return conn.opts, true
141147
}
142148

143-
func (conn *transaction) Begin(opts *sql.TxOptions) (sqldb.Connection, error) {
149+
func (conn *transaction) Begin(opts *sql.TxOptions, no uint64) (sqldb.Connection, error) {
144150
tx, err := conn.parent.db.BeginTx(conn.parent.ctx, opts)
145151
if err != nil {
146152
return nil, err
147153
}
148-
return newTransaction(conn.parent, tx, opts), nil
154+
return newTransaction(conn.parent, tx, opts, no), nil
149155
}
150156

151157
func (conn *transaction) Commit() error {

transaction.go

+18-5
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,20 @@ import (
44
"database/sql"
55
"errors"
66
"fmt"
7+
"sync/atomic"
78
)
89

10+
var txCounter atomic.Uint64
11+
12+
// NextTransactionNo returns the next globally unique number
13+
// for a new transaction in a threadsafe way.
14+
//
15+
// Use Connection.TransactionNo() to get the number
16+
// from a transaction connection.
17+
func NextTransactionNo() uint64 {
18+
return txCounter.Add(1)
19+
}
20+
921
// Transaction executes txFunc within a database transaction that is passed in to txFunc as tx Connection.
1022
// Transaction returns all errors from txFunc or transaction commit errors happening after txFunc.
1123
// If parentConn is already a transaction, then it is passed through to txFunc unchanged as tx Connection
@@ -31,9 +43,10 @@ func Transaction(parentConn Connection, opts *sql.TxOptions, txFunc func(tx Conn
3143
// Errors and panics from txFunc will rollback the transaction.
3244
// Recovered panics are re-paniced and rollback errors after a panic are logged with ErrLogger.
3345
func IsolatedTransaction(parentConn Connection, opts *sql.TxOptions, txFunc func(tx Connection) error) (err error) {
34-
tx, e := parentConn.Begin(opts)
46+
txNo := NextTransactionNo()
47+
tx, e := parentConn.Begin(opts, txNo)
3548
if e != nil {
36-
return fmt.Errorf("Transaction Begin error: %w", e)
49+
return fmt.Errorf("Transaction %d Begin error: %w", txNo, e)
3750
}
3851

3952
defer func() {
@@ -42,7 +55,7 @@ func IsolatedTransaction(parentConn Connection, opts *sql.TxOptions, txFunc func
4255
e := tx.Rollback()
4356
if e != nil && !errors.Is(e, sql.ErrTxDone) {
4457
// Double error situation, log e so it doesn't get lost
45-
ErrLogger.Printf("Transaction error (%s) from rollback after panic: %+v", e, r)
58+
ErrLogger.Printf("Transaction %d error (%s) from rollback after panic: %+v", txNo, e, r)
4659
}
4760
panic(r) // re-throw panic after Rollback
4861
}
@@ -52,15 +65,15 @@ func IsolatedTransaction(parentConn Connection, opts *sql.TxOptions, txFunc func
5265
e := tx.Rollback()
5366
if e != nil && !errors.Is(e, sql.ErrTxDone) {
5467
// Double error situation, wrap err with e so it doesn't get lost
55-
err = fmt.Errorf("Transaction error (%s) from rollback after error: %w", e, err)
68+
err = fmt.Errorf("Transaction %d error (%s) from rollback after error: %w", txNo, e, err)
5669
}
5770
return
5871
}
5972

6073
e := tx.Commit()
6174
if e != nil {
6275
// Set Commit error as function return value
63-
err = fmt.Errorf("Transaction Commit error: %w", e)
76+
err = fmt.Errorf("Transaction %d Commit error: %w", txNo, e)
6477
}
6578
}()
6679

0 commit comments

Comments
 (0)