sqlx package - github.com/avito-tech/go-transaction-manager/drivers/sqlx/v2 - Go Packages (original) (raw)

Package sqlx is an implementation of trm.Transaction interface by Transaction for sqlx.Tx.

Example demonstrates the implementation of the Repository pattern by trm.Manager.

package main

import ( "context" "fmt"

"github.com/jmoiron/sqlx"
_ "github.com/mattn/go-sqlite3"

trmsqlx "github.com/avito-tech/go-transaction-manager/drivers/sqlx/v2"
"github.com/avito-tech/go-transaction-manager/trm/v2/manager"

)

// Example demonstrates the implementation of the Repository pattern by trm.Manager. func main() { db := newDB()

defer db.Close() //nolint:errcheck

sqlStmt := `CREATE TABLE IF NOT EXISTS user (user_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, username TEXT);`
_, err := db.Exec(sqlStmt)
checkErr(err, sqlStmt)

r := newRepo(db, trmsqlx.DefaultCtxGetter)

u := &user{
    Username: "username",
}

ctx := context.Background()
trManager := manager.Must(trmsqlx.NewDefaultFactory(db))

err = trManager.Do(ctx, func(ctx context.Context) error {
    if err := r.Save(ctx, u); err != nil {
        return err
    }

    return trManager.Do(ctx, func(ctx context.Context) error {
        u.Username = "new_username"

        return r.Save(ctx, u)
    })
})
checkErr(err)

userFromDB, err := r.GetByID(ctx, u.ID)
checkErr(err)

fmt.Println(userFromDB)

}

func newDB() *sqlx.DB { db, err := sqlx.Open("sqlite3", "file:test?mode=memory") checkErr(err)

return db

}

type repo struct { db *sqlx.DB getter *trmsqlx.CtxGetter }

func newRepo(db *sqlx.DB, c *trmsqlx.CtxGetter) *repo { return &repo{ db: db, getter: c, } }

type user struct { ID int64 Username string }

type userRow struct { ID int64 db:"user_id" Username string db:"username" }

func (r *repo) GetByID(ctx context.Context, id int64) (*user, error) { query := "SELECT * FROM user WHERE user_id = ?;"

row := userRow{}

err := r.getter.DefaultTrOrDB(ctx, r.db).GetContext(ctx, &row, r.db.Rebind(query), id)
if err != nil {
    return nil, err
}

return r.toModel(row), nil

}

func (r *repo) Save(ctx context.Context, u *user) error { isNew := u.ID == 0

query := `UPDATE user SET username = :username WHERE user_id = :user_id;`
if isNew {
    query = `INSERT INTO user (username) VALUES (:username);`
}

res, err := sqlx.NamedExecContext(
    ctx,
    r.getter.DefaultTrOrDB(ctx, r.db),
    r.db.Rebind(query),
    r.toRow(u),
)
if err != nil {
    return err
} else if !isNew {
    return nil
} else if u.ID, err = res.LastInsertId(); err != nil {
    return err
}

// For PostgreSql need to use NamedQueryContext with RETURNING
// DO UPDATE SET username = EXCLUDED.username RETURNING id;
// defer res.Next()
// if u.ID == 0 && res.Next() {
//		if err = res.Scan(&u.ID); err != nil {
//			return err
//		}
//	}

return nil

}

func (r *repo) toRow(model *user) userRow { return userRow{ ID: model.ID, Username: model.Username, } }

func (r *repo) toModel(row userRow) *user { return &user{ ID: row.ID, Username: row.Username, } }

func checkErr(err error, args ...interface{}) { if err != nil { panic(fmt.Sprint(append([]interface{}{err}, args...)...)) } }

Output:

&{1 new_username}

Example demonstrates a work of manager.ChainedMW.

// connect DB db1 := newDB() defer db1.Close() //nolint:errcheck

db2 := newDB() defer db2.Close() //nolint:errcheck

// create DB sqlStmt := CREATE TABLE IF NOT EXISTS user (user_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, username TEXT); _, err := db1.Exec(sqlStmt) checkErr(err, sqlStmt)

_, err = db2.Exec(sqlStmt) checkErr(err, sqlStmt)

// init manager ctxKey1 := trmcontext.Generate() m1 := manager.Must( trmsqlx.NewDefaultFactory(db1), manager.WithSettings(settings.Must(settings.WithCtxKey(ctxKey1))), ) r1 := newRepo(db1, trmsqlx.NewCtxGetter(trmcontext.New(ctxKey1)))

ctxKey2 := trmcontext.Generate() m2 := manager.Must( trmsqlx.NewDefaultFactory(db2), manager.WithSettings(settings.Must(settings.WithCtxKey(ctxKey2))), ) r2 := newRepo(db2, trmsqlx.NewCtxGetter(trmcontext.New(ctxKey2)))

chainedManager := manager.MustChained([]trm.Manager{m1, m2})

u := &user{Username: "username"} ctx := context.Background()

