satomic

package module
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 5, 2020 License: Apache-2.0 Imports: 5 Imported by: 0

README

satomic

Build Status Code Coverage GoDoc Go Report Card GitHub Release Supported Go versions

satomic is a Golang package that makes managing nested SQL transactions/savepoints easier

Overview

Create a Querier and use the Atomic() method. Any SQL statements inside the Atomic() method's callback function will be appropriately wrapped in a transaction or savepoint. Transaction and savepoint management will be handled for you automatically. Any error returned by the callback function (or unrecovered panic) will rollback the savepoint or transaction accordingly. A new Querier instance is also provided to the Atomic() method's callback function to allow nesting savepoints.

Status

satomic is not stable yet, so the interfaces may change in a non-backwards compatible manner. satomic follows Semantic Versioning 2.0.0. While the major version number is 0, all backwards compatible changes will be denoted by a minor version number bump. All other changes will increase the patch version number.

Example usage

package main

import (
    "context"
    "database/sql"
    "github.com/dhui/satomic"
    "github.com/dhui/satomic/savepointers/postgres"
)

// Error handling omitted for brevity. Actual code should handle errors
func main() {
    ctx := context.Background()
    var db *sql.DB  // Use an actual db

    // savepointer should match the db driver used
    q, _ := satomic.NewQuerier(ctx, db, postgres.Savepointer{}, sql.TxOptions{})

    q.Atomic(func(ctx context.Context, q satomic.Querier) error {
        // In transaction
        var dummy int
        q.QueryRowContext(ctx, "SELECT 1;").Scan(&dummy)

        q.Atomic(func(ctx context.Context, q satomic.Querier) error {
            // In first savepoint
            return q.QueryRowContext(ctx, "SELECT 2;").Scan(&dummy)
        })

        q.Atomic(func(ctx context.Context, q satomic.Querier) error {
            // In second savepoint
            q.QueryRowContext(ctx, "SELECT 3;").Scan(&dummy)

            q.Atomic(func(ctx context.Context, q satomic.Querier) error {
                // In third savepoint
                q.QueryRowContext(ctx, "SELECT 4;").Scan(&dummy)
                return nil
            })

            return nil
        })

        return nil
    })
}

A more complete example can be found in the GoDoc

For usage with sqlx, use github.com/dhui/satomic/satomicx

What's with the name?

Go SQL atomic => satomic


Inspired by sqlexp and Django's atomic decorator/context manager.

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

func DefaultTxCreator

func DefaultTxCreator(ctx context.Context, db *sql.DB, txOpts sql.TxOptions) (*sql.Tx, error)

DefaultTxCreator is the default TxCreator to be used

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

func (*Error) Error

func (e *Error) Error() string

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

type TxCreator

type TxCreator func(context.Context, *sql.DB, sql.TxOptions) (*sql.Tx, error)

TxCreator is used to create transactions for a Querier

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL