Files
sqlite3/save.go
2023-02-24 14:56:49 +00:00

78 lines
1.7 KiB
Go

package sqlite3
import (
"context"
"fmt"
"runtime"
)
// Savepoint creates a named SQLite transaction using SAVEPOINT.
//
// On success Savepoint returns a release func that will call
// either RELEASE or ROLLBACK depending on whether the parameter *error
// points to a nil or non-nil error.
//
// This is meant to be deferred:
//
// func doWork(conn *sqlite3.Conn) (err error) {
// defer conn.Savepoint()(&err)
//
// // ... do work in the transaction
// }
func (conn *Conn) Savepoint() (release func(*error)) {
name := "sqlite3.Savepoint" // names can be reused
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
}
}
err := conn.Exec(fmt.Sprintf("SAVEPOINT %q;", name))
if err != nil {
return func(errp *error) {
if *errp == nil {
*errp = err
}
}
}
return func(errp *error) {
recovered := recover()
if recovered != nil {
defer panic(recovered)
}
if conn.GetAutocommit() {
// There is nothing to commit/rollback.
return
}
if *errp == nil && recovered == nil {
// Success path.
// RELEASE the savepoint successfully.
*errp = conn.Exec(fmt.Sprintf("RELEASE %q;", name))
if *errp == nil {
return
}
// Possible interrupt, fall through to the error path.
}
// Error path.
// Always ROLLBACK even if the connection has been interrupted.
old := conn.SetInterrupt(context.Background())
defer conn.SetInterrupt(old)
err := conn.Exec(fmt.Sprintf("ROLLBACK TO %q;", name))
if err != nil {
panic(err)
}
err = conn.Exec(fmt.Sprintf("RELEASE %q;", name))
if err != nil {
panic(err)
}
}
}