使用Golang预备语句的原始SQL事务

I'm having trouble finding some examples that do three of the following things:

1) Allow raw sql transactions in golang.

2) Use prepared statements.

3) Rollback on query failures.

I would like to do something like this, but with prepared statements.

    stmt, stmt_err := db.Prepare(`
            BEGIN TRANSACTION;

            -- Insert record into first table.

            INSERT INTO table_1 (
                    thing_1,
                    whatever)
            VALUES($1,$2);

            -- Inert record into second table.

            INSERT INTO table_2 (
                    thing_2,
                    whatever)
            VALUES($3,$4);

            END TRANSACTION;
            `)
    if stmt_err != nil {
            return stmt_err
    }   
    res, res_err := stmt.Exec(
            thing_1,
            whatever,
            thing_2,
            whatever)

When I run this, I get this error: pq: cannot insert multiple commands into a prepared statement

What gives? Are ACID compliant transactions even possible in golang? I cannot find an example.

EDIT no examples here.

Yes Go has a great implementation of sql transactions. We start the transaction with db.Begin and we can end it with tx.Commit if everything goes good or with tx.Rollback in case of error.

type Tx struct { }

Tx is an in-progress database transaction.

A transaction must end with a call to Commit or Rollback.

After a call to Commit or Rollback, all operations on the transaction fail with ErrTxDone.

The statements prepared for a transaction by calling the transaction's Prepare or Stmt methods are closed by the call to Commit or Rollback.

Also note that we prepare queries with the transaction variable tx.Prepare(...)

Your function may looks like this:

func doubleInsert(db *sql.DB) error {

    tx, err := db.Begin()
    if err != nil {
        return err
    }

    {
        stmt, err := tx.Prepare(`INSERT INTO table_1 (thing_1, whatever)
                     VALUES($1,$2);`)
        if err != nil {
            tx.Rollback()
            return err
        }
        defer stmt.Close()

        if _, err := stmt.Exec(thing_1, whatever); err != nil {
            tx.Rollback() // return an error too, we may want to wrap them
            return err
        }
    }

    {
        stmt, err := tx.Prepare(`INSERT INTO table_2 (thing_2, whatever)
                     VALUES($1, $2);`)
        if err != nil {
            tx.Rollback()
            return err
        }
        defer stmt.Close()

        if _, err := stmt.Exec(thing_2, whatever); err != nil {
            tx.Rollback() // return an error too, we may want to wrap them
            return err
        }
    }

    return tx.Commit()
}

I have a full example here

I came up with a possible solution to rollback on any failure without any significant drawbacks. I am pretty new to Golang though, I could be wrong.

func CloseTransaction(tx *sql.Tx, commit *bool) {
  if *commit {
    log.Println("Commit sql transaction")
    if err := tx.Commit(); err != nil {
      log.Panic(err)
    }
  } else {
    log.Println("Rollback sql transcation")
    if err := tx.Rollback(); err != nil {
      log.Panic(err)
    }
  }
}

func MultipleSqlQuriesWithTx(db *sql.DB, .. /* some parameter(s) */)  (.. .. /* some named return parameter(s) */, err error) {
  tx, err := db.Begin()
  if err != nil {
    return
  }
  commitTx := false
  defer CloseTransaction(tx, &commitTx)

  // First sql query
  stmt, err := tx.Prepare(..) // some raw sql
  if err != nil {
    return
  }
  defer stmt.Close()

  res, err := stmt.Exec(..) // some var args
  if err != nil {
    return
  }

  // Second sql query
  stmt, err := tx.Prepare(..) // some raw sql
  if err != nil {
    return
  }
  defer stmt.Close()

  res, err := stmt.Exec(..) // some var args
  if err != nil {
    return
  }

  /*
    more tx sql statements and queries here
  */

  // success, commit and return result
  commitTx = true
  return
}