err = chainedManager.Do(ctx, func(ctx context.Context) error { if err := r1.Save(ctx, u); err != nil { return err }

if err := r2.Save(ctx, u); err != nil {
    return err
}

return chainedManager.Do(ctx, func(ctx context.Context) error {
    u.Username = "new_username"

    if err = r1.Save(ctx, u); err != nil {
        return err
    }

    return r2.Save(ctx, u)
})

}) checkErr(err)

userFromDB1, err := r1.GetByID(ctx, u.ID) checkErr(err)

userFromDB2, err := r1.GetByID(ctx, u.ID) checkErr(err)

fmt.Println(userFromDB1, userFromDB2)

Output:

&{1 new_username} &{1 new_username}

This section is empty.

DefaultCtxGetter is the CtxGetter with settings.DefaultCtxKey.

NewDefaultFactory creates default trm.Transaction(sqlx.Tx).

NewFactory creates trm.Transaction(sql.Tx).

type CtxGetter struct {

}

CtxGetter gets Tr from trm.CtxManager by casting trm.Transaction to Tr.

func NewCtxGetter(c trm.CtxManager) *CtxGetter

NewCtxGetter returns *CtxGetter to get Tr from context.Context.

DefaultTrOrDB returns Tr from context.Context or DB(Tr) otherwise.

TrOrDB returns Tr from context.Context by trm.CtxKey or DB(Tr) otherwise.

type Tr interface { sqlx.ExtContext

[sqlx](/github.com/jmoiron/sqlx).[Preparer](/github.com/jmoiron/sqlx#Preparer)
Preparex(query [string](/builtin#string)) (*[sqlx](/github.com/jmoiron/sqlx).[Stmt](/github.com/jmoiron/sqlx#Stmt), [error](/builtin#error))
PreparexContext(ctx [context](/context).[Context](/context#Context), query [string](/builtin#string)) (*[sqlx](/github.com/jmoiron/sqlx).[Stmt](/github.com/jmoiron/sqlx#Stmt), [error](/builtin#error))
PrepareNamed(query [string](/builtin#string)) (*[sqlx](/github.com/jmoiron/sqlx).[NamedStmt](/github.com/jmoiron/sqlx#NamedStmt), [error](/builtin#error))
PrepareNamedContext(ctx [context](/context).[Context](/context#Context), query [string](/builtin#string)) (*[sqlx](/github.com/jmoiron/sqlx).[NamedStmt](/github.com/jmoiron/sqlx#NamedStmt), [error](/builtin#error))

[sqlx](/github.com/jmoiron/sqlx).[Execer](/github.com/jmoiron/sqlx#Execer)
MustExec(query [string](/builtin#string), args ...interface{}) [sql](/database/sql).[Result](/database/sql#Result)
MustExecContext(ctx [context](/context).[Context](/context#Context), query [string](/builtin#string), args ...interface{}) [sql](/database/sql).[Result](/database/sql#Result)
NamedExec(query [string](/builtin#string), arg interface{}) ([sql](/database/sql).[Result](/database/sql#Result), [error](/builtin#error))
NamedExecContext(ctx [context](/context).[Context](/context#Context), query [string](/builtin#string), arg interface{}) ([sql](/database/sql).[Result](/database/sql#Result), [error](/builtin#error))

[sqlx](/github.com/jmoiron/sqlx).[Queryer](/github.com/jmoiron/sqlx#Queryer)
QueryRow(query [string](/builtin#string), args ...interface{}) *[sql](/database/sql).[Row](/database/sql#Row)
QueryRowContext(ctx [context](/context).[Context](/context#Context), query [string](/builtin#string), args ...interface{}) *[sql](/database/sql).[Row](/database/sql#Row)
NamedQuery(query [string](/builtin#string), arg interface{}) (*[sqlx](/github.com/jmoiron/sqlx).[Rows](/github.com/jmoiron/sqlx#Rows), [error](/builtin#error))

Select(dest interface{}, query [string](/builtin#string), args ...interface{}) [error](/builtin#error)
SelectContext(ctx [context](/context).[Context](/context#Context), dest interface{}, query [string](/builtin#string), args ...interface{}) [error](/builtin#error)

Get(dest interface{}, query [string](/builtin#string), args ...interface{}) [error](/builtin#error)
GetContext(ctx [context](/context).[Context](/context#Context), dest interface{}, query [string](/builtin#string), args ...interface{}) [error](/builtin#error)

}

Tr is an interface to work with sqlx.DB or sqlx.Tx. Stmtx, StmtxContext, NamedStmt and NamedStmtContext are not implemented!

type Transaction struct {

}

Transaction is trm.Transaction for sqlx.Tx.

NewTransaction creates trm.Transaction for sqlx.Tx.

Begin nested transaction by save point.

func (t *Transaction) Closed() <-chan struct{}

Closed returns a channel that's closed when transaction committed or rolled back.

Commit closes the trm.Transaction.

func (t *Transaction) IsActive() bool

IsActive returns true if the transaction started but not committed or rolled back.

Rollback the trm.Transaction.

func (t *Transaction) Transaction() interface{}

Transaction returns the real transaction sqlx.Tx. trm.NestedTrFactory returns IsActive as true while trm.Transaction is opened.