Documentation
¶
Index ¶
- Variables
- func Retry(retries int, delay time.Duration, fn func(int) error) error
- func RetryTransaction(ctx context.Context, db *sql.DB, retries int, delay time.Duration, ...) error
- func WithTransaction(db *sql.DB, fn ...func(*sql.Tx) error) error
- type ConfigFunc
- type DB
- type Pool
- type Transaction
- type Tx
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrEmptyDatabase is returned when no database connection is set. ErrEmptyDatabase = errors.New("no database connection is set") )
Functions ¶
func Retry ¶
Retry calls fn for retries times until it returns nil. If retries is zero fn would not be called. It delays and retries if the function returns any errors or panics. The fn function receives the current iteration as its argument. Deprecated: use http://github.com/arsham/retry library instead.
Example ¶
package main
import (
"fmt"
"time"
"github.com/arsham/dbtools"
"github.com/pkg/errors"
)
func main() {
err := dbtools.Retry(100, time.Nanosecond, func(i int) error {
fmt.Printf("Running iteration %d.\n", i+1)
if i < 1 {
return errors.New("ignored error")
}
return nil
})
fmt.Println("Error:", err)
}
Output: Running iteration 1. Running iteration 2. Error: <nil>
Example (Two) ¶
package main
import (
"fmt"
"time"
"github.com/arsham/dbtools"
"github.com/pkg/errors"
)
func main() {
err := dbtools.Retry(2, time.Nanosecond, func(i int) error {
fmt.Printf("Running iteration %d.\n", i+1)
return errors.New("some error")
})
fmt.Println("Error:", err)
}
Output: Running iteration 1. Running iteration 2. Error: some error
func RetryTransaction ¶
func RetryTransaction(ctx context.Context, db *sql.DB, retries int, delay time.Duration, fn ...func(*sql.Tx) error) error
RetryTransaction combines WithTransaction and Retry calls. It stops the call if context is times out or cancelled. Deprecated: use Transaction instead.
Example ¶
// For this example we are using sqlmock, but you can use an actual
// connection with this function.
db, dbMock, err := sqlmock.New()
if err != nil {
panic(err)
}
defer db.Close()
defer func() {
if err := dbMock.ExpectationsWereMet(); err != nil {
fmt.Printf("there were unfulfilled expectations: %s\n", err)
}
}()
dbMock.ExpectBegin()
dbMock.ExpectRollback()
dbMock.ExpectBegin()
dbMock.ExpectRollback()
dbMock.ExpectBegin()
dbMock.ExpectCommit()
calls := 0
ctx := context.Background()
err = dbtools.RetryTransaction(ctx, db, 100, time.Millisecond*100, func(*sql.Tx) error {
calls++
fmt.Printf("Running iteration %d.\n", calls)
if calls < 3 {
return errors.New("some error")
}
return nil
})
fmt.Println("Error:", err)
Output: Running iteration 1. Running iteration 2. Running iteration 3. Error: <nil>
func WithTransaction ¶
WithTransaction creates a transaction on db and uses it to call fn functions one by one. The first function that returns an error or panics will cause the loop to stop and transaction to be rolled back. Deprecated: use Transaction instead.
Example ¶
// For this example we are using sqlmock, but you can use an actual
// connection with this function.
db, dbMock, err := sqlmock.New()
if err != nil {
panic(err)
}
defer db.Close()
defer func() {
if err := dbMock.ExpectationsWereMet(); err != nil {
fmt.Printf("there were unfulfilled expectations: %s\n", err)
}
}()
dbMock.ExpectBegin()
dbMock.ExpectCommit()
err = dbtools.WithTransaction(db, func(*sql.Tx) error {
fmt.Println("Running first query.")
return nil
}, func(*sql.Tx) error {
fmt.Println("Running second query.")
return nil
})
fmt.Println("Transaction has an error:", err != nil)
Output: Running first query. Running second query. Transaction has an error: false
Example (Two) ¶
// For this example we are using sqlmock, but you can use an actual
// connection with this function.
db, dbMock, err := sqlmock.New()
if err != nil {
panic(err)
}
defer db.Close()
defer func() {
if err := dbMock.ExpectationsWereMet(); err != nil {
fmt.Printf("there were unfulfilled expectations: %s\n", err)
}
}()
dbMock.ExpectBegin()
dbMock.ExpectRollback()
err = dbtools.WithTransaction(db, func(*sql.Tx) error {
fmt.Println("Running first query.")
return nil
}, func(*sql.Tx) error {
fmt.Println("Running second query.")
return errors.New("something happened")
}, func(*sql.Tx) error {
fmt.Println("Running third query.")
return nil
})
fmt.Println("Transaction has an error:", err != nil)
Output: Running first query. Running second query. Transaction has an error: true
Types ¶
type ConfigFunc ¶ added in v0.4.0
type ConfigFunc func(*Transaction)
A ConfigFunc function sets up a Transaction.
func DelayMethod ¶ added in v0.4.0
func DelayMethod(m retry.DelayMethod) ConfigFunc
DelayMethod decides how to delay between each tries. Default is retry.StandardDelay.
func RetryCount ¶ added in v0.4.0
func RetryCount(n int) ConfigFunc
RetryCount defines a transaction should be tried n times. If n is 0, it will be set as 1.
func RetryDelay ¶ added in v0.4.0
func RetryDelay(d time.Duration) ConfigFunc
RetryDelay is the amount of delay between each unsuccessful tries. Set DelayMethod for the method of delay duration.
type Pool ¶ added in v0.4.0
Pool is the contract for beginning a transaction with a pgxpool db connection.
type Transaction ¶ added in v0.4.0
type Transaction struct {
// contains filtered or unexported fields
}
Transaction is a concurrent-safe object that can retry a transaction on either a sql.DB or a pgxpool connection until it succeeds.
DB and PGX will try transaction functions one-by-one until all of them return nil, then commits the transaction. If any of the transactions return any error other than retry.StopError, it will retry the transaction until the retry count is exhausted. If a running function returns a retry.StopError, the transaction will be rolled-back and would stop retrying. Tryouts will be stopped when the passed contexts are cancelled.
If all attempts return errors, the last error is returned. If a retry.StopError is returned, transaction is rolled back and the Err inside the retry.StopError is returned. There will be delays between tries defined by the retry.DelayMethod and Delay duration.
Any panic in transactions will be wrapped in an error and will be counted as an error, either being retried or returned.
It's an error to invoke the methods without their respective connections are set.
func NewTransaction ¶ added in v0.4.0
func NewTransaction(conn interface{}, conf ...ConfigFunc) (*Transaction, error)
NewTransaction returns an error if conn is not a DB, Pool, or *sql.DB connection.
Example ¶
// This setup tries the transaction only once.
dbtools.NewTransaction(&exampleConn{})
// This setup tries 100 times until succeeds. The delay is set to 10ms and
// it uses the retry.IncrementalDelay method, which means every time it
// increments the delay between retries with a jitter to avoid thunder herd
// problem.
dbtools.NewTransaction(&exampleConn{},
dbtools.RetryCount(100),
dbtools.RetryDelay(10*time.Millisecond),
dbtools.DelayMethod(retry.IncrementalDelay),
)
func (*Transaction) PGX ¶ added in v0.4.0
PGX returns an error if a pgxpool connection is not set.
Example ¶
tr, err := dbtools.NewTransaction(&exampleConn{})
if err != nil {
panic(err)
}
err = tr.PGX(context.Background(), func(pgx.Tx) error {
fmt.Println("Running first query.")
return nil
}, func(pgx.Tx) error {
fmt.Println("Running second query.")
return nil
})
fmt.Printf("Transaction's error: %v", err)
Output: Running first query. Running second query. Transaction's error: <nil>
Example (Panics) ¶
tr, err := dbtools.NewTransaction(&exampleConn{}, dbtools.RetryCount(10))
if err != nil {
panic(err)
}
calls := 0
err = tr.PGX(context.Background(), func(pgx.Tx) error {
calls++
fmt.Printf("Call #%d.\n", calls)
if calls < 5 {
panic("We have a panic!")
}
fmt.Println("All done.")
return nil
})
fmt.Printf("Transaction's error: %v\n", err)
fmt.Printf("Called %d times.\n", calls)
Output: Call #1. Call #2. Call #3. Call #4. Call #5. All done. Transaction's error: <nil> Called 5 times.
Example (Retries) ¶
tr, err := dbtools.NewTransaction(&exampleConn{}, dbtools.RetryCount(10))
if err != nil {
panic(err)
}
called := false
err = tr.PGX(context.Background(), func(pgx.Tx) error {
fmt.Println("Running first query.")
return nil
}, func(pgx.Tx) error {
if !called {
called = true
fmt.Println("Second query error.")
return assert.AnError
}
fmt.Println("Running second query.")
return nil
})
fmt.Printf("Transaction's error: %v", err)
Output: Running first query. Second query error. Running first query. Running second query. Transaction's error: <nil>
Example (StopTrying) ¶
// This example shows how to stop trying when we know an error is not
// recoverable.
tr, err := dbtools.NewTransaction(&exampleConn{},
dbtools.RetryCount(100),
dbtools.RetryDelay(time.Second),
)
if err != nil {
panic(err)
}
err = tr.PGX(context.Background(), func(pgx.Tx) error {
fmt.Println("Running first query.")
return nil
}, func(pgx.Tx) error {
fmt.Println("Running second query.")
return retry.StopError{Err: assert.AnError}
})
fmt.Printf("Transaction returns my error: %t", strings.Contains(err.Error(), assert.AnError.Error()))
Output: Running first query. Running second query. Transaction returns my error: true
type Tx ¶ added in v0.4.0
type Tx interface {
Commit() error
Exec(query string, args ...interface{}) (sql.Result, error)
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
Prepare(query string) (*sql.Stmt, error)
PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
Query(query string, args ...interface{}) (*sql.Rows, error)
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
QueryRow(query string, args ...interface{}) *sql.Row
QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row
Rollback() error
Stmt(stmt *sql.Stmt) *sql.Stmt
StmtContext(ctx context.Context, stmt *sql.Stmt) *sql.Stmt
}
Tx is a transaction began with sql.DB.