Files
sqlite3/tx.go

203 lines
4.4 KiB
Go
Raw Permalink Normal View History

2023-02-26 03:22:08 +00:00
package sqlite3
import (
"context"
"errors"
"fmt"
2023-03-08 16:29:29 +00:00
"math/rand"
2023-02-26 03:22:08 +00:00
"runtime"
2023-03-08 16:29:29 +00:00
"strconv"
2023-02-26 03:22:08 +00:00
)
2023-03-01 13:27:50 +00:00
// Tx is an in-progress database transaction.
2023-03-08 16:29:29 +00:00
//
// https://www.sqlite.org/lang_transaction.html
2023-02-26 03:22:08 +00:00
type Tx struct {
c *Conn
}
// Begin starts a deferred transaction.
//
// https://www.sqlite.org/lang_transaction.html
func (c *Conn) Begin() Tx {
2023-03-08 16:29:29 +00:00
// BEGIN even if interrupted.
err := c.txExecInterrupted(`BEGIN DEFERRED`)
if err != nil {
2023-02-26 03:22:08 +00:00
panic(err)
}
return Tx{c}
}
// BeginImmediate starts an immediate transaction.
//
// https://www.sqlite.org/lang_transaction.html
func (c *Conn) BeginImmediate() (Tx, error) {
err := c.Exec(`BEGIN IMMEDIATE`)
if err != nil {
return Tx{}, err
}
return Tx{c}, nil
}
// BeginExclusive starts an exclusive transaction.
//
// https://www.sqlite.org/lang_transaction.html
func (c *Conn) BeginExclusive() (Tx, error) {
err := c.Exec(`BEGIN EXCLUSIVE`)
if err != nil {
return Tx{}, err
}
return Tx{c}, nil
}
2023-03-01 10:34:08 +00:00
// End calls either [Tx.Commit] or [Tx.Rollback]
2023-02-26 03:22:08 +00:00
// depending on whether *error points to a nil or non-nil error.
//
// This is meant to be deferred:
//
// func doWork(conn *sqlite3.Conn) (err error) {
// tx := conn.Begin()
// defer tx.End(&err)
//
// // ... do work in the transaction
// }
//
2023-03-01 10:34:08 +00:00
// https://www.sqlite.org/lang_transaction.html
2023-02-26 03:22:08 +00:00
func (tx Tx) End(errp *error) {
recovered := recover()
if recovered != nil {
defer panic(recovered)
}
2023-03-08 16:29:29 +00:00
if (errp == nil || *errp == nil) && recovered == nil {
2023-02-26 03:22:08 +00:00
// Success path.
2023-03-08 16:29:29 +00:00
if tx.c.GetAutocommit() { // There is nothing to commit.
return
}
2023-02-26 03:22:08 +00:00
*errp = tx.Commit()
if *errp == nil {
return
}
2023-03-08 16:29:29 +00:00
// Fall through to the error path.
2023-02-26 03:22:08 +00:00
}
// Error path.
2023-03-08 16:29:29 +00:00
if tx.c.GetAutocommit() { // There is nothing to rollback.
return
}
2023-02-26 03:22:08 +00:00
err := tx.Rollback()
if err != nil {
panic(err)
}
}
2023-03-01 10:34:08 +00:00
// Commit commits the transaction.
//
// https://www.sqlite.org/lang_transaction.html
2023-02-26 03:22:08 +00:00
func (tx Tx) Commit() error {
return tx.c.Exec(`COMMIT`)
}
2023-03-08 16:29:29 +00:00
// Rollback rolls back the transaction,
// even if the connection has been interrupted.
2023-03-01 10:34:08 +00:00
//
// https://www.sqlite.org/lang_transaction.html
2023-02-26 03:22:08 +00:00
func (tx Tx) Rollback() error {
2023-03-08 16:29:29 +00:00
return tx.c.txExecInterrupted(`ROLLBACK`)
2023-02-26 03:22:08 +00:00
}
2023-03-08 16:29:29 +00:00
// Savepoint is a marker within a transaction
// that allows for partial rollback.
2023-02-26 03:22:08 +00:00
//
2023-03-08 16:29:29 +00:00
// https://www.sqlite.org/lang_savepoint.html
type Savepoint struct {
c *Conn
name string
}
// Savepoint establishes a new transaction savepoint.
2023-02-26 03:22:08 +00:00
//
// https://www.sqlite.org/lang_savepoint.html
2023-03-08 16:29:29 +00:00
func (c *Conn) Savepoint() Savepoint {
name := "sqlite3.Savepoint"
2023-02-26 03:22:08 +00:00
var pc [1]uintptr
if n := runtime.Callers(2, pc[:]); n > 0 {
frames := runtime.CallersFrames(pc[:n])
frame, _ := frames.Next()
if frame.Function != "" {
name = frame.Function
}
}
2023-03-08 16:29:29 +00:00
// Names can be reused; this makes catching bugs more likely.
name += "#" + strconv.Itoa(int(rand.Int31()))
2023-02-26 03:22:08 +00:00
2023-03-08 16:29:29 +00:00
err := c.txExecInterrupted(fmt.Sprintf("SAVEPOINT %q;", name))
2023-02-26 03:22:08 +00:00
if err != nil {
panic(err)
}
2023-03-08 16:29:29 +00:00
return Savepoint{c: c, name: name}
}
2023-02-26 03:22:08 +00:00
2023-03-08 16:29:29 +00:00
// Release releases the savepoint rolling back any changes
// if *error points to a non-nil error.
//
// This is meant to be deferred:
//
// func doWork(conn *sqlite3.Conn) (err error) {
// savept := conn.Savepoint()
// defer savept.Release(&err)
//
// // ... do work in the transaction
// }
func (s Savepoint) Release(errp *error) {
recovered := recover()
if recovered != nil {
defer panic(recovered)
}
2023-02-26 03:22:08 +00:00
2023-03-08 16:29:29 +00:00
if (errp == nil || *errp == nil) && recovered == nil {
// Success path.
if s.c.GetAutocommit() { // There is nothing to commit.
2023-02-26 03:22:08 +00:00
return
}
2023-03-08 16:29:29 +00:00
*errp = s.c.Exec(fmt.Sprintf("RELEASE %q;", s.name))
if *errp == nil {
return
2023-02-26 03:22:08 +00:00
}
2023-03-08 16:29:29 +00:00
// Fall through to the error path.
}
2023-02-26 03:22:08 +00:00
2023-03-08 16:29:29 +00:00
// Error path.
if s.c.GetAutocommit() { // There is nothing to rollback.
return
}
// ROLLBACK and RELEASE even if interrupted.
err := s.c.txExecInterrupted(fmt.Sprintf(`
ROLLBACK TO %[1]q;
RELEASE %[1]q;
`, s.name))
if err != nil {
panic(err)
}
}
// Rollback rolls the transaction back to the savepoint,
// even if the connection has been interrupted.
// Rollback does not release the savepoint.
//
// https://www.sqlite.org/lang_transaction.html
func (s Savepoint) Rollback() error {
// ROLLBACK even if interrupted.
return s.c.txExecInterrupted(fmt.Sprintf("ROLLBACK TO %q;", s.name))
}
func (c *Conn) txExecInterrupted(sql string) error {
err := c.Exec(sql)
if errors.Is(err, INTERRUPT) {
2023-02-26 03:22:08 +00:00
old := c.SetInterrupt(context.Background())
defer c.SetInterrupt(old)
2023-03-08 16:29:29 +00:00
err = c.Exec(sql)
2023-02-26 03:22:08 +00:00
}
2023-03-08 16:29:29 +00:00
return err
2023-02-26 03:22:08 +00:00
}