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-11-07 15:19:40 +00:00
|
|
|
"strings"
|
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
|
|
|
//
|
2023-11-09 16:35:45 +00:00
|
|
|
// https://sqlite.org/lang_transaction.html
|
2023-02-26 03:22:08 +00:00
|
|
|
type Tx struct {
|
|
|
|
|
c *Conn
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Begin starts a deferred transaction.
|
|
|
|
|
//
|
2023-11-09 16:35:45 +00:00
|
|
|
// https://sqlite.org/lang_transaction.html
|
2023-02-26 03:22:08 +00:00
|
|
|
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.
|
|
|
|
|
//
|
2023-11-09 16:35:45 +00:00
|
|
|
// https://sqlite.org/lang_transaction.html
|
2023-02-26 03:22:08 +00:00
|
|
|
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.
|
|
|
|
|
//
|
2023-11-09 16:35:45 +00:00
|
|
|
// https://sqlite.org/lang_transaction.html
|
2023-02-26 03:22:08 +00:00
|
|
|
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:
|
|
|
|
|
//
|
2023-11-22 13:11:23 +00:00
|
|
|
// func doWork(db *sqlite3.Conn) (err error) {
|
|
|
|
|
// tx := db.Begin()
|
2023-02-26 03:22:08 +00:00
|
|
|
// defer tx.End(&err)
|
|
|
|
|
//
|
|
|
|
|
// // ... do work in the transaction
|
|
|
|
|
// }
|
|
|
|
|
//
|
2023-11-09 16:35:45 +00:00
|
|
|
// https://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-04-21 13:33:24 +01:00
|
|
|
if *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.
|
|
|
|
|
//
|
2023-11-09 16:35:45 +00:00
|
|
|
// https://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
|
|
|
//
|
2023-11-09 16:35:45 +00:00
|
|
|
// https://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-11-09 16:35:45 +00:00
|
|
|
// https://sqlite.org/lang_savepoint.html
|
2023-03-08 16:29:29 +00:00
|
|
|
type Savepoint struct {
|
|
|
|
|
c *Conn
|
|
|
|
|
name string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Savepoint establishes a new transaction savepoint.
|
2023-02-26 03:22:08 +00:00
|
|
|
//
|
2023-11-09 16:35:45 +00:00
|
|
|
// https://sqlite.org/lang_savepoint.html
|
2023-03-08 16:29:29 +00:00
|
|
|
func (c *Conn) Savepoint() Savepoint {
|
|
|
|
|
// Names can be reused; this makes catching bugs more likely.
|
2023-11-07 15:19:40 +00:00
|
|
|
name := saveptName() + "_" + 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-11-07 15:19:40 +00:00
|
|
|
func saveptName() (name string) {
|
|
|
|
|
defer func() {
|
|
|
|
|
if name == "" {
|
|
|
|
|
name = "sqlite3.Savepoint"
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
var pc [8]uintptr
|
|
|
|
|
n := runtime.Callers(3, pc[:])
|
|
|
|
|
if n <= 0 {
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
frames := runtime.CallersFrames(pc[:n])
|
|
|
|
|
frame, more := frames.Next()
|
|
|
|
|
for more && (strings.HasPrefix(frame.Function, "database/sql.") ||
|
|
|
|
|
strings.HasPrefix(frame.Function, "github.com/ncruces/go-sqlite3/driver.")) {
|
|
|
|
|
frame, more = frames.Next()
|
|
|
|
|
}
|
|
|
|
|
return frame.Function
|
|
|
|
|
}
|
|
|
|
|
|
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:
|
|
|
|
|
//
|
2023-11-22 13:11:23 +00:00
|
|
|
// func doWork(db *sqlite3.Conn) (err error) {
|
|
|
|
|
// savept := db.Savepoint()
|
2023-03-08 16:29:29 +00:00
|
|
|
// 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-04-21 13:33:24 +01:00
|
|
|
if *errp == nil && recovered == nil {
|
2023-03-08 16:29:29 +00:00
|
|
|
// 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.
|
|
|
|
|
//
|
2023-11-09 16:35:45 +00:00
|
|
|
// https://sqlite.org/lang_transaction.html
|
2023-03-08 16:29:29 +00:00
|
|
|
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
|
|
|
}
|
2024-01-17 15:39:13 +00:00
|
|
|
|
|
|
|
|
// TxnState starts a deferred transaction.
|
|
|
|
|
//
|
|
|
|
|
// https://sqlite.org/c3ref/txn_state.html
|
|
|
|
|
func (c *Conn) TxnState(schema string) TxnState {
|
|
|
|
|
var ptr uint32
|
|
|
|
|
if schema != "" {
|
|
|
|
|
defer c.arena.mark()()
|
|
|
|
|
ptr = c.arena.string(schema)
|
|
|
|
|
}
|
|
|
|
|
r := c.call("sqlite3_txn_state", uint64(c.handle), uint64(ptr))
|
|
|
|
|
return TxnState(r)
|
|
|
|
|
}
|