0

Let we have 2 methods

func createClient(db *sql.DB, ...) error // creates a new client
func createOrder(db *sql.DB, ...) error // creates a new order

Each of these method can be run on some *sql.DB, for example,

var mainDb *sql.DB // initialized somewhere in main() method

func orderHandler(r,w) {
   ...
   err := createOrder(mainDb, ...)
   ...
}

But what if i want to run both these methods in one transaction. For example,

func importOrdersHandler(r,w) {
     ...
     tx, err:= mainDb.Begin()
     ...
     err = createClient(tx, clientData) // but method defined on *sql.DB, not *sql.Tx ?!
     err = createOrder(tx, orderData)
     ...

My solution:

Define a wrapper around *sql.DB, *sql.Tx:

type Database struct {
   db *sql.DB
   tx *sql.Tx
}

// Redefine all standart methods from sql package, such as
// Exec(...)
// Query(..)
// QueryRow(...)

// Also method to run commands in one transaction:

func TransactionDo(db *sql.DB, body func(*Database) error) error {
  tx, err :=  db.Begin()
  ...
  d, err := NewDb(nil, tx)
  ....
  err = body(d)
  ...
  return tx.Commit()
}

In this way our ordersImportHandler can be realized like that:

 func importOrdersHandler(r,w) {

   for row := range parseFile(formFile(r)) {
     ...
     err := TransactionDo(mainDb, func(d *Database) error {
        err = createClient(d, clientData)
        ...
        err = createOrder(d, orderData)
    // if an error occurs in TransactionDo then transaction wiil be
    // rollbacked, else commit it

createClient, createOrder must be rewrited to use *Database object insted of *sql.DB

What do you think about such solution? May be there another better and idiomatic way to do that

2 Answers 2

2

I used an interface (that the library squirrel also uses)

type Database interface {
    Exec(query string, args ...interface{}) (sql.Result, error)
    Query(query string, args ...interface{}) (*sql.Rows, error)
    QueryRow(query string, args ...interface{}) *sql.Row
}

Then you can just pass write the functions

func createClient(db *Database, ...) error // creates a new client
func createOrder(db *Database, ...) error // creates a new order
Sign up to request clarification or add additional context in comments.

1 Comment

Oh, looks like you were faster. But yea, this is the idiomatic way I think.
1

Simpler way is to use interfaces.

type Execer interface {
    Exec(query string, args ...interface{}) (sql.Result, error)
}

Both sql.Tx and sql.Db satisfy this interface so you can redefine you createClient and createOrder funtions to take an Execer as an argument and there, you can now pass either Tx or Db to them.

3 Comments

Agree, but i can use QueryRow method inside createClient for example to get generated id of new client. QueryRow is not an interface method
I use Postgres where LastInsertId is not defined as i know and you must use RETURNING id clause explicitly
@aleksandr QueryRow method is also available to both Db and Tx, so if you need it you can add that (and any other methods implemented by both Db and Tx) to the interface. And then you should probably name the interface something other than Execer. But if you need the method just for getting the last id, you could just use RETURNING id and read it from the sql.Result.LastInsertId().

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.