2024-05-27 17:46:15 +02:00
|
|
|
package sqlite3
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"math"
|
2024-12-16 12:37:53 +01:00
|
|
|
"math/rand"
|
2024-05-27 17:46:15 +02:00
|
|
|
"net/url"
|
2025-01-14 19:30:55 +01:00
|
|
|
"runtime"
|
2024-05-27 17:46:15 +02:00
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2024-10-25 18:09:18 +02:00
|
|
|
"github.com/tetratelabs/wazero/api"
|
|
|
|
|
2024-05-27 17:46:15 +02:00
|
|
|
"github.com/ncruces/go-sqlite3/internal/util"
|
|
|
|
"github.com/ncruces/go-sqlite3/vfs"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Conn is a database connection handle.
|
|
|
|
// A Conn is not safe for concurrent use by multiple goroutines.
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/sqlite3.html
|
|
|
|
type Conn struct {
|
|
|
|
*sqlite
|
|
|
|
|
|
|
|
interrupt context.Context
|
|
|
|
pending *Stmt
|
2024-08-15 02:30:58 +02:00
|
|
|
stmts []*Stmt
|
2024-10-08 11:15:09 +02:00
|
|
|
busy func(context.Context, int) bool
|
2024-05-27 17:46:15 +02:00
|
|
|
log func(xErrorCode, string)
|
|
|
|
collation func(*Conn, string)
|
2024-08-15 02:30:58 +02:00
|
|
|
wal func(*Conn, string, int) error
|
|
|
|
trace func(TraceEvent, any, any) error
|
2024-05-27 17:46:15 +02:00
|
|
|
authorizer func(AuthorizerActionCode, string, string, string, string) AuthorizerReturnCode
|
|
|
|
update func(AuthorizerActionCode, string, string, int64)
|
|
|
|
commit func() bool
|
|
|
|
rollback func()
|
|
|
|
|
2024-12-16 12:37:53 +01:00
|
|
|
busy1st time.Time
|
|
|
|
busylst time.Time
|
2025-02-13 09:53:40 +01:00
|
|
|
arena arena
|
|
|
|
handle ptr_t
|
2024-05-27 17:46:15 +02:00
|
|
|
}
|
|
|
|
|
2024-10-08 11:15:09 +02:00
|
|
|
// Open calls [OpenFlags] with [OPEN_READWRITE], [OPEN_CREATE] and [OPEN_URI].
|
2024-05-27 17:46:15 +02:00
|
|
|
func Open(filename string) (*Conn, error) {
|
2024-10-08 11:15:09 +02:00
|
|
|
return newConn(context.Background(), filename, OPEN_READWRITE|OPEN_CREATE|OPEN_URI)
|
|
|
|
}
|
|
|
|
|
|
|
|
// OpenContext is like [Open] but includes a context,
|
|
|
|
// which is used to interrupt the process of opening the connectiton.
|
|
|
|
func OpenContext(ctx context.Context, filename string) (*Conn, error) {
|
|
|
|
return newConn(ctx, filename, OPEN_READWRITE|OPEN_CREATE|OPEN_URI)
|
2024-05-27 17:46:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// OpenFlags opens an SQLite database file as specified by the filename argument.
|
|
|
|
//
|
2024-10-08 11:15:09 +02:00
|
|
|
// If none of the required flags are used, a combination of [OPEN_READWRITE] and [OPEN_CREATE] is used.
|
2024-05-27 17:46:15 +02:00
|
|
|
// If a URI filename is used, PRAGMA statements to execute can be specified using "_pragma":
|
|
|
|
//
|
|
|
|
// sqlite3.Open("file:demo.db?_pragma=busy_timeout(10000)")
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/open.html
|
|
|
|
func OpenFlags(filename string, flags OpenFlag) (*Conn, error) {
|
|
|
|
if flags&(OPEN_READONLY|OPEN_READWRITE|OPEN_CREATE) == 0 {
|
|
|
|
flags |= OPEN_READWRITE | OPEN_CREATE
|
|
|
|
}
|
2024-10-08 11:15:09 +02:00
|
|
|
return newConn(context.Background(), filename, flags)
|
2024-05-27 17:46:15 +02:00
|
|
|
}
|
|
|
|
|
2025-01-14 19:30:55 +01:00
|
|
|
type connKey = util.ConnKey
|
2024-05-27 17:46:15 +02:00
|
|
|
|
2025-02-13 09:53:40 +01:00
|
|
|
func newConn(ctx context.Context, filename string, flags OpenFlag) (ret *Conn, _ error) {
|
2024-10-08 11:15:09 +02:00
|
|
|
err := ctx.Err()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
c := &Conn{interrupt: ctx}
|
|
|
|
c.sqlite, err = instantiateSQLite()
|
2024-05-27 17:46:15 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer func() {
|
2025-02-13 09:53:40 +01:00
|
|
|
if ret == nil {
|
2024-10-08 11:15:09 +02:00
|
|
|
c.Close()
|
|
|
|
c.sqlite.close()
|
|
|
|
} else {
|
|
|
|
c.interrupt = context.Background()
|
2024-05-27 17:46:15 +02:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
c.ctx = context.WithValue(c.ctx, connKey{}, c)
|
2025-02-13 09:53:40 +01:00
|
|
|
c.arena = c.newArena()
|
2024-05-27 17:46:15 +02:00
|
|
|
c.handle, err = c.openDB(filename, flags)
|
2024-07-08 22:03:00 +02:00
|
|
|
if err == nil {
|
|
|
|
err = initExtensions(c)
|
|
|
|
}
|
2024-05-27 17:46:15 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return c, nil
|
|
|
|
}
|
|
|
|
|
2025-02-13 09:53:40 +01:00
|
|
|
func (c *Conn) openDB(filename string, flags OpenFlag) (ptr_t, error) {
|
2024-05-27 17:46:15 +02:00
|
|
|
defer c.arena.mark()()
|
|
|
|
connPtr := c.arena.new(ptrlen)
|
|
|
|
namePtr := c.arena.string(filename)
|
|
|
|
|
|
|
|
flags |= OPEN_EXRESCODE
|
2025-02-13 09:53:40 +01:00
|
|
|
rc := res_t(c.call("sqlite3_open_v2", stk_t(namePtr), stk_t(connPtr), stk_t(flags), 0))
|
2024-05-27 17:46:15 +02:00
|
|
|
|
2025-02-13 09:53:40 +01:00
|
|
|
handle := util.Read32[ptr_t](c.mod, connPtr)
|
|
|
|
if err := c.sqlite.error(rc, handle); err != nil {
|
2024-05-27 17:46:15 +02:00
|
|
|
c.closeDB(handle)
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
2025-02-13 09:53:40 +01:00
|
|
|
c.call("sqlite3_progress_handler_go", stk_t(handle), 100)
|
2024-05-27 17:46:15 +02:00
|
|
|
if flags|OPEN_URI != 0 && strings.HasPrefix(filename, "file:") {
|
|
|
|
var pragmas strings.Builder
|
|
|
|
if _, after, ok := strings.Cut(filename, "?"); ok {
|
|
|
|
query, _ := url.ParseQuery(after)
|
|
|
|
for _, p := range query["_pragma"] {
|
|
|
|
pragmas.WriteString(`PRAGMA `)
|
|
|
|
pragmas.WriteString(p)
|
|
|
|
pragmas.WriteString(`;`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if pragmas.Len() != 0 {
|
2024-10-08 11:15:09 +02:00
|
|
|
c.checkInterrupt(handle)
|
2024-05-27 17:46:15 +02:00
|
|
|
pragmaPtr := c.arena.string(pragmas.String())
|
2025-02-13 09:53:40 +01:00
|
|
|
rc := res_t(c.call("sqlite3_exec", stk_t(handle), stk_t(pragmaPtr), 0, 0, 0))
|
|
|
|
if err := c.sqlite.error(rc, handle, pragmas.String()); err != nil {
|
2024-05-27 17:46:15 +02:00
|
|
|
err = fmt.Errorf("sqlite3: invalid _pragma: %w", err)
|
|
|
|
c.closeDB(handle)
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return handle, nil
|
|
|
|
}
|
|
|
|
|
2025-02-13 09:53:40 +01:00
|
|
|
func (c *Conn) closeDB(handle ptr_t) {
|
|
|
|
rc := res_t(c.call("sqlite3_close_v2", stk_t(handle)))
|
|
|
|
if err := c.sqlite.error(rc, handle); err != nil {
|
2024-05-27 17:46:15 +02:00
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close closes the database connection.
|
|
|
|
//
|
|
|
|
// If the database connection is associated with unfinalized prepared statements,
|
|
|
|
// open blob handles, and/or unfinished backup objects,
|
|
|
|
// Close will leave the database connection open and return [BUSY].
|
|
|
|
//
|
|
|
|
// It is safe to close a nil, zero or closed Conn.
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/close.html
|
|
|
|
func (c *Conn) Close() error {
|
|
|
|
if c == nil || c.handle == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
c.pending.Close()
|
|
|
|
c.pending = nil
|
|
|
|
|
2025-02-13 09:53:40 +01:00
|
|
|
rc := res_t(c.call("sqlite3_close", stk_t(c.handle)))
|
|
|
|
if err := c.error(rc); err != nil {
|
2024-05-27 17:46:15 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
c.handle = 0
|
|
|
|
return c.close()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Exec is a convenience function that allows an application to run
|
|
|
|
// multiple statements of SQL without having to use a lot of code.
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/exec.html
|
|
|
|
func (c *Conn) Exec(sql string) error {
|
|
|
|
defer c.arena.mark()()
|
|
|
|
sqlPtr := c.arena.string(sql)
|
|
|
|
|
2024-10-08 11:15:09 +02:00
|
|
|
c.checkInterrupt(c.handle)
|
2025-02-13 09:53:40 +01:00
|
|
|
rc := res_t(c.call("sqlite3_exec", stk_t(c.handle), stk_t(sqlPtr), 0, 0, 0))
|
|
|
|
return c.error(rc, sql)
|
2024-05-27 17:46:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Prepare calls [Conn.PrepareFlags] with no flags.
|
|
|
|
func (c *Conn) Prepare(sql string) (stmt *Stmt, tail string, err error) {
|
|
|
|
return c.PrepareFlags(sql, 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
// PrepareFlags compiles the first SQL statement in sql;
|
|
|
|
// tail is left pointing to what remains uncompiled.
|
|
|
|
// If the input text contains no SQL (if the input is an empty string or a comment),
|
|
|
|
// both stmt and err will be nil.
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/prepare.html
|
|
|
|
func (c *Conn) PrepareFlags(sql string, flags PrepareFlag) (stmt *Stmt, tail string, err error) {
|
|
|
|
if len(sql) > _MAX_SQL_LENGTH {
|
|
|
|
return nil, "", TOOBIG
|
|
|
|
}
|
|
|
|
|
|
|
|
defer c.arena.mark()()
|
|
|
|
stmtPtr := c.arena.new(ptrlen)
|
|
|
|
tailPtr := c.arena.new(ptrlen)
|
|
|
|
sqlPtr := c.arena.string(sql)
|
|
|
|
|
2024-10-25 18:09:18 +02:00
|
|
|
c.checkInterrupt(c.handle)
|
2025-02-13 09:53:40 +01:00
|
|
|
rc := res_t(c.call("sqlite3_prepare_v3", stk_t(c.handle),
|
|
|
|
stk_t(sqlPtr), stk_t(len(sql)+1), stk_t(flags),
|
|
|
|
stk_t(stmtPtr), stk_t(tailPtr)))
|
2024-05-27 17:46:15 +02:00
|
|
|
|
|
|
|
stmt = &Stmt{c: c}
|
2025-02-13 09:53:40 +01:00
|
|
|
stmt.handle = util.Read32[ptr_t](c.mod, stmtPtr)
|
|
|
|
if sql := sql[util.Read32[ptr_t](c.mod, tailPtr)-sqlPtr:]; sql != "" {
|
2024-05-27 17:46:15 +02:00
|
|
|
tail = sql
|
|
|
|
}
|
|
|
|
|
2025-02-13 09:53:40 +01:00
|
|
|
if err := c.error(rc, sql); err != nil {
|
2024-05-27 17:46:15 +02:00
|
|
|
return nil, "", err
|
|
|
|
}
|
|
|
|
if stmt.handle == 0 {
|
|
|
|
return nil, "", nil
|
|
|
|
}
|
2024-08-15 02:30:58 +02:00
|
|
|
c.stmts = append(c.stmts, stmt)
|
2024-05-27 17:46:15 +02:00
|
|
|
return stmt, tail, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// DBName returns the schema name for n-th database on the database connection.
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/db_name.html
|
|
|
|
func (c *Conn) DBName(n int) string {
|
2025-02-13 09:53:40 +01:00
|
|
|
ptr := ptr_t(c.call("sqlite3_db_name", stk_t(c.handle), stk_t(n)))
|
2024-05-27 17:46:15 +02:00
|
|
|
if ptr == 0 {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return util.ReadString(c.mod, ptr, _MAX_NAME)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Filename returns the filename for a database.
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/db_filename.html
|
|
|
|
func (c *Conn) Filename(schema string) *vfs.Filename {
|
2025-02-13 09:53:40 +01:00
|
|
|
var ptr ptr_t
|
2024-05-27 17:46:15 +02:00
|
|
|
if schema != "" {
|
|
|
|
defer c.arena.mark()()
|
|
|
|
ptr = c.arena.string(schema)
|
|
|
|
}
|
2025-02-13 09:53:40 +01:00
|
|
|
ptr = ptr_t(c.call("sqlite3_db_filename", stk_t(c.handle), stk_t(ptr)))
|
|
|
|
return vfs.GetFilename(c.ctx, c.mod, ptr, vfs.OPEN_MAIN_DB)
|
2024-05-27 17:46:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// ReadOnly determines if a database is read-only.
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/db_readonly.html
|
|
|
|
func (c *Conn) ReadOnly(schema string) (ro bool, ok bool) {
|
2025-02-13 09:53:40 +01:00
|
|
|
var ptr ptr_t
|
2024-05-27 17:46:15 +02:00
|
|
|
if schema != "" {
|
|
|
|
defer c.arena.mark()()
|
|
|
|
ptr = c.arena.string(schema)
|
|
|
|
}
|
2025-02-13 09:53:40 +01:00
|
|
|
b := int32(c.call("sqlite3_db_readonly", stk_t(c.handle), stk_t(ptr)))
|
|
|
|
return b > 0, b < 0
|
2024-05-27 17:46:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetAutocommit tests the connection for auto-commit mode.
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/get_autocommit.html
|
|
|
|
func (c *Conn) GetAutocommit() bool {
|
2025-02-13 09:53:40 +01:00
|
|
|
b := int32(c.call("sqlite3_get_autocommit", stk_t(c.handle)))
|
|
|
|
return b != 0
|
2024-05-27 17:46:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// LastInsertRowID returns the rowid of the most recent successful INSERT
|
|
|
|
// on the database connection.
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/last_insert_rowid.html
|
|
|
|
func (c *Conn) LastInsertRowID() int64 {
|
2025-02-13 09:53:40 +01:00
|
|
|
return int64(c.call("sqlite3_last_insert_rowid", stk_t(c.handle)))
|
2024-05-27 17:46:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// SetLastInsertRowID allows the application to set the value returned by
|
|
|
|
// [Conn.LastInsertRowID].
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/set_last_insert_rowid.html
|
|
|
|
func (c *Conn) SetLastInsertRowID(id int64) {
|
2025-02-13 09:53:40 +01:00
|
|
|
c.call("sqlite3_set_last_insert_rowid", stk_t(c.handle), stk_t(id))
|
2024-05-27 17:46:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Changes returns the number of rows modified, inserted or deleted
|
|
|
|
// by the most recently completed INSERT, UPDATE or DELETE statement
|
|
|
|
// on the database connection.
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/changes.html
|
|
|
|
func (c *Conn) Changes() int64 {
|
2025-02-13 09:53:40 +01:00
|
|
|
return int64(c.call("sqlite3_changes64", stk_t(c.handle)))
|
2024-05-27 17:46:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// TotalChanges returns the number of rows modified, inserted or deleted
|
|
|
|
// by all INSERT, UPDATE or DELETE statements completed
|
|
|
|
// since the database connection was opened.
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/total_changes.html
|
|
|
|
func (c *Conn) TotalChanges() int64 {
|
2025-02-13 09:53:40 +01:00
|
|
|
return int64(c.call("sqlite3_total_changes64", stk_t(c.handle)))
|
2024-05-27 17:46:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// ReleaseMemory frees memory used by a database connection.
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/db_release_memory.html
|
|
|
|
func (c *Conn) ReleaseMemory() error {
|
2025-02-13 09:53:40 +01:00
|
|
|
rc := res_t(c.call("sqlite3_db_release_memory", stk_t(c.handle)))
|
|
|
|
return c.error(rc)
|
2024-05-27 17:46:15 +02:00
|
|
|
}
|
|
|
|
|
2024-10-08 11:15:09 +02:00
|
|
|
// GetInterrupt gets the context set with [Conn.SetInterrupt].
|
2024-05-27 17:46:15 +02:00
|
|
|
func (c *Conn) GetInterrupt() context.Context {
|
|
|
|
return c.interrupt
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetInterrupt interrupts a long-running query when a context is done.
|
|
|
|
//
|
|
|
|
// Subsequent uses of the connection will return [INTERRUPT]
|
|
|
|
// until the context is reset by another call to SetInterrupt.
|
|
|
|
//
|
|
|
|
// To associate a timeout with a connection:
|
|
|
|
//
|
|
|
|
// ctx, cancel := context.WithTimeout(context.TODO(), 100*time.Millisecond)
|
|
|
|
// conn.SetInterrupt(ctx)
|
|
|
|
// defer cancel()
|
|
|
|
//
|
|
|
|
// SetInterrupt returns the old context assigned to the connection.
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/interrupt.html
|
|
|
|
func (c *Conn) SetInterrupt(ctx context.Context) (old context.Context) {
|
2024-10-08 11:15:09 +02:00
|
|
|
old = c.interrupt
|
|
|
|
c.interrupt = ctx
|
|
|
|
|
|
|
|
if ctx == old || ctx.Done() == old.Done() {
|
|
|
|
return old
|
2024-05-27 17:46:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// A busy SQL statement prevents SQLite from ignoring an interrupt
|
|
|
|
// that comes before any other statements are started.
|
|
|
|
if c.pending == nil {
|
2024-08-15 02:30:58 +02:00
|
|
|
defer c.arena.mark()()
|
|
|
|
stmtPtr := c.arena.new(ptrlen)
|
|
|
|
loopPtr := c.arena.string(`WITH RECURSIVE c(x) AS (VALUES(0) UNION ALL SELECT x FROM c) SELECT x FROM c`)
|
2025-02-13 09:53:40 +01:00
|
|
|
c.call("sqlite3_prepare_v3", stk_t(c.handle), stk_t(loopPtr), math.MaxUint64,
|
|
|
|
stk_t(PREPARE_PERSISTENT), stk_t(stmtPtr), 0)
|
2024-08-15 02:30:58 +02:00
|
|
|
c.pending = &Stmt{c: c}
|
2025-02-13 09:53:40 +01:00
|
|
|
c.pending.handle = util.Read32[ptr_t](c.mod, stmtPtr)
|
2024-05-27 17:46:15 +02:00
|
|
|
}
|
|
|
|
|
2024-10-08 11:15:09 +02:00
|
|
|
if old.Done() != nil && ctx.Err() == nil {
|
2024-05-27 17:46:15 +02:00
|
|
|
c.pending.Reset()
|
|
|
|
}
|
2024-10-08 11:15:09 +02:00
|
|
|
if ctx.Done() != nil {
|
2024-05-27 17:46:15 +02:00
|
|
|
c.pending.Step()
|
|
|
|
}
|
|
|
|
return old
|
|
|
|
}
|
|
|
|
|
2025-02-13 09:53:40 +01:00
|
|
|
func (c *Conn) checkInterrupt(handle ptr_t) {
|
2024-10-08 11:15:09 +02:00
|
|
|
if c.interrupt.Err() != nil {
|
2025-02-13 09:53:40 +01:00
|
|
|
c.call("sqlite3_interrupt", stk_t(handle))
|
2024-05-27 17:46:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-02-13 09:53:40 +01:00
|
|
|
func progressCallback(ctx context.Context, mod api.Module, _ ptr_t) (interrupt int32) {
|
2025-01-14 19:30:55 +01:00
|
|
|
if c, ok := ctx.Value(connKey{}).(*Conn); ok {
|
|
|
|
if c.interrupt.Done() != nil {
|
|
|
|
runtime.Gosched()
|
|
|
|
}
|
|
|
|
if c.interrupt.Err() != nil {
|
|
|
|
interrupt = 1
|
|
|
|
}
|
2024-05-27 17:46:15 +02:00
|
|
|
}
|
|
|
|
return interrupt
|
|
|
|
}
|
|
|
|
|
|
|
|
// BusyTimeout sets a busy timeout.
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/busy_timeout.html
|
|
|
|
func (c *Conn) BusyTimeout(timeout time.Duration) error {
|
|
|
|
ms := min((timeout+time.Millisecond-1)/time.Millisecond, math.MaxInt32)
|
2025-02-13 09:53:40 +01:00
|
|
|
rc := res_t(c.call("sqlite3_busy_timeout", stk_t(c.handle), stk_t(ms)))
|
|
|
|
return c.error(rc)
|
2024-05-27 17:46:15 +02:00
|
|
|
}
|
|
|
|
|
2025-02-13 09:53:40 +01:00
|
|
|
func timeoutCallback(ctx context.Context, mod api.Module, count, tmout int32) (retry int32) {
|
2024-12-16 12:37:53 +01:00
|
|
|
// https://fractaledmind.github.io/2024/04/15/sqlite-on-rails-the-how-and-why-of-optimal-performance/
|
2024-10-08 11:15:09 +02:00
|
|
|
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.interrupt.Err() == nil {
|
2024-12-16 12:37:53 +01:00
|
|
|
switch {
|
|
|
|
case count == 0:
|
|
|
|
c.busy1st = time.Now()
|
|
|
|
case time.Since(c.busy1st) >= time.Duration(tmout)*time.Millisecond:
|
|
|
|
return 0
|
2024-05-27 17:46:15 +02:00
|
|
|
}
|
2024-12-16 12:37:53 +01:00
|
|
|
if time.Since(c.busylst) < time.Millisecond {
|
|
|
|
const sleepIncrement = 2*1024*1024 - 1 // power of two, ~2ms
|
|
|
|
time.Sleep(time.Duration(rand.Int63() & sleepIncrement))
|
2024-05-27 17:46:15 +02:00
|
|
|
}
|
2024-12-16 12:37:53 +01:00
|
|
|
c.busylst = time.Now()
|
|
|
|
return 1
|
2024-05-27 17:46:15 +02:00
|
|
|
}
|
2024-09-14 16:36:25 +02:00
|
|
|
return 0
|
2024-05-27 17:46:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// BusyHandler registers a callback to handle [BUSY] errors.
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/busy_handler.html
|
2024-10-08 11:15:09 +02:00
|
|
|
func (c *Conn) BusyHandler(cb func(ctx context.Context, count int) (retry bool)) error {
|
2025-02-13 09:53:40 +01:00
|
|
|
var enable int32
|
2024-05-27 17:46:15 +02:00
|
|
|
if cb != nil {
|
|
|
|
enable = 1
|
|
|
|
}
|
2025-02-13 09:53:40 +01:00
|
|
|
rc := res_t(c.call("sqlite3_busy_handler_go", stk_t(c.handle), stk_t(enable)))
|
|
|
|
if err := c.error(rc); err != nil {
|
2024-05-27 17:46:15 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
c.busy = cb
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2025-02-13 09:53:40 +01:00
|
|
|
func busyCallback(ctx context.Context, mod api.Module, pDB ptr_t, count int32) (retry int32) {
|
2024-10-08 11:15:09 +02:00
|
|
|
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.busy != nil {
|
|
|
|
interrupt := c.interrupt
|
|
|
|
if interrupt == nil {
|
|
|
|
interrupt = context.Background()
|
|
|
|
}
|
|
|
|
if interrupt.Err() == nil && c.busy(interrupt, int(count)) {
|
2024-05-27 17:46:15 +02:00
|
|
|
retry = 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return retry
|
|
|
|
}
|
|
|
|
|
2024-08-15 02:30:58 +02:00
|
|
|
// Status retrieves runtime status information about a database connection.
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/db_status.html
|
|
|
|
func (c *Conn) Status(op DBStatus, reset bool) (current, highwater int, err error) {
|
|
|
|
defer c.arena.mark()()
|
2024-10-25 18:09:18 +02:00
|
|
|
hiPtr := c.arena.new(intlen)
|
|
|
|
curPtr := c.arena.new(intlen)
|
2024-08-15 02:30:58 +02:00
|
|
|
|
2025-02-13 09:53:40 +01:00
|
|
|
var i int32
|
2024-08-15 02:30:58 +02:00
|
|
|
if reset {
|
|
|
|
i = 1
|
|
|
|
}
|
|
|
|
|
2025-02-13 09:53:40 +01:00
|
|
|
rc := res_t(c.call("sqlite3_db_status", stk_t(c.handle),
|
|
|
|
stk_t(op), stk_t(curPtr), stk_t(hiPtr), stk_t(i)))
|
|
|
|
if err = c.error(rc); err == nil {
|
|
|
|
current = int(util.Read32[int32](c.mod, curPtr))
|
|
|
|
highwater = int(util.Read32[int32](c.mod, hiPtr))
|
2024-08-15 02:30:58 +02:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// TableColumnMetadata extracts metadata about a column of a table.
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/table_column_metadata.html
|
|
|
|
func (c *Conn) TableColumnMetadata(schema, table, column string) (declType, collSeq string, notNull, primaryKey, autoInc bool, err error) {
|
|
|
|
defer c.arena.mark()()
|
|
|
|
|
2025-02-13 09:53:40 +01:00
|
|
|
var schemaPtr, columnPtr ptr_t
|
2024-08-15 02:30:58 +02:00
|
|
|
declTypePtr := c.arena.new(ptrlen)
|
|
|
|
collSeqPtr := c.arena.new(ptrlen)
|
|
|
|
notNullPtr := c.arena.new(ptrlen)
|
|
|
|
autoIncPtr := c.arena.new(ptrlen)
|
2024-10-25 18:09:18 +02:00
|
|
|
primaryKeyPtr := c.arena.new(ptrlen)
|
2024-08-15 02:30:58 +02:00
|
|
|
if schema != "" {
|
|
|
|
schemaPtr = c.arena.string(schema)
|
|
|
|
}
|
|
|
|
tablePtr := c.arena.string(table)
|
|
|
|
if column != "" {
|
|
|
|
columnPtr = c.arena.string(column)
|
|
|
|
}
|
|
|
|
|
2025-02-13 09:53:40 +01:00
|
|
|
rc := res_t(c.call("sqlite3_table_column_metadata", stk_t(c.handle),
|
|
|
|
stk_t(schemaPtr), stk_t(tablePtr), stk_t(columnPtr),
|
|
|
|
stk_t(declTypePtr), stk_t(collSeqPtr),
|
|
|
|
stk_t(notNullPtr), stk_t(primaryKeyPtr), stk_t(autoIncPtr)))
|
|
|
|
if err = c.error(rc); err == nil && column != "" {
|
|
|
|
if ptr := util.Read32[ptr_t](c.mod, declTypePtr); ptr != 0 {
|
2024-12-12 20:44:53 +01:00
|
|
|
declType = util.ReadString(c.mod, ptr, _MAX_NAME)
|
|
|
|
}
|
2025-02-13 09:53:40 +01:00
|
|
|
if ptr := util.Read32[ptr_t](c.mod, collSeqPtr); ptr != 0 {
|
2024-12-12 20:44:53 +01:00
|
|
|
collSeq = util.ReadString(c.mod, ptr, _MAX_NAME)
|
|
|
|
}
|
2025-02-13 09:53:40 +01:00
|
|
|
notNull = util.Read32[uint32](c.mod, notNullPtr) != 0
|
|
|
|
autoInc = util.Read32[uint32](c.mod, autoIncPtr) != 0
|
|
|
|
primaryKey = util.Read32[uint32](c.mod, primaryKeyPtr) != 0
|
2024-08-15 02:30:58 +02:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2025-02-13 09:53:40 +01:00
|
|
|
func (c *Conn) error(rc res_t, sql ...string) error {
|
2024-05-27 17:46:15 +02:00
|
|
|
return c.sqlite.error(rc, c.handle, sql...)
|
|
|
|
}
|
|
|
|
|
2024-08-15 02:30:58 +02:00
|
|
|
func (c *Conn) stmtsIter(yield func(*Stmt) bool) {
|
|
|
|
for _, s := range c.stmts {
|
|
|
|
if !yield(s) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|