1101 lines
28 KiB
Go
1101 lines
28 KiB
Go
// Copyright 2025 The Sqlite Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package sqlite // import "modernc.org/sqlite"
|
|
|
|
import (
|
|
"context"
|
|
"database/sql/driver"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
"unsafe"
|
|
|
|
"modernc.org/libc"
|
|
"modernc.org/libc/sys/types"
|
|
sqlite3 "modernc.org/sqlite/lib"
|
|
)
|
|
|
|
type conn struct {
|
|
db uintptr // *sqlite3.Xsqlite3
|
|
tls *libc.TLS
|
|
|
|
// Context handling can cause conn.Close and conn.interrupt to be invoked
|
|
// concurrently.
|
|
sync.Mutex
|
|
|
|
writeTimeFormat string
|
|
beginMode string
|
|
intToTime bool
|
|
integerTimeFormat string
|
|
}
|
|
|
|
func newConn(dsn string) (*conn, error) {
|
|
var query, vfsName string
|
|
|
|
// Parse the query parameters from the dsn and them from the dsn if not prefixed by file:
|
|
// https://github.com/mattn/go-sqlite3/blob/3392062c729d77820afc1f5cae3427f0de39e954/sqlite3.go#L1046
|
|
// https://github.com/mattn/go-sqlite3/blob/3392062c729d77820afc1f5cae3427f0de39e954/sqlite3.go#L1383
|
|
pos := strings.IndexRune(dsn, '?')
|
|
if pos >= 1 {
|
|
query = dsn[pos+1:]
|
|
var err error
|
|
vfsName, err = getVFSName(query)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !strings.HasPrefix(dsn, "file:") {
|
|
dsn = dsn[:pos]
|
|
}
|
|
}
|
|
|
|
c := &conn{tls: libc.NewTLS()}
|
|
db, err := c.openV2(
|
|
dsn,
|
|
vfsName,
|
|
sqlite3.SQLITE_OPEN_READWRITE|sqlite3.SQLITE_OPEN_CREATE|
|
|
sqlite3.SQLITE_OPEN_FULLMUTEX|
|
|
sqlite3.SQLITE_OPEN_URI,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
c.db = db
|
|
if err = c.extendedResultCodes(true); err != nil {
|
|
c.Close()
|
|
return nil, err
|
|
}
|
|
|
|
if err = applyQueryParams(c, query); err != nil {
|
|
c.Close()
|
|
return nil, err
|
|
}
|
|
|
|
return c, nil
|
|
}
|
|
|
|
// Attempt to parse s as a time. Return (s, false) if s is not
|
|
// recognized as a valid time encoding.
|
|
func (c *conn) parseTime(s string) (interface{}, bool) {
|
|
if v, ok := c.parseTimeString(s, strings.Index(s, "m=")); ok {
|
|
return v, true
|
|
}
|
|
|
|
ts := strings.TrimSuffix(s, "Z")
|
|
|
|
for _, f := range parseTimeFormats {
|
|
t, err := time.Parse(f, ts)
|
|
if err == nil {
|
|
return t, true
|
|
}
|
|
}
|
|
|
|
return s, false
|
|
}
|
|
|
|
// Attempt to parse s as a time string produced by t.String(). If x > 0 it's
|
|
// the index of substring "m=" within s. Return (s, false) if s is
|
|
// not recognized as a valid time encoding.
|
|
func (c *conn) parseTimeString(s0 string, x int) (interface{}, bool) {
|
|
s := s0
|
|
if x > 0 {
|
|
s = s[:x] // "2006-01-02 15:04:05.999999999 -0700 MST m=+9999" -> "2006-01-02 15:04:05.999999999 -0700 MST "
|
|
}
|
|
s = strings.TrimSpace(s)
|
|
if t, err := time.Parse("2006-01-02 15:04:05.999999999 -0700 MST", s); err == nil {
|
|
return t, true
|
|
}
|
|
|
|
return s0, false
|
|
}
|
|
|
|
// writeTimeFormats are the names and formats supported
|
|
// by the `_time_format` DSN query param.
|
|
var writeTimeFormats = map[string]string{
|
|
"sqlite": parseTimeFormats[0],
|
|
}
|
|
|
|
func (c *conn) formatTime(t time.Time) string {
|
|
// Before configurable write time formats were supported,
|
|
// time.Time.String was used. Maintain that default to
|
|
// keep existing driver users formatting times the same.
|
|
if c.writeTimeFormat == "" {
|
|
return t.String()
|
|
}
|
|
return t.Format(c.writeTimeFormat)
|
|
}
|
|
|
|
// C documentation
|
|
//
|
|
// const void *sqlite3_column_blob(sqlite3_stmt*, int iCol);
|
|
func (c *conn) columnBlob(pstmt uintptr, iCol int) (v []byte, err error) {
|
|
p := sqlite3.Xsqlite3_column_blob(c.tls, pstmt, int32(iCol))
|
|
len, err := c.columnBytes(pstmt, iCol)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if p == 0 || len == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
v = make([]byte, len)
|
|
copy(v, (*libc.RawMem)(unsafe.Pointer(p))[:len:len])
|
|
return v, nil
|
|
}
|
|
|
|
// C documentation
|
|
//
|
|
// int sqlite3_column_bytes(sqlite3_stmt*, int iCol);
|
|
func (c *conn) columnBytes(pstmt uintptr, iCol int) (_ int, err error) {
|
|
v := sqlite3.Xsqlite3_column_bytes(c.tls, pstmt, int32(iCol))
|
|
return int(v), nil
|
|
}
|
|
|
|
// C documentation
|
|
//
|
|
// const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol);
|
|
func (c *conn) columnText(pstmt uintptr, iCol int) (v string, err error) {
|
|
p := sqlite3.Xsqlite3_column_text(c.tls, pstmt, int32(iCol))
|
|
len, err := c.columnBytes(pstmt, iCol)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if p == 0 || len == 0 {
|
|
return "", nil
|
|
}
|
|
|
|
b := make([]byte, len)
|
|
copy(b, (*libc.RawMem)(unsafe.Pointer(p))[:len:len])
|
|
return string(b), nil
|
|
}
|
|
|
|
// C documentation
|
|
//
|
|
// double sqlite3_column_double(sqlite3_stmt*, int iCol);
|
|
func (c *conn) columnDouble(pstmt uintptr, iCol int) (v float64, err error) {
|
|
v = sqlite3.Xsqlite3_column_double(c.tls, pstmt, int32(iCol))
|
|
return v, nil
|
|
}
|
|
|
|
// C documentation
|
|
//
|
|
// sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol);
|
|
func (c *conn) columnInt64(pstmt uintptr, iCol int) (v int64, err error) {
|
|
v = sqlite3.Xsqlite3_column_int64(c.tls, pstmt, int32(iCol))
|
|
return v, nil
|
|
}
|
|
|
|
// C documentation
|
|
//
|
|
// int sqlite3_column_type(sqlite3_stmt*, int iCol);
|
|
func (c *conn) columnType(pstmt uintptr, iCol int) (_ int, err error) {
|
|
v := sqlite3.Xsqlite3_column_type(c.tls, pstmt, int32(iCol))
|
|
return int(v), nil
|
|
}
|
|
|
|
// C documentation
|
|
//
|
|
// const char *sqlite3_column_decltype(sqlite3_stmt*,int);
|
|
func (c *conn) columnDeclType(pstmt uintptr, iCol int) string {
|
|
return libc.GoString(sqlite3.Xsqlite3_column_decltype(c.tls, pstmt, int32(iCol)))
|
|
}
|
|
|
|
// C documentation
|
|
//
|
|
// const char *sqlite3_column_name(sqlite3_stmt*, int N);
|
|
func (c *conn) columnName(pstmt uintptr, n int) (string, error) {
|
|
p := sqlite3.Xsqlite3_column_name(c.tls, pstmt, int32(n))
|
|
return libc.GoString(p), nil
|
|
}
|
|
|
|
// C documentation
|
|
//
|
|
// int sqlite3_column_count(sqlite3_stmt *pStmt);
|
|
func (c *conn) columnCount(pstmt uintptr) (_ int, err error) {
|
|
v := sqlite3.Xsqlite3_column_count(c.tls, pstmt)
|
|
return int(v), nil
|
|
}
|
|
|
|
// C documentation
|
|
//
|
|
// sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*);
|
|
func (c *conn) lastInsertRowID() (v int64, _ error) {
|
|
return sqlite3.Xsqlite3_last_insert_rowid(c.tls, c.db), nil
|
|
}
|
|
|
|
// C documentation
|
|
//
|
|
// int sqlite3_changes(sqlite3*);
|
|
func (c *conn) changes() (int, error) {
|
|
v := sqlite3.Xsqlite3_changes(c.tls, c.db)
|
|
return int(v), nil
|
|
}
|
|
|
|
// C documentation
|
|
//
|
|
// int sqlite3_step(sqlite3_stmt*);
|
|
func (c *conn) step(pstmt uintptr) (int, error) {
|
|
for {
|
|
switch rc := sqlite3.Xsqlite3_step(c.tls, pstmt); rc {
|
|
case sqliteLockedSharedcache:
|
|
if err := c.retry(pstmt); err != nil {
|
|
return sqlite3.SQLITE_LOCKED, err
|
|
}
|
|
case
|
|
sqlite3.SQLITE_DONE,
|
|
sqlite3.SQLITE_ROW:
|
|
|
|
return int(rc), nil
|
|
default:
|
|
return int(rc), c.errstr(rc)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *conn) retry(pstmt uintptr) error {
|
|
mu := mutexAlloc(c.tls)
|
|
(*mutex)(unsafe.Pointer(mu)).Lock()
|
|
rc := sqlite3.Xsqlite3_unlock_notify(
|
|
c.tls,
|
|
c.db,
|
|
*(*uintptr)(unsafe.Pointer(&struct {
|
|
f func(*libc.TLS, uintptr, int32)
|
|
}{unlockNotify})),
|
|
mu,
|
|
)
|
|
if rc == sqlite3.SQLITE_LOCKED { // Deadlock, see https://www.sqlite.org/c3ref/unlock_notify.html
|
|
(*mutex)(unsafe.Pointer(mu)).Unlock()
|
|
mutexFree(c.tls, mu)
|
|
return c.errstr(rc)
|
|
}
|
|
|
|
(*mutex)(unsafe.Pointer(mu)).Lock()
|
|
(*mutex)(unsafe.Pointer(mu)).Unlock()
|
|
mutexFree(c.tls, mu)
|
|
if pstmt != 0 {
|
|
sqlite3.Xsqlite3_reset(c.tls, pstmt)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *conn) bind(pstmt uintptr, n int, args []driver.NamedValue) (allocs []uintptr, err error) {
|
|
defer func() {
|
|
if err == nil {
|
|
return
|
|
}
|
|
|
|
for _, v := range allocs {
|
|
c.free(v)
|
|
}
|
|
allocs = nil
|
|
}()
|
|
|
|
for i := 1; i <= n; i++ {
|
|
name, err := c.bindParameterName(pstmt, i)
|
|
if err != nil {
|
|
return allocs, err
|
|
}
|
|
|
|
var found bool
|
|
var v driver.NamedValue
|
|
for _, v = range args {
|
|
if name != "" {
|
|
// For ?NNN and $NNN params, match if NNN == v.Ordinal.
|
|
//
|
|
// Supporting this for $NNN is a special case that makes eg
|
|
// `select $1, $2, $3 ...` work without needing to use
|
|
// sql.Named.
|
|
if (name[0] == '?' || name[0] == '$') && name[1:] == strconv.Itoa(v.Ordinal) {
|
|
found = true
|
|
break
|
|
}
|
|
|
|
// sqlite supports '$', '@' and ':' prefixes for string
|
|
// identifiers and '?' for numeric, so we cannot
|
|
// combine different prefixes with the same name
|
|
// because `database/sql` requires variable names
|
|
// to start with a letter
|
|
if name[1:] == v.Name[:] {
|
|
found = true
|
|
break
|
|
}
|
|
} else {
|
|
if v.Ordinal == i {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
if name != "" {
|
|
return allocs, fmt.Errorf("missing named argument %q", name[1:])
|
|
}
|
|
|
|
return allocs, fmt.Errorf("missing argument with index %d", i)
|
|
}
|
|
|
|
var p uintptr
|
|
switch x := v.Value.(type) {
|
|
case int64:
|
|
if err := c.bindInt64(pstmt, i, x); err != nil {
|
|
return allocs, err
|
|
}
|
|
case float64:
|
|
if err := c.bindDouble(pstmt, i, x); err != nil {
|
|
return allocs, err
|
|
}
|
|
case bool:
|
|
v := 0
|
|
if x {
|
|
v = 1
|
|
}
|
|
if err := c.bindInt(pstmt, i, v); err != nil {
|
|
return allocs, err
|
|
}
|
|
case []byte:
|
|
if p, err = c.bindBlob(pstmt, i, x); err != nil {
|
|
return allocs, err
|
|
}
|
|
case string:
|
|
if p, err = c.bindText(pstmt, i, x); err != nil {
|
|
return allocs, err
|
|
}
|
|
case time.Time:
|
|
switch c.integerTimeFormat {
|
|
case "unix":
|
|
if err := c.bindInt64(pstmt, i, x.Unix()); err != nil {
|
|
return allocs, err
|
|
}
|
|
case "unix_milli":
|
|
if err := c.bindInt64(pstmt, i, x.UnixMilli()); err != nil {
|
|
return allocs, err
|
|
}
|
|
case "unix_micro":
|
|
if err := c.bindInt64(pstmt, i, x.UnixMicro()); err != nil {
|
|
return allocs, err
|
|
}
|
|
case "unix_nano":
|
|
if err := c.bindInt64(pstmt, i, x.UnixNano()); err != nil {
|
|
return allocs, err
|
|
}
|
|
default:
|
|
if p, err = c.bindText(pstmt, i, c.formatTime(x)); err != nil {
|
|
return allocs, err
|
|
}
|
|
}
|
|
|
|
case nil:
|
|
if p, err = c.bindNull(pstmt, i); err != nil {
|
|
return allocs, err
|
|
}
|
|
default:
|
|
return allocs, fmt.Errorf("sqlite: invalid driver.Value type %T", x)
|
|
}
|
|
if p != 0 {
|
|
allocs = append(allocs, p)
|
|
}
|
|
}
|
|
return allocs, nil
|
|
}
|
|
|
|
// C documentation
|
|
//
|
|
// int sqlite3_bind_null(sqlite3_stmt*, int);
|
|
func (c *conn) bindNull(pstmt uintptr, idx1 int) (uintptr, error) {
|
|
if rc := sqlite3.Xsqlite3_bind_null(c.tls, pstmt, int32(idx1)); rc != sqlite3.SQLITE_OK {
|
|
return 0, c.errstr(rc)
|
|
}
|
|
|
|
return 0, nil
|
|
}
|
|
|
|
// C documentation
|
|
//
|
|
// int sqlite3_bind_text(sqlite3_stmt*,int,const char*,int,void(*)(void*));
|
|
func (c *conn) bindText(pstmt uintptr, idx1 int, value string) (uintptr, error) {
|
|
p, err := libc.CString(value)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if rc := sqlite3.Xsqlite3_bind_text(c.tls, pstmt, int32(idx1), p, int32(len(value)), 0); rc != sqlite3.SQLITE_OK {
|
|
c.free(p)
|
|
return 0, c.errstr(rc)
|
|
}
|
|
|
|
return p, nil
|
|
}
|
|
|
|
// C documentation
|
|
//
|
|
// int sqlite3_bind_int(sqlite3_stmt*, int, int);
|
|
func (c *conn) bindInt(pstmt uintptr, idx1, value int) (err error) {
|
|
if rc := sqlite3.Xsqlite3_bind_int(c.tls, pstmt, int32(idx1), int32(value)); rc != sqlite3.SQLITE_OK {
|
|
return c.errstr(rc)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// C documentation
|
|
//
|
|
// int sqlite3_bind_double(sqlite3_stmt*, int, double);
|
|
func (c *conn) bindDouble(pstmt uintptr, idx1 int, value float64) (err error) {
|
|
if rc := sqlite3.Xsqlite3_bind_double(c.tls, pstmt, int32(idx1), value); rc != 0 {
|
|
return c.errstr(rc)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// C documentation
|
|
//
|
|
// int sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64);
|
|
func (c *conn) bindInt64(pstmt uintptr, idx1 int, value int64) (err error) {
|
|
if rc := sqlite3.Xsqlite3_bind_int64(c.tls, pstmt, int32(idx1), value); rc != sqlite3.SQLITE_OK {
|
|
return c.errstr(rc)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// C documentation
|
|
//
|
|
// const char *sqlite3_bind_parameter_name(sqlite3_stmt*, int);
|
|
func (c *conn) bindParameterName(pstmt uintptr, i int) (string, error) {
|
|
p := sqlite3.Xsqlite3_bind_parameter_name(c.tls, pstmt, int32(i))
|
|
return libc.GoString(p), nil
|
|
}
|
|
|
|
// C documentation
|
|
//
|
|
// int sqlite3_bind_parameter_count(sqlite3_stmt*);
|
|
func (c *conn) bindParameterCount(pstmt uintptr) (_ int, err error) {
|
|
r := sqlite3.Xsqlite3_bind_parameter_count(c.tls, pstmt)
|
|
return int(r), nil
|
|
}
|
|
|
|
// C documentation
|
|
//
|
|
// int sqlite3_finalize(sqlite3_stmt *pStmt);
|
|
func (c *conn) finalize(pstmt uintptr) error {
|
|
if rc := sqlite3.Xsqlite3_finalize(c.tls, pstmt); rc != sqlite3.SQLITE_OK {
|
|
return c.errstr(rc)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// C documentation
|
|
//
|
|
// int sqlite3_prepare_v2(
|
|
// sqlite3 *db, /* Database handle */
|
|
// const char *zSql, /* SQL statement, UTF-8 encoded */
|
|
// int nByte, /* Maximum length of zSql in bytes. */
|
|
// sqlite3_stmt **ppStmt, /* OUT: Statement handle */
|
|
// const char **pzTail /* OUT: Pointer to unused portion of zSql */
|
|
// );
|
|
func (c *conn) prepareV2(zSQL *uintptr) (pstmt uintptr, err error) {
|
|
var ppstmt, pptail uintptr
|
|
|
|
defer func() {
|
|
c.free(ppstmt)
|
|
c.free(pptail)
|
|
}()
|
|
|
|
if ppstmt, err = c.malloc(int(ptrSize)); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if pptail, err = c.malloc(int(ptrSize)); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
for {
|
|
// https://gitlab.com/cznic/sqlite/-/issues/236
|
|
// trc("Xsqlite3_prepare_v2(`%s`)", libc.GoString(*zSQL))
|
|
switch rc := sqlite3.Xsqlite3_prepare_v2(c.tls, c.db, *zSQL, -1, ppstmt, pptail); rc {
|
|
case sqlite3.SQLITE_OK:
|
|
*zSQL = *(*uintptr)(unsafe.Pointer(pptail))
|
|
return *(*uintptr)(unsafe.Pointer(ppstmt)), nil
|
|
case sqliteLockedSharedcache:
|
|
if err := c.retry(0); err != nil {
|
|
return 0, err
|
|
}
|
|
default:
|
|
return 0, c.errstr(rc)
|
|
}
|
|
}
|
|
}
|
|
|
|
// C documentation
|
|
//
|
|
// void sqlite3_interrupt(sqlite3*);
|
|
func (c *conn) interrupt(pdb uintptr) (err error) {
|
|
c.Lock() // Defend against race with .Close invoked by context handling.
|
|
|
|
defer c.Unlock()
|
|
|
|
if c.tls != nil {
|
|
sqlite3.Xsqlite3_interrupt(c.tls, pdb)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// C documentation
|
|
//
|
|
// int sqlite3_extended_result_codes(sqlite3*, int onoff);
|
|
func (c *conn) extendedResultCodes(on bool) error {
|
|
if rc := sqlite3.Xsqlite3_extended_result_codes(c.tls, c.db, libc.Bool32(on)); rc != sqlite3.SQLITE_OK {
|
|
return c.errstr(rc)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// C documentation
|
|
//
|
|
// int sqlite3_open_v2(
|
|
// const char *filename, /* Database filename (UTF-8) */
|
|
// sqlite3 **ppDb, /* OUT: SQLite db handle */
|
|
// int flags, /* Flags */
|
|
// const char *zVfs /* Name of VFS module to use */
|
|
// );
|
|
func (c *conn) openV2(name, vfsName string, flags int32) (uintptr, error) {
|
|
var p, s, vfs uintptr
|
|
|
|
defer func() {
|
|
if p != 0 {
|
|
c.free(p)
|
|
}
|
|
if s != 0 {
|
|
c.free(s)
|
|
}
|
|
if vfs != 0 {
|
|
c.free(vfs)
|
|
}
|
|
}()
|
|
|
|
p, err := c.malloc(int(ptrSize))
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if s, err = libc.CString(name); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if vfsName != "" {
|
|
if vfs, err = libc.CString(vfsName); err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
|
|
if rc := sqlite3.Xsqlite3_open_v2(c.tls, s, p, flags, vfs); rc != sqlite3.SQLITE_OK {
|
|
return 0, c.errstr(rc)
|
|
}
|
|
|
|
return *(*uintptr)(unsafe.Pointer(p)), nil
|
|
}
|
|
|
|
func (c *conn) malloc(n int) (uintptr, error) {
|
|
if p := libc.Xmalloc(c.tls, types.Size_t(n)); p != 0 || n == 0 {
|
|
return p, nil
|
|
}
|
|
|
|
return 0, fmt.Errorf("sqlite: cannot allocate %d bytes of memory", n)
|
|
}
|
|
|
|
func (c *conn) free(p uintptr) {
|
|
if p != 0 {
|
|
libc.Xfree(c.tls, p)
|
|
}
|
|
}
|
|
|
|
// C documentation
|
|
//
|
|
// const char *sqlite3_errstr(int);
|
|
func (c *conn) errstr(rc int32) error {
|
|
p := sqlite3.Xsqlite3_errstr(c.tls, rc)
|
|
str := libc.GoString(p)
|
|
p = sqlite3.Xsqlite3_errmsg(c.tls, c.db)
|
|
var s string
|
|
if rc == sqlite3.SQLITE_BUSY {
|
|
s = " (SQLITE_BUSY)"
|
|
}
|
|
switch msg := libc.GoString(p); {
|
|
case msg == str:
|
|
return &Error{msg: fmt.Sprintf("%s (%v)%s", str, rc, s), code: int(rc)}
|
|
default:
|
|
return &Error{msg: fmt.Sprintf("%s: %s (%v)%s", str, msg, rc, s), code: int(rc)}
|
|
}
|
|
}
|
|
|
|
// Begin starts a transaction.
|
|
//
|
|
// Deprecated: Drivers should implement ConnBeginTx instead (or additionally).
|
|
func (c *conn) Begin() (dt driver.Tx, err error) {
|
|
if dmesgs {
|
|
defer func() {
|
|
dmesg("conn %p: (driver.Tx %p, err %v)", c, dt, err)
|
|
}()
|
|
}
|
|
return c.begin(context.Background(), driver.TxOptions{})
|
|
}
|
|
|
|
func (c *conn) begin(ctx context.Context, opts driver.TxOptions) (t driver.Tx, err error) {
|
|
return newTx(ctx, c, opts)
|
|
}
|
|
|
|
// Close invalidates and potentially stops any current prepared statements and
|
|
// transactions, marking this connection as no longer in use.
|
|
//
|
|
// Because the sql package maintains a free pool of connections and only calls
|
|
// Close when there's a surplus of idle connections, it shouldn't be necessary
|
|
// for drivers to do their own connection caching.
|
|
func (c *conn) Close() (err error) {
|
|
if dmesgs {
|
|
defer func() {
|
|
dmesg("conn %p: err %v", c, err)
|
|
}()
|
|
}
|
|
c.Lock() // Defend against race with .interrupt invoked by context handling.
|
|
|
|
defer c.Unlock()
|
|
|
|
if c.db != 0 {
|
|
if err := c.closeV2(c.db); err != nil {
|
|
return err
|
|
}
|
|
|
|
c.db = 0
|
|
}
|
|
|
|
if c.tls != nil {
|
|
c.tls.Close()
|
|
c.tls = nil
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// C documentation
|
|
//
|
|
// int sqlite3_close_v2(sqlite3*);
|
|
func (c *conn) closeV2(db uintptr) error {
|
|
if rc := sqlite3.Xsqlite3_close_v2(c.tls, db); rc != sqlite3.SQLITE_OK {
|
|
return c.errstr(rc)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ResetSession is called prior to executing a query on the connection if the
|
|
// connection has been used before. If the driver returns ErrBadConn the
|
|
// connection is discarded.
|
|
func (c *conn) ResetSession(ctx context.Context) error {
|
|
if !c.usable() {
|
|
return driver.ErrBadConn
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// IsValid is called prior to placing the connection into the connection pool.
|
|
// The connection will be discarded if false is returned.
|
|
func (c *conn) IsValid() bool {
|
|
return c.usable()
|
|
}
|
|
|
|
func (c *conn) usable() bool {
|
|
return c.db != 0 && sqlite3.Xsqlite3_is_interrupted(c.tls, c.db) == 0
|
|
}
|
|
|
|
type userDefinedFunction struct {
|
|
zFuncName uintptr
|
|
nArg int32
|
|
eTextRep int32
|
|
pApp uintptr
|
|
|
|
scalar bool
|
|
freeOnce sync.Once
|
|
}
|
|
|
|
func (c *conn) createFunctionInternal(fun *userDefinedFunction) error {
|
|
var rc int32
|
|
|
|
if fun.scalar {
|
|
rc = sqlite3.Xsqlite3_create_function(
|
|
c.tls,
|
|
c.db,
|
|
fun.zFuncName,
|
|
fun.nArg,
|
|
fun.eTextRep,
|
|
fun.pApp,
|
|
cFuncPointer(funcTrampoline),
|
|
0,
|
|
0,
|
|
)
|
|
} else {
|
|
rc = sqlite3.Xsqlite3_create_window_function(
|
|
c.tls,
|
|
c.db,
|
|
fun.zFuncName,
|
|
fun.nArg,
|
|
fun.eTextRep,
|
|
fun.pApp,
|
|
cFuncPointer(stepTrampoline),
|
|
cFuncPointer(finalTrampoline),
|
|
cFuncPointer(valueTrampoline),
|
|
cFuncPointer(inverseTrampoline),
|
|
0,
|
|
)
|
|
}
|
|
|
|
if rc != sqlite3.SQLITE_OK {
|
|
return c.errstr(rc)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *conn) createCollationInternal(coll *collation) error {
|
|
rc := sqlite3.Xsqlite3_create_collation_v2(
|
|
c.tls,
|
|
c.db,
|
|
coll.zName,
|
|
coll.enc,
|
|
coll.pApp,
|
|
cFuncPointer(collationTrampoline),
|
|
0,
|
|
)
|
|
if rc != sqlite3.SQLITE_OK {
|
|
return c.errstr(rc)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Execer is an optional interface that may be implemented by a Conn.
|
|
//
|
|
// If a Conn does not implement Execer, the sql package's DB.Exec will first
|
|
// prepare a query, execute the statement, and then close the statement.
|
|
//
|
|
// Exec may return ErrSkip.
|
|
//
|
|
// Deprecated: Drivers should implement ExecerContext instead.
|
|
func (c *conn) Exec(query string, args []driver.Value) (dr driver.Result, err error) {
|
|
if dmesgs {
|
|
defer func() {
|
|
dmesg("conn %p, query %q, args %v: (driver.Result %p, err %v)", c, query, args, dr, err)
|
|
}()
|
|
}
|
|
return c.exec(context.Background(), query, toNamedValues(args))
|
|
}
|
|
|
|
func (c *conn) exec(ctx context.Context, query string, args []driver.NamedValue) (r driver.Result, err error) {
|
|
s, err := c.prepare(ctx, query)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer func() {
|
|
if err2 := s.Close(); err2 != nil && err == nil {
|
|
err = err2
|
|
}
|
|
}()
|
|
|
|
return s.(*stmt).exec(ctx, args)
|
|
}
|
|
|
|
// Prepare returns a prepared statement, bound to this connection.
|
|
func (c *conn) Prepare(query string) (ds driver.Stmt, err error) {
|
|
if dmesgs {
|
|
defer func() {
|
|
dmesg("conn %p, query %q: (driver.Stmt %p, err %v)", c, query, ds, err)
|
|
}()
|
|
}
|
|
return c.prepare(context.Background(), query)
|
|
}
|
|
|
|
func (c *conn) prepare(ctx context.Context, query string) (s driver.Stmt, err error) {
|
|
//TODO use ctx
|
|
return newStmt(c, query)
|
|
}
|
|
|
|
// Queryer is an optional interface that may be implemented by a Conn.
|
|
//
|
|
// If a Conn does not implement Queryer, the sql package's DB.Query will first
|
|
// prepare a query, execute the statement, and then close the statement.
|
|
//
|
|
// Query may return ErrSkip.
|
|
//
|
|
// Deprecated: Drivers should implement QueryerContext instead.
|
|
func (c *conn) Query(query string, args []driver.Value) (dr driver.Rows, err error) {
|
|
if dmesgs {
|
|
defer func() {
|
|
dmesg("conn %p, query %q, args %v: (driver.Rows %p, err %v)", c, query, args, dr, err)
|
|
}()
|
|
}
|
|
return c.query(context.Background(), query, toNamedValues(args))
|
|
}
|
|
|
|
func (c *conn) query(ctx context.Context, query string, args []driver.NamedValue) (r driver.Rows, err error) {
|
|
// Use newStmt directly. c.prepare wraps this, but we need the concrete *stmt type
|
|
// to manipulate the handle ownership below.
|
|
s, err := newStmt(c, query)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
r, err = s.query(ctx, args)
|
|
if err != nil {
|
|
s.Close()
|
|
return nil, err
|
|
}
|
|
|
|
// Fix for TestIssue118 / One-Shot Query Crash:
|
|
// If the statement was cached (optimized path in newStmt), s.pstmt is valid.
|
|
// s.query() sets r.reuseStmt = true by default for cached statements.
|
|
//
|
|
// However, since this is a transient statement created just for this query (db.Query),
|
|
// s.Close() will be called immediately below. We must transfer ownership of the
|
|
// sqlite statement handle to the rows object so it isn't finalized yet.
|
|
if s.pstmt != 0 {
|
|
// Steal the handle from the statement so s.Close() doesn't finalize it.
|
|
s.pstmt = 0
|
|
|
|
// Instruct rows to finalize the statement when done, rather than resetting it.
|
|
r.(*rows).reuseStmt = false
|
|
}
|
|
|
|
// s.Close() now only frees the C-string allocation (psql), skipping the pstmt finalize
|
|
// because we set s.pstmt to 0.
|
|
if err := s.Close(); err != nil {
|
|
r.Close()
|
|
return nil, err
|
|
}
|
|
|
|
return r, nil
|
|
}
|
|
|
|
// Serialize returns a serialization of the main database. For an ordinary on-disk
|
|
// database file, the serialization is just a copy of the disk file. For an in-memory
|
|
// database or a "TEMP" database, the serialization is the same sequence of bytes
|
|
// which would be written to disk if that database where backed up to disk.
|
|
func (c *conn) Serialize() (v []byte, err error) {
|
|
pLen := c.tls.Alloc(8)
|
|
defer c.tls.Free(8)
|
|
|
|
zSchema := sqlite3.Xsqlite3_db_name(c.tls, c.db, 0)
|
|
if zSchema == 0 {
|
|
return nil, fmt.Errorf("failed to get main db name")
|
|
}
|
|
|
|
pBuf := sqlite3.Xsqlite3_serialize(c.tls, c.db, zSchema, pLen, 0)
|
|
bufLen := *(*sqlite3.Sqlite3_int64)(unsafe.Pointer(pLen))
|
|
if pBuf != 0 {
|
|
defer sqlite3.Xsqlite3_free(c.tls, pBuf)
|
|
}
|
|
if bufLen <= 0 {
|
|
return nil, fmt.Errorf("invalid length returned: %d", bufLen)
|
|
} else if pBuf == 0 || bufLen == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
v = make([]byte, bufLen)
|
|
copy(v, (*libc.RawMem)(unsafe.Pointer(pBuf))[:bufLen:bufLen])
|
|
return v, nil
|
|
}
|
|
|
|
// Deserialize restore a database from the content returned by Serialize.
|
|
func (c *conn) Deserialize(buf []byte) (err error) {
|
|
bufLen := len(buf)
|
|
pBuf := c.tls.Alloc(bufLen) // free will be done if it fails or on close, must not be freed here
|
|
|
|
copy((*libc.RawMem)(unsafe.Pointer(pBuf))[:bufLen:bufLen], buf)
|
|
|
|
zSchema := sqlite3.Xsqlite3_db_name(c.tls, c.db, 0)
|
|
if zSchema == 0 {
|
|
return fmt.Errorf("failed to get main db name")
|
|
}
|
|
|
|
rc := sqlite3.Xsqlite3_deserialize(c.tls, c.db, zSchema, pBuf, int64(bufLen), int64(bufLen), sqlite3.SQLITE_DESERIALIZE_RESIZEABLE|sqlite3.SQLITE_DESERIALIZE_FREEONCLOSE)
|
|
if rc != sqlite3.SQLITE_OK {
|
|
return c.errstr(rc)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NewBackup returns a Backup object that will create an online backup of
|
|
// current database to the databased pointed by the passed URI.
|
|
func (c *conn) NewBackup(dstUri string) (*Backup, error) {
|
|
dstConn, err := newConn(dstUri)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
backup, err := c.backup(dstConn, false)
|
|
if err != nil {
|
|
dstConn.Close()
|
|
}
|
|
return backup, err
|
|
}
|
|
|
|
// NewRestore returns a Backup object that will restore a backup to current
|
|
// database from the databased pointed by the passed URI.
|
|
func (c *conn) NewRestore(srcUri string) (*Backup, error) {
|
|
srcConn, err := newConn(srcUri)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
backup, err := c.backup(srcConn, true)
|
|
if err != nil {
|
|
srcConn.Close()
|
|
}
|
|
return backup, err
|
|
}
|
|
|
|
func (c *conn) backup(remoteConn *conn, restore bool) (_ *Backup, finalErr error) {
|
|
srcSchema := sqlite3.Xsqlite3_db_name(c.tls, c.db, 0)
|
|
if srcSchema == 0 {
|
|
return nil, fmt.Errorf("failed to get main source db name")
|
|
}
|
|
|
|
dstSchema := sqlite3.Xsqlite3_db_name(remoteConn.tls, remoteConn.db, 0)
|
|
if dstSchema == 0 {
|
|
return nil, fmt.Errorf("failed to get main destination db name")
|
|
}
|
|
|
|
var pBackup uintptr
|
|
if restore {
|
|
pBackup = sqlite3.Xsqlite3_backup_init(c.tls, c.db, srcSchema, remoteConn.db, dstSchema)
|
|
} else {
|
|
pBackup = sqlite3.Xsqlite3_backup_init(c.tls, remoteConn.db, dstSchema, c.db, srcSchema)
|
|
}
|
|
if pBackup <= 0 {
|
|
rc := sqlite3.Xsqlite3_errcode(c.tls, remoteConn.db)
|
|
return nil, c.errstr(rc)
|
|
}
|
|
|
|
return &Backup{srcConn: c, dstConn: remoteConn, pBackup: pBackup}, nil
|
|
}
|
|
|
|
// C documentation
|
|
//
|
|
// int sqlite3_limit(sqlite3*, int id, int newVal);
|
|
func (c *conn) limit(id int, newVal int) int {
|
|
return int(sqlite3.Xsqlite3_limit(c.tls, c.db, int32(id), int32(newVal)))
|
|
}
|
|
|
|
// C documentation
|
|
//
|
|
// int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*));
|
|
func (c *conn) bindBlob(pstmt uintptr, idx1 int, value []byte) (uintptr, error) {
|
|
if value == nil {
|
|
if rc := sqlite3.Xsqlite3_bind_null(c.tls, pstmt, int32(idx1)); rc != sqlite3.SQLITE_OK {
|
|
return 0, c.errstr(rc)
|
|
}
|
|
return 0, nil
|
|
}
|
|
|
|
p, err := c.malloc(len(value))
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if len(value) != 0 {
|
|
copy((*libc.RawMem)(unsafe.Pointer(p))[:len(value):len(value)], value)
|
|
}
|
|
if rc := sqlite3.Xsqlite3_bind_blob(c.tls, pstmt, int32(idx1), p, int32(len(value)), 0); rc != sqlite3.SQLITE_OK {
|
|
c.free(p)
|
|
return 0, c.errstr(rc)
|
|
}
|
|
|
|
return p, nil
|
|
}
|
|
|
|
// Ping implements driver.Pinger
|
|
func (c *conn) Ping(ctx context.Context) (err error) {
|
|
if dmesgs {
|
|
defer func() {
|
|
dmesg("conn %p, ctx %p: err %v", c, ctx, err)
|
|
}()
|
|
}
|
|
_, err = c.ExecContext(ctx, "select 1", nil)
|
|
return err
|
|
}
|
|
|
|
// BeginTx implements driver.ConnBeginTx
|
|
func (c *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (dt driver.Tx, err error) {
|
|
if dmesgs {
|
|
defer func() {
|
|
dmesg("conn %p, ctx %p, opts %+v: (driver.Tx %v, err %v)", c, ctx, opts, dt, err)
|
|
}()
|
|
}
|
|
return c.begin(ctx, opts)
|
|
}
|
|
|
|
// PrepareContext implements driver.ConnPrepareContext
|
|
func (c *conn) PrepareContext(ctx context.Context, query string) (ds driver.Stmt, err error) {
|
|
if dmesgs {
|
|
defer func() {
|
|
dmesg("conn %p, ctx %p, query %q: (driver.Stmt %v, err %v)", c, ctx, query, ds, err)
|
|
}()
|
|
}
|
|
return c.prepare(ctx, query)
|
|
}
|
|
|
|
// ExecContext implements driver.ExecerContext
|
|
func (c *conn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (dr driver.Result, err error) {
|
|
if dmesgs {
|
|
defer func() {
|
|
dmesg("conn %p, ctx %p, query %q, args %v: (driver.Result %p, err %v)", c, ctx, query, args, dr, err)
|
|
}()
|
|
}
|
|
return c.exec(ctx, query, args)
|
|
}
|
|
|
|
// QueryContext implements driver.QueryerContext
|
|
func (c *conn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (dr driver.Rows, err error) {
|
|
if dmesgs {
|
|
defer func() {
|
|
dmesg("conn %p, ctx %p, query %q, args %v: (driver.Rows %p, err %v)", c, ctx, query, args, dr, err)
|
|
}()
|
|
}
|
|
return c.query(ctx, query, args)
|
|
}
|
|
|
|
// IsReadOnly reports whether the database schema specified by dbName is read-only.
|
|
//
|
|
// dbName is the internal name of the attached database, not the filename.
|
|
// Use "main" for the primary database, "temp" for the temporary database,
|
|
// or the name used in an ATTACH statement.
|
|
func (c *conn) IsReadOnly(schema string) (bool, error) {
|
|
if dmesgs {
|
|
defer func() {
|
|
dmesg("conn %p", c)
|
|
}()
|
|
}
|
|
cs, err := libc.CString(schema)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
defer libc.Xfree(c.tls, cs)
|
|
|
|
switch r := sqlite3.Xsqlite3_db_readonly(c.tls, c.db, cs); r {
|
|
case 1:
|
|
return true, nil
|
|
case 0:
|
|
return false, nil
|
|
case -1:
|
|
return false, fmt.Errorf("not a name of a database on connection: '%s'", schema)
|
|
default:
|
|
return false, fmt.Errorf("unexpected sqlite3_db_readonly(%q) return value: %v", schema, r)
|
|
}
|
|
}
|