353
vendor/zombiezen.com/go/sqlite/sqlitex/exec.go
generated
vendored
Normal file
353
vendor/zombiezen.com/go/sqlite/sqlitex/exec.go
generated
vendored
Normal file
@@ -0,0 +1,353 @@
|
||||
// Copyright (c) 2018 David Crawshaw <david@zentus.com>
|
||||
// Copyright (c) 2021 Ross Light <rosss@zombiezen.com>
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
//
|
||||
// SPDX-License-Identifier: ISC
|
||||
|
||||
// Package sqlitex provides utilities for working with SQLite.
|
||||
package sqlitex
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"zombiezen.com/go/sqlite"
|
||||
"zombiezen.com/go/sqlite/fs"
|
||||
)
|
||||
|
||||
// ExecOptions is the set of optional arguments executing a statement.
|
||||
type ExecOptions struct {
|
||||
// Args is the set of positional arguments to bind to the statement. The first
|
||||
// element in the slice is ?1. See https://sqlite.org/lang_expr.html for more
|
||||
// details.
|
||||
Args []interface{}
|
||||
// Named is the set of named arguments to bind to the statement. Keys must
|
||||
// start with ':', '@', or '$'. See https://sqlite.org/lang_expr.html for more
|
||||
// details.
|
||||
Named map[string]interface{}
|
||||
// ResultFunc is called for each result row. If ResultFunc returns an error
|
||||
// then iteration ceases and Exec returns the error value.
|
||||
ResultFunc func(stmt *sqlite.Stmt) error
|
||||
}
|
||||
|
||||
// Exec executes an SQLite query.
|
||||
//
|
||||
// For each result row, the resultFn is called.
|
||||
// Result values can be read by resultFn using stmt.Column* methods.
|
||||
// If resultFn returns an error then iteration ceases and Exec returns
|
||||
// the error value.
|
||||
//
|
||||
// Any args provided to Exec are bound to numbered parameters of the
|
||||
// query using the Stmt Bind* methods. Basic reflection on args is used
|
||||
// to map:
|
||||
//
|
||||
// integers to BindInt64
|
||||
// floats to BindFloat
|
||||
// []byte to BindBytes
|
||||
// string to BindText
|
||||
// bool to BindBool
|
||||
//
|
||||
// All other kinds are printed using fmt.Sprintf("%v", v) and passed
|
||||
// to BindText.
|
||||
//
|
||||
// Exec is implemented using the Stmt prepare mechanism which allows
|
||||
// better interactions with Go's type system and avoids pitfalls of
|
||||
// passing a Go closure to cgo.
|
||||
//
|
||||
// As Exec is implemented using Conn.Prepare, subsequent calls to Exec
|
||||
// with the same statement will reuse the cached statement object.
|
||||
//
|
||||
// Typical use:
|
||||
//
|
||||
// conn := dbpool.Get()
|
||||
// defer dbpool.Put(conn)
|
||||
//
|
||||
// if err := sqlitex.Exec(conn, "INSERT INTO t (a, b, c, d) VALUES (?, ?, ?, ?);", nil, "a1", 1, 42, 1); err != nil {
|
||||
// // handle err
|
||||
// }
|
||||
//
|
||||
// var a []string
|
||||
// var b []int64
|
||||
// fn := func(stmt *sqlite.Stmt) error {
|
||||
// a = append(a, stmt.ColumnText(0))
|
||||
// b = append(b, stmt.ColumnInt64(1))
|
||||
// return nil
|
||||
// }
|
||||
// err := sqlutil.Exec(conn, "SELECT a, b FROM t WHERE c = ? AND d = ?;", fn, 42, 1)
|
||||
// if err != nil {
|
||||
// // handle err
|
||||
// }
|
||||
func Exec(conn *sqlite.Conn, query string, resultFn func(stmt *sqlite.Stmt) error, args ...interface{}) error {
|
||||
stmt, err := conn.Prepare(query)
|
||||
if err != nil {
|
||||
return annotateErr(err)
|
||||
}
|
||||
err = exec(stmt, &ExecOptions{
|
||||
Args: args,
|
||||
ResultFunc: resultFn,
|
||||
})
|
||||
resetErr := stmt.Reset()
|
||||
if err == nil {
|
||||
err = resetErr
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ExecFS executes the single statement in the given SQL file.
|
||||
// ExecFS is implemented using Conn.Prepare, so subsequent calls to ExecFS with the
|
||||
// same statement will reuse the cached statement object.
|
||||
func ExecFS(conn *sqlite.Conn, fsys fs.FS, filename string, opts *ExecOptions) error {
|
||||
query, err := readString(fsys, filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("exec: %w", err)
|
||||
}
|
||||
|
||||
stmt, err := conn.Prepare(strings.TrimSpace(query))
|
||||
if err != nil {
|
||||
return fmt.Errorf("exec %s: %w", filename, err)
|
||||
}
|
||||
err = exec(stmt, opts)
|
||||
resetErr := stmt.Reset()
|
||||
if err != nil {
|
||||
// Don't strip the error query: we already do this inside exec.
|
||||
return fmt.Errorf("exec %s: %w", filename, err)
|
||||
}
|
||||
if resetErr != nil {
|
||||
return fmt.Errorf("exec %s: %w", filename, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExecTransient executes an SQLite query without caching the
|
||||
// underlying query.
|
||||
// The interface is exactly the same as Exec.
|
||||
//
|
||||
// It is the spiritual equivalent of sqlite3_exec.
|
||||
func ExecTransient(conn *sqlite.Conn, query string, resultFn func(stmt *sqlite.Stmt) error, args ...interface{}) (err error) {
|
||||
var stmt *sqlite.Stmt
|
||||
var trailingBytes int
|
||||
stmt, trailingBytes, err = conn.PrepareTransient(query)
|
||||
if err != nil {
|
||||
return annotateErr(err)
|
||||
}
|
||||
defer func() {
|
||||
ferr := stmt.Finalize()
|
||||
if err == nil {
|
||||
err = ferr
|
||||
}
|
||||
}()
|
||||
if trailingBytes != 0 {
|
||||
return fmt.Errorf("sqlitex.Exec: query %q has trailing bytes", query)
|
||||
}
|
||||
return exec(stmt, &ExecOptions{
|
||||
Args: args,
|
||||
ResultFunc: resultFn,
|
||||
})
|
||||
}
|
||||
|
||||
// ExecTransientFS executes the single statement in the given SQL file without
|
||||
// caching the underlying query.
|
||||
func ExecTransientFS(conn *sqlite.Conn, fsys fs.FS, filename string, opts *ExecOptions) error {
|
||||
query, err := readString(fsys, filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("exec: %w", err)
|
||||
}
|
||||
|
||||
stmt, _, err := conn.PrepareTransient(strings.TrimSpace(query))
|
||||
if err != nil {
|
||||
return fmt.Errorf("exec %s: %w", filename, err)
|
||||
}
|
||||
defer stmt.Finalize()
|
||||
err = exec(stmt, opts)
|
||||
resetErr := stmt.Reset()
|
||||
if err != nil {
|
||||
// Don't strip the error query: we already do this inside exec.
|
||||
return fmt.Errorf("exec %s: %w", filename, err)
|
||||
}
|
||||
if resetErr != nil {
|
||||
return fmt.Errorf("exec %s: %w", filename, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrepareTransientFS prepares an SQL statement from a file that is not cached by
|
||||
// the Conn. Subsequent calls with the same query will create new Stmts.
|
||||
// The caller is responsible for calling Finalize on the returned Stmt when the
|
||||
// Stmt is no longer needed.
|
||||
func PrepareTransientFS(conn *sqlite.Conn, fsys fs.FS, filename string) (*sqlite.Stmt, error) {
|
||||
query, err := readString(fsys, filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("prepare: %w", err)
|
||||
}
|
||||
stmt, _, err := conn.PrepareTransient(strings.TrimSpace(query))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("prepare %s: %w", filename, err)
|
||||
}
|
||||
return stmt, nil
|
||||
}
|
||||
|
||||
func exec(stmt *sqlite.Stmt, opts *ExecOptions) (err error) {
|
||||
if opts != nil {
|
||||
for i, arg := range opts.Args {
|
||||
setArg(stmt, i+1, reflect.ValueOf(arg))
|
||||
}
|
||||
if err := setNamed(stmt, opts.Named); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for {
|
||||
hasRow, err := stmt.Step()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !hasRow {
|
||||
break
|
||||
}
|
||||
if opts != nil && opts.ResultFunc != nil {
|
||||
if err := opts.ResultFunc(stmt); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setArg(stmt *sqlite.Stmt, i int, v reflect.Value) {
|
||||
switch v.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
stmt.BindInt64(i, v.Int())
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
stmt.BindInt64(i, int64(v.Uint()))
|
||||
case reflect.Float32, reflect.Float64:
|
||||
stmt.BindFloat(i, v.Float())
|
||||
case reflect.String:
|
||||
stmt.BindText(i, v.String())
|
||||
case reflect.Bool:
|
||||
stmt.BindBool(i, v.Bool())
|
||||
case reflect.Invalid:
|
||||
stmt.BindNull(i)
|
||||
default:
|
||||
if v.Kind() == reflect.Slice && v.Type().Elem().Kind() == reflect.Uint8 {
|
||||
stmt.BindBytes(i, v.Bytes())
|
||||
} else {
|
||||
stmt.BindText(i, fmt.Sprint(v.Interface()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setNamed(stmt *sqlite.Stmt, args map[string]interface{}) error {
|
||||
if len(args) == 0 {
|
||||
return nil
|
||||
}
|
||||
for i, count := 1, stmt.BindParamCount(); i <= count; i++ {
|
||||
name := stmt.BindParamName(i)
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
arg, present := args[name]
|
||||
if !present {
|
||||
return fmt.Errorf("missing parameter %s", name)
|
||||
}
|
||||
setArg(stmt, i, reflect.ValueOf(arg))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func annotateErr(err error) error {
|
||||
// TODO(maybe)
|
||||
// if err, isError := err.(sqlite.Error); isError {
|
||||
// if err.Loc == "" {
|
||||
// err.Loc = "Exec"
|
||||
// } else {
|
||||
// err.Loc = "Exec: " + err.Loc
|
||||
// }
|
||||
// return err
|
||||
// }
|
||||
return fmt.Errorf("sqlutil.Exec: %w", err)
|
||||
}
|
||||
|
||||
// ExecScript executes a script of SQL statements.
|
||||
//
|
||||
// The script is wrapped in a SAVEPOINT transaction,
|
||||
// which is rolled back on any error.
|
||||
func ExecScript(conn *sqlite.Conn, queries string) (err error) {
|
||||
defer Save(conn)(&err)
|
||||
|
||||
for {
|
||||
queries = strings.TrimSpace(queries)
|
||||
if queries == "" {
|
||||
break
|
||||
}
|
||||
var stmt *sqlite.Stmt
|
||||
var trailingBytes int
|
||||
stmt, trailingBytes, err = conn.PrepareTransient(queries)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
usedBytes := len(queries) - trailingBytes
|
||||
queries = queries[usedBytes:]
|
||||
_, err := stmt.Step()
|
||||
stmt.Finalize()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExecScriptFS executes a script of SQL statements from a file.
|
||||
//
|
||||
// The script is wrapped in a SAVEPOINT transaction, which is rolled back on
|
||||
// any error.
|
||||
func ExecScriptFS(conn *sqlite.Conn, fsys fs.FS, filename string, opts *ExecOptions) (err error) {
|
||||
queries, err := readString(fsys, filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("exec: %w", err)
|
||||
}
|
||||
|
||||
defer Save(conn)(&err)
|
||||
for {
|
||||
queries = strings.TrimSpace(queries)
|
||||
if queries == "" {
|
||||
return nil
|
||||
}
|
||||
stmt, trailingBytes, err := conn.PrepareTransient(queries)
|
||||
if err != nil {
|
||||
return fmt.Errorf("exec %s: %w", filename, err)
|
||||
}
|
||||
usedBytes := len(queries) - trailingBytes
|
||||
queries = queries[usedBytes:]
|
||||
err = exec(stmt, opts)
|
||||
stmt.Finalize()
|
||||
if err != nil {
|
||||
return fmt.Errorf("exec %s: %w", filename, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func readString(fsys fs.FS, filename string) (string, error) {
|
||||
f, err := fsys.Open(filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
content := new(strings.Builder)
|
||||
_, err = io.Copy(content, f)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%s: %w", filename, err)
|
||||
}
|
||||
return content.String(), nil
|
||||
}
|
||||
262
vendor/zombiezen.com/go/sqlite/sqlitex/pool.go
generated
vendored
Normal file
262
vendor/zombiezen.com/go/sqlite/sqlitex/pool.go
generated
vendored
Normal file
@@ -0,0 +1,262 @@
|
||||
// Copyright (c) 2018 David Crawshaw <david@zentus.com>
|
||||
// Copyright (c) 2021 Ross Light <rosss@zombiezen.com>
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
//
|
||||
// SPDX-License-Identifier: ISC
|
||||
|
||||
package sqlitex
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"zombiezen.com/go/sqlite"
|
||||
)
|
||||
|
||||
// Pool is a pool of SQLite connections.
|
||||
//
|
||||
// It is safe for use by multiple goroutines concurrently.
|
||||
//
|
||||
// Typically, a goroutine that needs to use an SQLite *Conn
|
||||
// Gets it from the pool and defers its return:
|
||||
//
|
||||
// conn := dbpool.Get(nil)
|
||||
// defer dbpool.Put(conn)
|
||||
//
|
||||
// As Get may block, a context can be used to return if a task
|
||||
// is cancelled. In this case the Conn returned will be nil:
|
||||
//
|
||||
// conn := dbpool.Get(ctx)
|
||||
// if conn == nil {
|
||||
// return context.Canceled
|
||||
// }
|
||||
// defer dbpool.Put(conn)
|
||||
type Pool struct {
|
||||
free chan *sqlite.Conn
|
||||
closed chan struct{}
|
||||
|
||||
mu sync.Mutex
|
||||
all map[*sqlite.Conn]context.CancelFunc
|
||||
}
|
||||
|
||||
// Open opens a fixed-size pool of SQLite connections.
|
||||
// A flags value of 0 defaults to:
|
||||
//
|
||||
// SQLITE_OPEN_READWRITE
|
||||
// SQLITE_OPEN_CREATE
|
||||
// SQLITE_OPEN_WAL
|
||||
// SQLITE_OPEN_URI
|
||||
// SQLITE_OPEN_NOMUTEX
|
||||
func Open(uri string, flags sqlite.OpenFlags, poolSize int) (pool *Pool, err error) {
|
||||
if uri == ":memory:" {
|
||||
return nil, strerror{msg: `sqlite: ":memory:" does not work with multiple connections, use "file::memory:?mode=memory"`}
|
||||
}
|
||||
|
||||
p := &Pool{
|
||||
free: make(chan *sqlite.Conn, poolSize),
|
||||
closed: make(chan struct{}),
|
||||
}
|
||||
defer func() {
|
||||
// If an error occurred, call Close outside the lock so this doesn't deadlock.
|
||||
if err != nil {
|
||||
p.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
if flags == 0 {
|
||||
flags = sqlite.OpenReadWrite |
|
||||
sqlite.OpenCreate |
|
||||
sqlite.OpenWAL |
|
||||
sqlite.OpenURI |
|
||||
sqlite.OpenNoMutex
|
||||
}
|
||||
|
||||
// TODO(maybe)
|
||||
// sqlitex_pool is also defined in package sqlite
|
||||
// const sqlitex_pool = sqlite.OpenFlags(0x01000000)
|
||||
// flags |= sqlitex_pool
|
||||
|
||||
p.all = make(map[*sqlite.Conn]context.CancelFunc)
|
||||
for i := 0; i < poolSize; i++ {
|
||||
conn, err := sqlite.OpenConn(uri, flags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.free <- conn
|
||||
p.all[conn] = func() {}
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Get returns an SQLite connection from the Pool.
|
||||
//
|
||||
// If no Conn is available, Get will block until at least one Conn is returned
|
||||
// with Put, or until either the Pool is closed or the context is canceled. If
|
||||
// no Conn can be obtained, nil is returned.
|
||||
//
|
||||
// The provided context is also used to control the execution lifetime of the
|
||||
// connection. See Conn.SetInterrupt for details.
|
||||
//
|
||||
// Applications must ensure that all non-nil Conns returned from Get are
|
||||
// returned to the same Pool with Put.
|
||||
//
|
||||
// Although ctx historically may be nil, this is not a recommended design
|
||||
// pattern.
|
||||
func (p *Pool) Get(ctx context.Context) *sqlite.Conn {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
select {
|
||||
case conn := <-p.free:
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
// TODO(maybe)
|
||||
// conn.SetTracer(&tracer{ctx: ctx})
|
||||
conn.SetInterrupt(ctx.Done())
|
||||
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
p.all[conn] = cancel
|
||||
|
||||
return conn
|
||||
case <-ctx.Done():
|
||||
case <-p.closed:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Put puts an SQLite connection back into the Pool.
|
||||
//
|
||||
// Put will panic if the conn was not originally created by p. Put(nil) is a
|
||||
// no-op.
|
||||
//
|
||||
// Applications must ensure that all non-nil Conns returned from Get are
|
||||
// returned to the same Pool with Put.
|
||||
func (p *Pool) Put(conn *sqlite.Conn) {
|
||||
if conn == nil {
|
||||
// See https://github.com/zombiezen/go-sqlite/issues/17
|
||||
return
|
||||
}
|
||||
query := conn.CheckReset()
|
||||
if query != "" {
|
||||
panic(fmt.Sprintf(
|
||||
"connection returned to pool has active statement: %q",
|
||||
query))
|
||||
}
|
||||
|
||||
p.mu.Lock()
|
||||
cancel, found := p.all[conn]
|
||||
if found {
|
||||
p.all[conn] = func() {}
|
||||
}
|
||||
p.mu.Unlock()
|
||||
|
||||
if !found {
|
||||
panic("sqlite.Pool.Put: connection not created by this pool")
|
||||
}
|
||||
|
||||
conn.SetInterrupt(nil)
|
||||
cancel()
|
||||
p.free <- conn
|
||||
}
|
||||
|
||||
// Close interrupts and closes all the connections in the Pool,
|
||||
// blocking until all connections are returned to the Pool.
|
||||
func (p *Pool) Close() (err error) {
|
||||
close(p.closed)
|
||||
|
||||
p.mu.Lock()
|
||||
n := len(p.all)
|
||||
cancelList := make([]context.CancelFunc, 0, n)
|
||||
for conn, cancel := range p.all {
|
||||
cancelList = append(cancelList, cancel)
|
||||
p.all[conn] = func() {}
|
||||
}
|
||||
p.mu.Unlock()
|
||||
|
||||
for _, cancel := range cancelList {
|
||||
cancel()
|
||||
}
|
||||
for closed := 0; closed < n; closed++ {
|
||||
conn := <-p.free
|
||||
if err2 := conn.Close(); err == nil {
|
||||
err = err2
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type strerror struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (err strerror) Error() string { return err.msg }
|
||||
|
||||
// TODO(maybe)
|
||||
|
||||
// type tracer struct {
|
||||
// ctx context.Context
|
||||
// ctxStack []context.Context
|
||||
// taskStack []*trace.Task
|
||||
// }
|
||||
|
||||
// func (t *tracer) pctx() context.Context {
|
||||
// if len(t.ctxStack) != 0 {
|
||||
// return t.ctxStack[len(t.ctxStack)-1]
|
||||
// }
|
||||
// return t.ctx
|
||||
// }
|
||||
|
||||
// func (t *tracer) Push(name string) {
|
||||
// ctx, task := trace.NewTask(t.pctx(), name)
|
||||
// t.ctxStack = append(t.ctxStack, ctx)
|
||||
// t.taskStack = append(t.taskStack, task)
|
||||
// }
|
||||
|
||||
// func (t *tracer) Pop() {
|
||||
// t.taskStack[len(t.taskStack)-1].End()
|
||||
// t.taskStack = t.taskStack[:len(t.taskStack)-1]
|
||||
// t.ctxStack = t.ctxStack[:len(t.ctxStack)-1]
|
||||
// }
|
||||
|
||||
// func (t *tracer) NewTask(name string) sqlite.TracerTask {
|
||||
// ctx, task := trace.NewTask(t.pctx(), name)
|
||||
// return &tracerTask{
|
||||
// ctx: ctx,
|
||||
// task: task,
|
||||
// }
|
||||
// }
|
||||
|
||||
// type tracerTask struct {
|
||||
// ctx context.Context
|
||||
// task *trace.Task
|
||||
// region *trace.Region
|
||||
// }
|
||||
|
||||
// func (t *tracerTask) StartRegion(regionType string) {
|
||||
// if t.region != nil {
|
||||
// panic("sqlitex.tracerTask.StartRegion: already in region")
|
||||
// }
|
||||
// t.region = trace.StartRegion(t.ctx, regionType)
|
||||
// }
|
||||
|
||||
// func (t *tracerTask) EndRegion() {
|
||||
// t.region.End()
|
||||
// t.region = nil
|
||||
// }
|
||||
|
||||
// func (t *tracerTask) End() {
|
||||
// t.task.End()
|
||||
// }
|
||||
82
vendor/zombiezen.com/go/sqlite/sqlitex/query.go
generated
vendored
Normal file
82
vendor/zombiezen.com/go/sqlite/sqlitex/query.go
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
// Copyright 2021 Ross Light
|
||||
// SPDX-License-Identifier: ISC
|
||||
|
||||
package sqlitex
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"zombiezen.com/go/sqlite"
|
||||
)
|
||||
|
||||
var errNoResults = errors.New("sqlite: statement has no results")
|
||||
var errMultipleResults = errors.New("sqlite: statement has multiple result rows")
|
||||
|
||||
func resultSetup(stmt *sqlite.Stmt) error {
|
||||
hasRow, err := stmt.Step()
|
||||
if err != nil {
|
||||
stmt.Reset()
|
||||
return err
|
||||
}
|
||||
if !hasRow {
|
||||
stmt.Reset()
|
||||
return errNoResults
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func resultTeardown(stmt *sqlite.Stmt) error {
|
||||
hasRow, err := stmt.Step()
|
||||
if err != nil {
|
||||
stmt.Reset()
|
||||
return err
|
||||
}
|
||||
if hasRow {
|
||||
stmt.Reset()
|
||||
return errMultipleResults
|
||||
}
|
||||
return stmt.Reset()
|
||||
}
|
||||
|
||||
func ResultBool(stmt *sqlite.Stmt) (bool, error) {
|
||||
res, err := ResultInt64(stmt)
|
||||
return res != 0, err
|
||||
}
|
||||
|
||||
func ResultInt(stmt *sqlite.Stmt) (int, error) {
|
||||
res, err := ResultInt64(stmt)
|
||||
return int(res), err
|
||||
}
|
||||
|
||||
func ResultInt64(stmt *sqlite.Stmt) (int64, error) {
|
||||
if err := resultSetup(stmt); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
res := stmt.ColumnInt64(0)
|
||||
if err := resultTeardown(stmt); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func ResultText(stmt *sqlite.Stmt) (string, error) {
|
||||
if err := resultSetup(stmt); err != nil {
|
||||
return "", err
|
||||
}
|
||||
res := stmt.ColumnText(0)
|
||||
if err := resultTeardown(stmt); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func ResultFloat(stmt *sqlite.Stmt) (float64, error) {
|
||||
if err := resultSetup(stmt); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
res := stmt.ColumnFloat(0)
|
||||
if err := resultTeardown(stmt); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
51
vendor/zombiezen.com/go/sqlite/sqlitex/rand_id.go
generated
vendored
Normal file
51
vendor/zombiezen.com/go/sqlite/sqlitex/rand_id.go
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) 2019 David Crawshaw <david@zentus.com>
|
||||
// Copyright (c) 2021 Ross Light <rosss@zombiezen.com>
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
//
|
||||
// SPDX-License-Identifier: ISC
|
||||
|
||||
package sqlitex
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"zombiezen.com/go/sqlite"
|
||||
)
|
||||
|
||||
// InsertRandID executes stmt with a random value in the range [min, max) for $param.
|
||||
func InsertRandID(stmt *sqlite.Stmt, param string, min, max int64) (int64, error) {
|
||||
if min < 0 {
|
||||
return 0, fmt.Errorf("sqlitex.InsertRandID: min (%d) is negative", min)
|
||||
}
|
||||
|
||||
for i := 0; ; i++ {
|
||||
v, err := rand.Int(rand.Reader, big.NewInt(max-min))
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("sqlitex.InsertRandID: %w", err)
|
||||
}
|
||||
id := v.Int64() + min
|
||||
|
||||
stmt.Reset()
|
||||
stmt.SetInt64(param, id)
|
||||
_, err = stmt.Step()
|
||||
if err == nil {
|
||||
return id, nil
|
||||
}
|
||||
if i >= 100 || sqlite.ErrCode(err) != sqlite.ResultConstraintPrimaryKey {
|
||||
return 0, fmt.Errorf("sqlitex.InsertRandID: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
254
vendor/zombiezen.com/go/sqlite/sqlitex/savepoint.go
generated
vendored
Normal file
254
vendor/zombiezen.com/go/sqlite/sqlitex/savepoint.go
generated
vendored
Normal file
@@ -0,0 +1,254 @@
|
||||
// Copyright (c) 2018 David Crawshaw <david@zentus.com>
|
||||
// Copyright (c) 2021 Ross Light <rosss@zombiezen.com>
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
//
|
||||
// SPDX-License-Identifier: ISC
|
||||
|
||||
package sqlitex
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"zombiezen.com/go/sqlite"
|
||||
)
|
||||
|
||||
// Save creates a named SQLite transaction using SAVEPOINT.
|
||||
//
|
||||
// On success Savepoint returns a releaseFn that will call either
|
||||
// RELEASE or ROLLBACK depending on whether the parameter *error
|
||||
// points to a nil or non-nil error. This is designed to be deferred.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// func doWork(conn *sqlite.Conn) (err error) {
|
||||
// defer sqlitex.Save(conn)(&err)
|
||||
//
|
||||
// // ... do work in the transaction
|
||||
// }
|
||||
//
|
||||
// https://www.sqlite.org/lang_savepoint.html
|
||||
func Save(conn *sqlite.Conn) (releaseFn func(*error)) {
|
||||
name := "sqlitex.Save" // safe as names can be reused
|
||||
var pc [3]uintptr
|
||||
if n := runtime.Callers(0, pc[:]); n > 0 {
|
||||
frames := runtime.CallersFrames(pc[:n])
|
||||
if _, more := frames.Next(); more { // runtime.Callers
|
||||
if _, more := frames.Next(); more { // savepoint.Save
|
||||
frame, _ := frames.Next() // caller we care about
|
||||
if frame.Function != "" {
|
||||
name = frame.Function
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
releaseFn, err := savepoint(conn, name)
|
||||
if err != nil {
|
||||
if sqlite.ErrCode(err) == sqlite.ResultInterrupt {
|
||||
return func(errp *error) {
|
||||
if *errp == nil {
|
||||
*errp = err
|
||||
}
|
||||
}
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
return releaseFn
|
||||
}
|
||||
|
||||
func savepoint(conn *sqlite.Conn, name string) (releaseFn func(*error), err error) {
|
||||
if strings.Contains(name, `"`) {
|
||||
return nil, fmt.Errorf("sqlitex.Savepoint: invalid name: %q", name)
|
||||
}
|
||||
if err := Exec(conn, fmt.Sprintf("SAVEPOINT %q;", name), nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO(maybe)
|
||||
// tracer := conn.Tracer()
|
||||
// if tracer != nil {
|
||||
// tracer.Push("TX " + name)
|
||||
// }
|
||||
releaseFn = func(errp *error) {
|
||||
// TODO(maybe)
|
||||
// if tracer != nil {
|
||||
// tracer.Pop()
|
||||
// }
|
||||
recoverP := recover()
|
||||
|
||||
// If a query was interrupted or if a user exec'd COMMIT or
|
||||
// ROLLBACK, then everything was already rolled back
|
||||
// automatically, thus returning the connection to autocommit
|
||||
// mode.
|
||||
if conn.AutocommitEnabled() {
|
||||
// There is nothing to rollback.
|
||||
if recoverP != nil {
|
||||
panic(recoverP)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if *errp == nil && recoverP == nil {
|
||||
// Success path. Release the savepoint successfully.
|
||||
*errp = Exec(conn, fmt.Sprintf("RELEASE %q;", name), nil)
|
||||
if *errp == nil {
|
||||
return
|
||||
}
|
||||
// Possible interrupt. Fall through to the error path.
|
||||
if conn.AutocommitEnabled() {
|
||||
// There is nothing to rollback.
|
||||
if recoverP != nil {
|
||||
panic(recoverP)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
orig := ""
|
||||
if *errp != nil {
|
||||
orig = (*errp).Error() + "\n\t"
|
||||
}
|
||||
|
||||
// Error path.
|
||||
|
||||
// Always run ROLLBACK even if the connection has been interrupted.
|
||||
oldDoneCh := conn.SetInterrupt(nil)
|
||||
defer conn.SetInterrupt(oldDoneCh)
|
||||
|
||||
err := Exec(conn, fmt.Sprintf("ROLLBACK TO %q;", name), nil)
|
||||
if err != nil {
|
||||
panic(orig + err.Error())
|
||||
}
|
||||
err = Exec(conn, fmt.Sprintf("RELEASE %q;", name), nil)
|
||||
if err != nil {
|
||||
panic(orig + err.Error())
|
||||
}
|
||||
|
||||
if recoverP != nil {
|
||||
panic(recoverP)
|
||||
}
|
||||
}
|
||||
return releaseFn, nil
|
||||
}
|
||||
|
||||
// Transaction creates a DEFERRED SQLite transaction.
|
||||
//
|
||||
// On success Transaction returns an endFn that will call either
|
||||
// COMMIT or ROLLBACK depending on whether the parameter *error
|
||||
// points to a nil or non-nil error. This is designed to be deferred.
|
||||
//
|
||||
// https://www.sqlite.org/lang_transaction.html
|
||||
func Transaction(conn *sqlite.Conn) (endFn func(*error)) {
|
||||
endFn, err := transaction(conn, "DEFERRED")
|
||||
if err != nil {
|
||||
if sqlite.ErrCode(err) == sqlite.ResultInterrupt {
|
||||
return func(errp *error) {
|
||||
if *errp == nil {
|
||||
*errp = err
|
||||
}
|
||||
}
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
return endFn
|
||||
}
|
||||
|
||||
// ImmediateTransaction creates an IMMEDIATE SQLite transaction.
|
||||
//
|
||||
// On success ImmediateTransaction returns an endFn that will call either
|
||||
// COMMIT or ROLLBACK depending on whether the parameter *error
|
||||
// points to a nil or non-nil error. This is designed to be deferred.
|
||||
//
|
||||
// https://www.sqlite.org/lang_transaction.html
|
||||
func ImmediateTransaction(conn *sqlite.Conn) (endFn func(*error), err error) {
|
||||
endFn, err = transaction(conn, "IMMEDIATE")
|
||||
if err != nil {
|
||||
return func(*error) {}, err
|
||||
}
|
||||
return endFn, nil
|
||||
}
|
||||
|
||||
// ExclusiveTransaction creates an EXCLUSIVE SQLite transaction.
|
||||
//
|
||||
// On success ImmediateTransaction returns an endFn that will call either
|
||||
// COMMIT or ROLLBACK depending on whether the parameter *error
|
||||
// points to a nil or non-nil error. This is designed to be deferred.
|
||||
//
|
||||
// https://www.sqlite.org/lang_transaction.html
|
||||
func ExclusiveTransaction(conn *sqlite.Conn) (endFn func(*error), err error) {
|
||||
endFn, err = transaction(conn, "EXCLUSIVE")
|
||||
if err != nil {
|
||||
return func(*error) {}, err
|
||||
}
|
||||
return endFn, nil
|
||||
}
|
||||
|
||||
func transaction(conn *sqlite.Conn, mode string) (endFn func(*error), err error) {
|
||||
if err := Exec(conn, "BEGIN "+mode+";", nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
endFn = func(errp *error) {
|
||||
recoverP := recover()
|
||||
|
||||
// If a query was interrupted or if a user exec'd COMMIT or
|
||||
// ROLLBACK, then everything was already rolled back
|
||||
// automatically, thus returning the connection to autocommit
|
||||
// mode.
|
||||
if conn.AutocommitEnabled() {
|
||||
// There is nothing to rollback.
|
||||
if recoverP != nil {
|
||||
panic(recoverP)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if *errp == nil && recoverP == nil {
|
||||
// Success path. Commit the transaction.
|
||||
*errp = Exec(conn, "COMMIT;", nil)
|
||||
if *errp == nil {
|
||||
return
|
||||
}
|
||||
// Possible interrupt. Fall through to the error path.
|
||||
if conn.AutocommitEnabled() {
|
||||
// There is nothing to rollback.
|
||||
if recoverP != nil {
|
||||
panic(recoverP)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
orig := ""
|
||||
if *errp != nil {
|
||||
orig = (*errp).Error() + "\n\t"
|
||||
}
|
||||
|
||||
// Error path.
|
||||
|
||||
// Always run ROLLBACK even if the connection has been interrupted.
|
||||
oldDoneCh := conn.SetInterrupt(nil)
|
||||
defer conn.SetInterrupt(oldDoneCh)
|
||||
|
||||
err := Exec(conn, "ROLLBACK;", nil)
|
||||
if err != nil {
|
||||
panic(orig + err.Error())
|
||||
}
|
||||
|
||||
if recoverP != nil {
|
||||
panic(recoverP)
|
||||
}
|
||||
}
|
||||
return endFn, nil
|
||||
}
|
||||
Reference in New Issue
Block a user