Documentation
¶
Overview ¶
Package satomic provides a easy way to nest atomic SQL updates using transactions and savepoints
Example ¶
package main import ( "context" "database/sql" "errors" "fmt" "os" sqlmock "github.com/DATA-DOG/go-sqlmock" "github.com/dhui/satomic" "github.com/dhui/satomic/savepointers/mock" ) func main() { db, _sqlmock, err := sqlmock.New() if err != nil { fmt.Println("Error creating sqlmock:", err) return } defer db.Close() // nolint:errcheck _sqlmock.ExpectBegin() _sqlmock.ExpectQuery("SELECT 1;").WillReturnRows(sqlmock.NewRows([]string{""}).AddRow(1)) _sqlmock.ExpectExec("SAVEPOINT 1;").WillReturnResult(sqlmock.NewResult(0, 0)) _sqlmock.ExpectQuery("SELECT 2;").WillReturnError(errors.New("select 2 error")) _sqlmock.ExpectExec("ROLLBACK TO 1;").WillReturnResult(sqlmock.NewResult(0, 0)) _sqlmock.ExpectExec("SAVEPOINT 2;").WillReturnResult(sqlmock.NewResult(0, 0)) _sqlmock.ExpectQuery("SELECT 3;").WillReturnRows(sqlmock.NewRows([]string{""}).AddRow(3)) _sqlmock.ExpectExec("RELEASE 2;").WillReturnResult(sqlmock.NewResult(0, 0)) _sqlmock.ExpectExec("SAVEPOINT 3;").WillReturnResult(sqlmock.NewResult(0, 0)) _sqlmock.ExpectQuery("SELECT 4;").WillReturnRows(sqlmock.NewRows([]string{""}).AddRow(4)) _sqlmock.ExpectExec("SAVEPOINT 4;").WillReturnResult(sqlmock.NewResult(0, 0)) _sqlmock.ExpectQuery("SELECT 5;").WillReturnRows(sqlmock.NewRows([]string{""}).AddRow(5)) _sqlmock.ExpectExec("SAVEPOINT 5;").WillReturnResult(sqlmock.NewResult(0, 0)) _sqlmock.ExpectQuery("SELECT 6;").WillReturnError(errors.New("select 6 error")) _sqlmock.ExpectExec("ROLLBACK TO 5;").WillReturnResult(sqlmock.NewResult(0, 0)) _sqlmock.ExpectExec("RELEASE 4;").WillReturnResult(sqlmock.NewResult(0, 0)) _sqlmock.ExpectExec("RELEASE 3;").WillReturnResult(sqlmock.NewResult(0, 0)) _sqlmock.ExpectCommit() ctx := context.Background() // For actual code, use a real Savepointer instead of a mocked one q, err := satomic.NewQuerier(ctx, db, mock.NewSavepointer(os.Stdout, true), sql.TxOptions{}) if err != nil { fmt.Println("Error creating Querier:", err) return } if err := q.Atomic(func(ctx context.Context, q satomic.Querier) error { var dummy int if err := q.QueryRowContext(ctx, "SELECT 1;").Scan(&dummy); err != nil { fmt.Println(err) } // SAVEPOINT 1 if err := q.Atomic(func(ctx context.Context, q satomic.Querier) error { return q.QueryRowContext(ctx, "SELECT 2;").Scan(&dummy) }); err != nil { fmt.Println(err) } // SAVEPOINT 2 if err := q.Atomic(func(ctx context.Context, q satomic.Querier) error { return q.QueryRowContext(ctx, "SELECT 3;").Scan(&dummy) }); err != nil { fmt.Println(err) } // SAVEPOINT 3 if err := q.Atomic(func(ctx context.Context, q satomic.Querier) error { if err := q.QueryRowContext(ctx, "SELECT 4;").Scan(&dummy); err != nil { fmt.Println(err) } // SAVEPOINT 4 if err := q.Atomic(func(ctx context.Context, q satomic.Querier) error { if err := q.QueryRowContext(ctx, "SELECT 5;").Scan(&dummy); err != nil { fmt.Println(err) } // SAVEPOINT 5 if err := q.Atomic(func(ctx context.Context, q satomic.Querier) error { return q.QueryRowContext(ctx, "SELECT 6;").Scan(&dummy) }); err != nil { fmt.Println(err) } return nil }); err != nil { fmt.Println(err) } return nil }); err != nil { fmt.Println(err) } return nil }); err != nil { fmt.Println(err) } if err := _sqlmock.ExpectationsWereMet(); err != nil { fmt.Println(err) } }
Output: SAVEPOINT 1; ROLLBACK TO 1; Err: "select 2 error" Atomic: <nil> SAVEPOINT 2; RELEASE 2; SAVEPOINT 3; SAVEPOINT 4; SAVEPOINT 5; ROLLBACK TO 5; Err: "select 6 error" Atomic: <nil> RELEASE 4; RELEASE 3;
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
View Source
var ( // ErrNeedsDb is the canonical error value when an attempt to create a Querier doesn't specify a DB ErrNeedsDb = errors.New("Need DB to create Querier") // ErrNeedsSavepointer is the canonical error value when an attempt to create a Querier doesn't specify a // Savepointer ErrNeedsSavepointer = errors.New("Need Savepointer to create Querier") // ErrNilQuerier is the canonical error value for when a nil Querier is used ErrNilQuerier = errors.New("nil Querier") // ErrInvalidQuerier is the canonical error value for when an invalid Querier is used ErrInvalidQuerier = errors.New("Invalid Querier") )
Functions ¶
Types ¶
type Error ¶
type Error struct { // Err is an error returned from Querier.Atomic()'s callback function Err error // Atomic is an error from within Querier.Atomic()'s implementation. // Usually such an error is the result of an improperly configured/created Querier or a DB error. Atomic error }
Error implements the error interface and is used to differentiate between Querier.Atomic() errors and Querier.Atomic() callback function errors
type Querier ¶
type Querier interface { QuerierBase // Atomic runs any SQL statement(s) with the given querier atomicly by wrapping the statement(s) // in a transaction or savepoint. // Any error returned by the callback function (or panic) will result in the rollback of the transaction // or rollback to the previous savepoint as appropriate. // Otherwise, the previous savepoint will be released or the transaction will be committed. // // Note: Atomic() is not safe for concurrent use by multiple goroutines. e.g. your SQL statements may be // interleaved and thus nonsensical. Atomic(f func(context.Context, Querier) error) *Error }
Querier provides an interface to interact with a SQL DB within an atomic transaction or savepoint
func NewQuerier ¶
func NewQuerier(ctx context.Context, db *sql.DB, savepointer savepointers.Savepointer, txOpts sql.TxOptions) (Querier, error)
NewQuerier creates a new Querier
func NewQuerierWithTxCreator ¶
func NewQuerierWithTxCreator(ctx context.Context, db *sql.DB, savepointer savepointers.Savepointer, txOpts sql.TxOptions, txCreator TxCreator) (Querier, error)
NewQuerierWithTxCreator creates a new Querier, allowing the transaction creation to be customized
type QuerierBase ¶ added in v0.3.1
type QuerierBase interface { Exec(query string, args ...interface{}) (sql.Result, error) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, 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 }
QuerierBase provides an interface containing database/sql methods shared between sql.DB and sql.Tx
Click to show internal directories.
Click to hide internal directories.