Skip to content

Commit ec1e66f

Browse files
authored
Merge pull request #8 from ziflex/feat/beginner-in-transaction-func
Refactor transaction handling to use Beginner interface and improve context management
2 parents 1eaade6 + 1f0f675 commit ec1e66f

2 files changed

Lines changed: 52 additions & 70 deletions

File tree

context.go

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ func NewDatabaseContext(parent context.Context, db Database) Context {
123123
//
124124
// Parameters:
125125
// - ctx: The context to search for an existing dbx Context
126-
// - creator: Either a ContextCreator, Database, or any type with Context method to use if no existing Context is found
126+
// - creator: Either a ContextCreator, Database, or Transactor
127127
//
128128
// Returns:
129129
// - Context: Either the existing dbx Context or a newly created one
@@ -133,30 +133,24 @@ func NewDatabaseContext(parent context.Context, db Database) Context {
133133
// // This will reuse existing dbx Context or create new one
134134
// dbCtx := dbx.NewContextFrom(ctx, database)
135135
// executor := dbCtx.Executor()
136-
func NewContextFrom(ctx context.Context, creator interface{}) Context {
136+
func NewContextFrom(ctx context.Context, input any) Context {
137137
found := FromContext(ctx)
138138

139139
if found != nil {
140140
return found
141141
}
142142

143-
// Try ContextCreator interface first
144-
if cc, ok := creator.(ContextCreator); ok {
145-
return cc.Context(ctx)
143+
switch val := input.(type) {
144+
case ContextCreator:
145+
return val.Context(ctx)
146+
case Database:
147+
return NewDatabaseContext(ctx, val)
148+
case Transactor:
149+
return NewContext(ctx, val)
150+
default:
151+
// If none work, panic with helpful message
152+
panic("input must implement ContextCreator, Database, or Transactor")
146153
}
147-
148-
// Try Database interface
149-
if db, ok := creator.(Database); ok {
150-
return NewDatabaseContext(ctx, db)
151-
}
152-
153-
// Try any type with Context method (for backward compatibility)
154-
if contextProvider, ok := creator.(interface{ Context(context.Context) Context }); ok {
155-
return contextProvider.Context(ctx)
156-
}
157-
158-
// If none work, panic with helpful message
159-
panic("creator must implement ContextCreator, Database, or have Context(context.Context) Context method")
160154
}
161155

162156
// FromContext extracts a dbx Context from the provided Go context.

transaction.go

Lines changed: 40 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -8,101 +8,89 @@ import (
88
// and handles commit or rollback automatically. If the context already contains
99
// a transaction, it will be reused unless the WithNewTransaction option is specified.
1010
//
11-
// The transaction lifecycle is managed automatically:
12-
// - If a new transaction is created, it will be committed on success or rolled back on error
13-
// - If an existing transaction is reused, commit/rollback is left to the outer transaction
14-
// - Any panic during operation execution will trigger a rollback if a new transaction was created
11+
// Transaction lifecycle management:
12+
// - If a new transaction is created, it's committed on successful operation or rolled back on error.
13+
// - If an existing transaction is reused, commit/rollback is left to the outer transaction.
14+
// - Any panic during operation execution triggers rollback if a new transaction was created.
1515
//
1616
// Parameters:
17-
// - ctx: The parent Go context
18-
// - db: Database instance to create transaction from (if needed)
19-
// - op: Operation to execute within the transaction
20-
// - opts: Optional transaction configuration (isolation level, read-only, etc.)
17+
// - ctx: Parent Go context.
18+
// - beginner: A Beginner capable of creating transactions (typically a Database).
19+
// - op: Operation to execute within the transaction, taking a dbx.Context.
20+
// - opts: Optional configuration (e.g., isolation, read-only, always create new transaction).
2121
//
2222
// Returns:
23-
// - error: Any error from transaction creation, operation execution, or commit/rollback
23+
// - error: Any error from transaction creation, operation execution, or commit/rollback.
2424
//
2525
// Example:
2626
//
2727
// err := dbx.Transaction(ctx, db, func(txCtx dbx.Context) error {
2828
// _, err := txCtx.Executor().Exec("INSERT INTO users (name) VALUES (?)", "John")
29-
// if err != nil {
30-
// return err // This will trigger automatic rollback
31-
// }
29+
// if err != nil { return err } // triggers automatic rollback
3230
// _, err = txCtx.Executor().Exec("INSERT INTO profiles (user_id) VALUES (?)", userID)
3331
// return err
3432
// })
35-
func Transaction(ctx context.Context, db Database, op Operation, opts ...Option) error {
36-
_, err := transactionWithInternal(ctx, db, func(ctx Context) (interface{}, error) {
33+
func Transaction(ctx context.Context, beginner Beginner, op Operation, opts ...Option) error {
34+
_, err := transactionWithInternal(ctx, beginner, func(ctx Context) (interface{}, error) {
3735
return nil, op(ctx)
3836
}, opts)
3937

4038
return err
4139
}
4240

43-
// TransactionWithResult begins a transaction and executes an operation that returns a typed result.
44-
// Like Transaction, it handles automatic commit/rollback and transaction reuse, but allows
45-
// the operation to return a value along with any error.
46-
//
47-
// The transaction lifecycle follows the same rules as Transaction:
48-
// - New transactions are committed on success or rolled back on error
49-
// - Existing transactions are reused and their lifecycle managed by the outer scope
41+
// TransactionWithResult begins a transaction and executes an operation returning a typed result.
42+
// Handles automatic commit/rollback and transaction reuse (see Transaction for rules).
5043
//
5144
// Parameters:
52-
// - ctx: The parent Go context
53-
// - db: Database instance to create transaction from (if needed)
54-
// - op: Operation to execute that returns a typed result
55-
// - setters: Optional transaction configuration options
45+
// - ctx: Parent Go context.
46+
// - beginner: A Beginner capable of creating transactions (typically a Database).
47+
// - op: Operation to execute within the transaction that returns (T, error).
48+
// - setters: Optional configuration (transaction isolation, read-only, always create, etc.).
5649
//
5750
// Returns:
58-
// - T: The result returned by the operation (zero value if error occurred)
59-
// - error: Any error from transaction creation, operation execution, or commit/rollback
51+
// - T: Result returned by the operation (zero value if error).
52+
// - error: Any error from transaction creation, operation execution, or commit/rollback.
6053
//
6154
// Example:
6255
//
6356
// userID, err := dbx.TransactionWithResult(ctx, db, func(txCtx dbx.Context) (int64, error) {
6457
// result, err := txCtx.Executor().Exec("INSERT INTO users (name) VALUES (?)", "John")
65-
// if err != nil {
66-
// return 0, err
67-
// }
58+
// if err != nil { return 0, err }
6859
// return result.LastInsertId()
6960
// })
70-
func TransactionWithResult[T any](ctx context.Context, db Database, op OperationWithResult[T], setters ...Option) (T, error) {
71-
return transactionWithInternal(ctx, db, op, setters)
61+
func TransactionWithResult[T any](ctx context.Context, beginner Beginner, op OperationWithResult[T], setters ...Option) (T, error) {
62+
return transactionWithInternal(ctx, beginner, op, setters)
7263
}
7364

74-
// transactionWithInternal implements the core transaction logic used by both
75-
// Transaction and TransactionWithResult functions. It handles transaction creation,
76-
// reuse detection, operation execution, and automatic commit/rollback.
65+
// transactionWithInternal contains core transaction logic for Transaction and TransactionWithResult.
7766
//
78-
// Transaction Reuse Logic:
79-
// - If opts.AlwaysCreate is false (default), checks for existing transaction in context
80-
// - If existing transaction found, reuses it and delegates lifecycle management to outer scope
81-
// - If no existing transaction or AlwaysCreate is true, creates a new transaction
67+
// Transaction reuse/creation:
68+
// - By default, attempts to detect and reuse an existing transaction in context.
69+
// - If WithNewTransaction is specified or no transaction exists, creates a new one.
8270
//
83-
// Error Handling:
84-
// - If operation returns an error and a new transaction was created, automatically rolls back
85-
// - If operation succeeds and a new transaction was created, automatically commits
86-
// - If reusing existing transaction, no automatic commit/rollback occurs
71+
// Error handling and lifecycle:
72+
// - Rolls back on error or panic if a new transaction was created.
73+
// - Commits on success if a new transaction was created.
74+
// - Existing transactions are reused with lifecycle managed by caller.
8775
//
8876
// Parameters:
89-
// - ctx: Parent Go context
90-
// - db: Database instance for creating new transactions
91-
// - op: Operation to execute within transaction scope
92-
// - setters: Transaction configuration options
77+
// - ctx: Parent Go context.
78+
// - beginner: Capable of creating a new transaction.
79+
// - op: Operation to execute, returns (T, error).
80+
// - setters: List of functional options for transaction configuration.
9381
//
9482
// Returns:
95-
// - T: Result from the operation (zero value if error occurred)
96-
// - error: Any error from transaction management or operation execution
97-
func transactionWithInternal[T any](ctx context.Context, db Database, op OperationWithResult[T], setters []Option) (T, error) {
83+
// - T: Operation result (zero value if error).
84+
// - error: Any error from transaction handling or op execution.
85+
func transactionWithInternal[T any](ctx context.Context, beginner Beginner, op OperationWithResult[T], setters []Option) (T, error) {
9886
var tx Transactor
9987
var createdTx bool
10088
var dbCtx Context
10189
opts := newOptions(setters)
10290

10391
if !opts.AlwaysCreate {
10492
// retrieve existing or create a new context
105-
dbCtx = NewContextFrom(ctx, db)
93+
dbCtx = NewContextFrom(ctx, beginner)
10694
executor := dbCtx.Executor()
10795

10896
// check if the executor is a transaction
@@ -119,7 +107,7 @@ func transactionWithInternal[T any](ctx context.Context, db Database, op Operati
119107
createdTx = true
120108

121109
// create a new transaction
122-
tx, err = db.BeginTx(ctx, opts.TxOptions)
110+
tx, err = beginner.BeginTx(ctx, opts.TxOptions)
123111

124112
if err != nil {
125113
return *new(T), err

0 commit comments

Comments
 (0)