Files
sqlite3/conn.go

332 lines
8.5 KiB
Go
Raw Normal View History

2023-01-12 05:57:09 +00:00
package sqlite3
2023-01-11 14:58:20 +00:00
import (
"context"
2023-02-27 13:45:32 +00:00
"database/sql/driver"
2023-03-10 15:50:11 +00:00
"errors"
2023-02-28 16:03:31 +00:00
"fmt"
"net/url"
"runtime"
2023-02-28 16:03:31 +00:00
"strings"
2023-03-01 12:16:36 +00:00
"sync/atomic"
"unsafe"
2023-01-11 14:58:20 +00:00
)
2023-02-09 16:40:48 +00:00
// Conn is a database connection handle.
2023-03-01 10:34:08 +00:00
// A Conn is not safe for concurrent use by multiple goroutines.
2023-02-09 16:40:48 +00:00
//
// https://www.sqlite.org/c3ref/sqlite3.html
2023-01-12 05:57:09 +00:00
type Conn struct {
2023-03-06 18:28:50 +00:00
*module
2023-02-14 18:21:18 +00:00
2023-03-06 18:28:50 +00:00
handle uint32
2023-02-24 14:56:49 +00:00
arena arena
interrupt context.Context
waiter chan struct{}
pending *Stmt
2023-01-11 14:58:20 +00:00
}
2023-02-28 16:03:31 +00:00
// Open calls [OpenFlags] with [OPEN_READWRITE], [OPEN_CREATE] and [OPEN_URI].
func Open(filename string) (*Conn, error) {
2023-03-03 14:48:56 +00:00
return newConn(filename, OPEN_READWRITE|OPEN_CREATE|OPEN_URI)
2023-01-16 12:54:24 +00:00
}
2023-02-09 16:40:48 +00:00
// OpenFlags opens an SQLite database file as specified by the filename argument.
//
2023-02-28 16:03:31 +00: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)&_pragma=locking_mode(normal)")
//
2023-02-09 16:40:48 +00:00
// https://www.sqlite.org/c3ref/open.html
func OpenFlags(filename string, flags OpenFlag) (*Conn, error) {
2023-03-03 14:48:56 +00:00
return newConn(filename, flags)
}
2023-03-03 14:48:56 +00:00
func newConn(filename string, flags OpenFlag) (conn *Conn, err error) {
2023-03-06 12:22:17 +00:00
mod, err := instantiateModule()
2023-01-11 14:58:20 +00:00
if err != nil {
2023-01-12 05:57:09 +00:00
return nil, err
2023-01-11 14:58:20 +00:00
}
2023-01-12 13:43:35 +00:00
defer func() {
if conn == nil {
2023-03-06 23:41:54 +00:00
mod.close()
} else {
runtime.SetFinalizer(conn, finalizer[Conn](3))
2023-01-12 13:43:35 +00:00
}
}()
2023-01-12 05:57:09 +00:00
2023-03-06 18:28:50 +00:00
c := &Conn{module: mod}
2023-02-14 11:34:24 +00:00
c.arena = c.newArena(1024)
2023-03-03 14:48:56 +00:00
c.handle, err = c.openDB(filename, flags)
if err != nil {
return nil, err
}
return c, nil
}
2023-02-10 16:42:49 +00:00
2023-03-03 14:48:56 +00:00
func (c *Conn) openDB(filename string, flags OpenFlag) (uint32, error) {
2023-02-14 11:34:24 +00:00
defer c.arena.reset()
connPtr := c.arena.new(ptrlen)
namePtr := c.arena.string(filename)
2023-01-12 13:43:35 +00:00
2023-03-15 13:29:09 +00:00
flags |= OPEN_EXRESCODE
2023-02-24 17:49:16 +00:00
r := c.call(c.api.open, uint64(namePtr), uint64(connPtr), uint64(flags), 0)
2023-01-11 14:58:20 +00:00
2023-03-03 14:48:56 +00:00
handle := c.mem.readUint32(connPtr)
2023-03-06 18:28:50 +00:00
if err := c.module.error(r[0], handle); err != nil {
2023-03-03 14:48:56 +00:00
c.closeDB(handle)
return 0, err
2023-01-12 05:57:09 +00:00
}
2023-02-28 16:03:31 +00: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.WriteByte(';')
}
}
2023-03-03 14:48:56 +00:00
2023-03-08 17:39:41 +00:00
c.arena.reset()
2023-03-03 14:48:56 +00:00
pragmaPtr := c.arena.string(pragmas.String())
r := c.call(c.api.exec, uint64(handle), uint64(pragmaPtr), 0, 0, 0)
2023-03-06 18:28:50 +00:00
if err := c.module.error(r[0], handle, pragmas.String()); err != nil {
2023-03-10 15:50:11 +00:00
if errors.Is(err, ERROR) {
err = fmt.Errorf("sqlite3: invalid _pragma: %w", err)
}
2023-03-03 14:48:56 +00:00
c.closeDB(handle)
2023-03-10 15:50:11 +00:00
return 0, err
2023-02-28 16:03:31 +00:00
}
}
2023-03-10 16:25:07 +00:00
c.call(c.api.timeCollation, uint64(handle))
2023-03-03 14:48:56 +00:00
return handle, nil
}
func (c *Conn) closeDB(handle uint32) {
2023-03-07 12:12:48 +00:00
r := c.call(c.api.closeZombie, uint64(handle))
2023-03-06 18:28:50 +00:00
if err := c.module.error(r[0], handle); err != nil {
2023-03-03 14:48:56 +00:00
panic(err)
}
2023-01-12 05:57:09 +00:00
}
2023-02-10 14:14:19 +00:00
// Close closes the database connection.
//
2023-02-09 16:40:48 +00:00
// 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].
//
2023-03-01 10:34:08 +00:00
// It is safe to close a nil, zero or closed Conn.
//
2023-02-09 16:40:48 +00:00
// https://www.sqlite.org/c3ref/close.html
2023-01-12 13:43:35 +00:00
func (c *Conn) Close() error {
2023-02-16 13:52:05 +00:00
if c == nil || c.handle == 0 {
2023-02-10 14:14:19 +00:00
return nil
}
2023-02-24 14:56:49 +00:00
c.SetInterrupt(context.Background())
2023-03-07 14:45:54 +00:00
c.pending.Close()
c.pending = nil
2023-02-14 18:21:18 +00:00
2023-02-24 17:49:16 +00:00
r := c.call(c.api.close, uint64(c.handle))
2023-01-17 13:43:16 +00:00
if err := c.error(r[0]); err != nil {
return err
2023-01-12 13:43:35 +00:00
}
2023-02-10 14:14:19 +00:00
c.handle = 0
runtime.SetFinalizer(c, nil)
2023-03-06 23:41:54 +00:00
return c.module.close()
2023-01-11 14:58:20 +00:00
}
2023-02-24 15:06:19 +00:00
// Exec is a convenience function that allows an application to run
// multiple statements of SQL without having to use a lot of code.
//
// https://www.sqlite.org/c3ref/exec.html
func (c *Conn) Exec(sql string) error {
c.checkInterrupt()
defer c.arena.reset()
sqlPtr := c.arena.string(sql)
2023-02-24 17:49:16 +00:00
r := c.call(c.api.exec, uint64(c.handle), uint64(sqlPtr), 0, 0, 0)
2023-02-24 15:06:19 +00:00
return c.error(r[0])
}
// 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://www.sqlite.org/c3ref/prepare.html
func (c *Conn) PrepareFlags(sql string, flags PrepareFlag) (stmt *Stmt, tail string, err error) {
if emptyStatement(sql) {
return nil, "", nil
}
defer c.arena.reset()
stmtPtr := c.arena.new(ptrlen)
tailPtr := c.arena.new(ptrlen)
sqlPtr := c.arena.string(sql)
2023-02-24 17:49:16 +00:00
r := c.call(c.api.prepare, uint64(c.handle),
2023-02-24 15:06:19 +00:00
uint64(sqlPtr), uint64(len(sql)+1), uint64(flags),
uint64(stmtPtr), uint64(tailPtr))
stmt = &Stmt{c: c}
stmt.handle = c.mem.readUint32(stmtPtr)
i := c.mem.readUint32(tailPtr)
tail = sql[i-sqlPtr:]
if err := c.error(r[0], sql); err != nil {
return nil, "", err
}
if stmt.handle == 0 {
return nil, "", nil
}
return
}
2023-02-24 14:31:41 +00:00
// GetAutocommit tests the connection for auto-commit mode.
//
// https://www.sqlite.org/c3ref/get_autocommit.html
func (c *Conn) GetAutocommit() bool {
2023-02-24 17:49:16 +00:00
r := c.call(c.api.autocommit, uint64(c.handle))
2023-02-24 14:31:41 +00:00
return r[0] != 0
}
2023-02-24 15:06:19 +00:00
// LastInsertRowID returns the rowid of the most recent successful INSERT
// on the database connection.
//
// https://www.sqlite.org/c3ref/last_insert_rowid.html
2023-02-27 13:45:32 +00:00
func (c *Conn) LastInsertRowID() int64 {
2023-02-24 17:49:16 +00:00
r := c.call(c.api.lastRowid, uint64(c.handle))
2023-02-27 13:45:32 +00:00
return int64(r[0])
2023-02-24 15:06:19 +00: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://www.sqlite.org/c3ref/changes.html
2023-02-27 13:45:32 +00:00
func (c *Conn) Changes() int64 {
2023-02-24 17:49:16 +00:00
r := c.call(c.api.changes, uint64(c.handle))
2023-02-27 13:45:32 +00:00
return int64(r[0])
2023-02-24 15:06:19 +00:00
}
2023-02-24 14:56:49 +00:00
// SetInterrupt interrupts a long-running query when a context is done.
2023-02-14 18:21:18 +00:00
//
// Subsequent uses of the connection will return [INTERRUPT]
2023-02-24 14:56:49 +00:00
// until the context is reset by another call to SetInterrupt.
2023-02-14 18:21:18 +00:00
//
2023-03-01 10:34:08 +00:00
// To associate a timeout with a connection:
2023-02-14 18:21:18 +00:00
//
// ctx, cancel := context.WithTimeout(context.TODO(), 100*time.Millisecond)
2023-02-24 14:56:49 +00:00
// conn.SetInterrupt(ctx)
2023-02-14 18:21:18 +00:00
// defer cancel()
//
2023-02-24 14:56:49 +00:00
// SetInterrupt returns the old context assigned to the connection.
//
2023-02-14 18:21:18 +00:00
// https://www.sqlite.org/c3ref/interrupt.html
2023-02-24 14:56:49 +00:00
func (c *Conn) SetInterrupt(ctx context.Context) (old context.Context) {
2023-02-14 18:21:18 +00:00
// Is a waiter running?
if c.waiter != nil {
c.waiter <- struct{}{} // Cancel the waiter.
<-c.waiter // Wait for it to finish.
c.waiter = nil
}
2023-03-07 14:45:54 +00:00
// Reset the pending statement.
if c.pending != nil {
c.pending.Reset()
}
2023-02-14 18:21:18 +00:00
2023-02-24 14:56:49 +00:00
old = c.interrupt
c.interrupt = ctx
2023-02-25 15:11:07 +00:00
if ctx == nil || ctx.Done() == nil {
2023-02-14 18:21:18 +00:00
return old
}
2023-02-16 13:30:31 +00:00
// Creating an uncompleted SQL statement prevents SQLite from ignoring
// an interrupt that comes before any other statements are started.
2023-02-24 14:31:41 +00:00
if c.pending == nil {
2023-03-08 17:39:41 +00:00
c.pending, _, _ = c.Prepare(`SELECT 1 UNION ALL SELECT 2`)
2023-02-24 14:31:41 +00:00
}
2023-03-07 14:45:54 +00:00
c.pending.Step()
2023-02-24 14:31:41 +00:00
// Don't create the goroutine if we're already interrupted.
// This happens frequently while restoring to a previously interrupted state.
if c.checkInterrupt() {
return old
}
2023-02-16 13:30:31 +00:00
2023-02-14 18:21:18 +00:00
waiter := make(chan struct{})
c.waiter = waiter
go func() {
select {
2023-02-16 13:30:31 +00:00
case <-waiter: // Waiter was cancelled.
break
2023-02-24 14:56:49 +00:00
case <-ctx.Done(): // Done was closed.
2023-03-01 12:16:36 +00:00
buf := c.mem.view(c.handle+c.api.interrupt, 4)
(*atomic.Uint32)(unsafe.Pointer(&buf[0])).Store(1)
2023-02-14 18:21:18 +00:00
// Wait for the next call to SetInterrupt.
2023-02-16 13:30:31 +00:00
<-waiter
2023-02-14 18:21:18 +00:00
}
2023-02-16 13:30:31 +00:00
// Signal that the waiter has finished.
2023-02-14 18:21:18 +00:00
waiter <- struct{}{}
}()
return old
}
2023-02-24 14:31:41 +00:00
func (c *Conn) checkInterrupt() bool {
2023-02-24 14:56:49 +00:00
if c.interrupt == nil || c.interrupt.Err() == nil {
2023-02-24 14:31:41 +00:00
return false
}
2023-03-01 12:16:36 +00:00
buf := c.mem.view(c.handle+c.api.interrupt, 4)
(*atomic.Uint32)(unsafe.Pointer(&buf[0])).Store(1)
2023-02-24 15:19:57 +00:00
return true
}
2023-03-01 10:34:08 +00:00
// Pragma executes a PRAGMA statement and returns any results.
2023-02-25 15:34:24 +00:00
//
// https://www.sqlite.org/pragma.html
2023-03-08 17:39:41 +00:00
func (c *Conn) Pragma(str string) ([]string, error) {
stmt, _, err := c.Prepare(`PRAGMA ` + str)
if err != nil {
return nil, err
}
2023-02-25 15:34:24 +00:00
defer stmt.Close()
2023-02-26 04:49:10 +00:00
var pragmas []string
for stmt.Step() {
pragmas = append(pragmas, stmt.ColumnText(0))
2023-02-25 15:34:24 +00:00
}
2023-03-08 17:39:41 +00:00
return pragmas, stmt.Close()
2023-02-25 15:34:24 +00:00
}
2023-02-10 16:42:49 +00:00
func (c *Conn) error(rc uint64, sql ...string) error {
2023-03-06 18:28:50 +00:00
return c.module.error(rc, c.handle, sql...)
2023-03-03 14:48:56 +00:00
}
2023-03-01 10:34:08 +00:00
// DriverConn is implemented by the SQLite [database/sql] driver connection.
//
// It can be used to access advanced SQLite features like
// [savepoints] and [incremental BLOB I/O].
//
// [savepoints]: https://www.sqlite.org/lang_savepoint.html
// [incremental BLOB I/O]: https://www.sqlite.org/c3ref/blob_open.html
2023-02-27 13:45:32 +00:00
type DriverConn interface {
driver.ConnBeginTx
driver.ExecerContext
driver.ConnPrepareContext
2023-03-08 16:29:29 +00:00
Savepoint() Savepoint
2023-02-27 13:45:32 +00:00
OpenBlob(db, table, column string, row int64, write bool) (*Blob, error)
}