package sqlite3
import (
"context"
"fmt"
"iter"
"math"
"math/rand"
"net/url"
"runtime"
"strings"
"time"
"github.com/tetratelabs/wazero/api"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/vfs"
)
type Conn struct {
*sqlite
interrupt context .Context
stmts []*Stmt
busy func (context .Context , int ) bool
log func (xErrorCode , string )
collation func (*Conn , string )
wal func (*Conn , string , int ) error
trace func (TraceEvent , any , any ) error
authorizer func (AuthorizerActionCode , string , string , string , string ) AuthorizerReturnCode
update func (AuthorizerActionCode , string , string , int64 )
commit func () bool
rollback func ()
busy1st time .Time
busylst time .Time
arena arena
handle ptr_t
gosched uint8
}
func Open (filename string ) (*Conn , error ) {
return newConn (context .Background (), filename , OPEN_READWRITE |OPEN_CREATE |OPEN_URI )
}
func OpenContext (ctx context .Context , filename string ) (*Conn , error ) {
return newConn (ctx , filename , OPEN_READWRITE |OPEN_CREATE |OPEN_URI )
}
func OpenFlags (filename string , flags OpenFlag ) (*Conn , error ) {
if flags &(OPEN_READONLY |OPEN_READWRITE |OPEN_CREATE ) == 0 {
flags |= OPEN_READWRITE | OPEN_CREATE
}
return newConn (context .Background (), filename , flags )
}
type connKey = util .ConnKey
func newConn(ctx context .Context , filename string , flags OpenFlag ) (ret *Conn , _ error ) {
err := ctx .Err ()
if err != nil {
return nil , err
}
c := &Conn {interrupt : ctx }
c .sqlite , err = instantiateSQLite ()
if err != nil {
return nil , err
}
defer func () {
if ret == nil {
c .Close ()
c .sqlite .close ()
} else {
c .interrupt = context .Background ()
}
}()
c .ctx = context .WithValue (c .ctx , connKey {}, c )
if logger := defaultLogger .Load (); logger != nil {
c .ConfigLog (*logger )
}
c .arena = c .newArena ()
c .handle , err = c .openDB (filename , flags )
if err == nil {
err = initExtensions (c )
}
if err != nil {
return nil , err
}
return c , nil
}
func (c *Conn ) openDB (filename string , flags OpenFlag ) (ptr_t , error ) {
defer c .arena .mark ()()
connPtr := c .arena .new (ptrlen )
namePtr := c .arena .string (filename )
flags |= OPEN_EXRESCODE
rc := res_t (c .call ("sqlite3_open_v2" , stk_t (namePtr ), stk_t (connPtr ), stk_t (flags ), 0 ))
handle := util .Read32 [ptr_t ](c .mod , connPtr )
if err := c .sqlite .error (rc , handle ); err != nil {
c .closeDB (handle )
return 0 , err
}
c .call ("sqlite3_progress_handler_go" , stk_t (handle ), 1000 )
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 {
pragmaPtr := c .arena .string (pragmas .String ())
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 {
err = fmt .Errorf ("sqlite3: invalid _pragma: %w" , err )
c .closeDB (handle )
return 0 , err
}
}
}
return handle , nil
}
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 {
panic (err )
}
}
func (c *Conn ) Close () error {
if c == nil || c .handle == 0 {
return nil
}
rc := res_t (c .call ("sqlite3_close" , stk_t (c .handle )))
if err := c .error (rc ); err != nil {
return err
}
c .handle = 0
return c .close ()
}
func (c *Conn ) Exec (sql string ) error {
if c .interrupt .Err () != nil {
return INTERRUPT
}
return c .exec (sql )
}
func (c *Conn ) exec (sql string ) error {
defer c .arena .mark ()()
textPtr := c .arena .string (sql )
rc := res_t (c .call ("sqlite3_exec" , stk_t (c .handle ), stk_t (textPtr ), 0 , 0 , 0 ))
return c .error (rc , sql )
}
func (c *Conn ) Prepare (sql string ) (stmt *Stmt , tail string , err error ) {
return c .PrepareFlags (sql , 0 )
}
func (c *Conn ) PrepareFlags (sql string , flags PrepareFlag ) (stmt *Stmt , tail string , err error ) {
if len (sql ) > _MAX_SQL_LENGTH {
return nil , "" , TOOBIG
}
if c .interrupt .Err () != nil {
return nil , "" , INTERRUPT
}
defer c .arena .mark ()()
stmtPtr := c .arena .new (ptrlen )
tailPtr := c .arena .new (ptrlen )
textPtr := c .arena .string (sql )
rc := res_t (c .call ("sqlite3_prepare_v3" , stk_t (c .handle ),
stk_t (textPtr ), stk_t (len (sql )+1 ), stk_t (flags ),
stk_t (stmtPtr ), stk_t (tailPtr )))
stmt = &Stmt {c : c , sql : sql }
stmt .handle = util .Read32 [ptr_t ](c .mod , stmtPtr )
if sql := sql [util .Read32 [ptr_t ](c .mod , tailPtr )-textPtr :]; sql != "" {
tail = sql
}
if err := c .error (rc , sql ); err != nil {
return nil , "" , err
}
if stmt .handle == 0 {
return nil , "" , nil
}
c .stmts = append (c .stmts , stmt )
return stmt , tail , nil
}
func (c *Conn ) DBName (n int ) string {
ptr := ptr_t (c .call ("sqlite3_db_name" , stk_t (c .handle ), stk_t (n )))
if ptr == 0 {
return ""
}
return util .ReadString (c .mod , ptr , _MAX_NAME )
}
func (c *Conn ) Filename (schema string ) *vfs .Filename {
var ptr ptr_t
if schema != "" {
defer c .arena .mark ()()
ptr = c .arena .string (schema )
}
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 )
}
func (c *Conn ) ReadOnly (schema string ) (ro bool , ok bool ) {
var ptr ptr_t
if schema != "" {
defer c .arena .mark ()()
ptr = c .arena .string (schema )
}
b := int32 (c .call ("sqlite3_db_readonly" , stk_t (c .handle ), stk_t (ptr )))
return b > 0 , b < 0
}
func (c *Conn ) GetAutocommit () bool {
b := int32 (c .call ("sqlite3_get_autocommit" , stk_t (c .handle )))
return b != 0
}
func (c *Conn ) LastInsertRowID () int64 {
return int64 (c .call ("sqlite3_last_insert_rowid" , stk_t (c .handle )))
}
func (c *Conn ) SetLastInsertRowID (id int64 ) {
c .call ("sqlite3_set_last_insert_rowid" , stk_t (c .handle ), stk_t (id ))
}
func (c *Conn ) Changes () int64 {
return int64 (c .call ("sqlite3_changes64" , stk_t (c .handle )))
}
func (c *Conn ) TotalChanges () int64 {
return int64 (c .call ("sqlite3_total_changes64" , stk_t (c .handle )))
}
func (c *Conn ) ReleaseMemory () error {
rc := res_t (c .call ("sqlite3_db_release_memory" , stk_t (c .handle )))
return c .error (rc )
}
func (c *Conn ) GetInterrupt () context .Context {
return c .interrupt
}
func (c *Conn ) SetInterrupt (ctx context .Context ) (old context .Context ) {
if ctx == nil {
panic ("nil Context" )
}
old = c .interrupt
c .interrupt = ctx
return old
}
func progressCallback(ctx context .Context , mod api .Module , _ ptr_t ) (interrupt int32 ) {
if c , ok := ctx .Value (connKey {}).(*Conn ); ok {
if c .gosched ++; c .gosched %16 == 0 {
runtime .Gosched ()
}
if c .interrupt .Err () != nil {
interrupt = 1
}
}
return interrupt
}
func (c *Conn ) BusyTimeout (timeout time .Duration ) error {
ms := min ((timeout +time .Millisecond -1 )/time .Millisecond , math .MaxInt32 )
rc := res_t (c .call ("sqlite3_busy_timeout" , stk_t (c .handle ), stk_t (ms )))
return c .error (rc )
}
func timeoutCallback(ctx context .Context , mod api .Module , count , tmout int32 ) (retry int32 ) {
if c , ok := ctx .Value (connKey {}).(*Conn ); ok && c .interrupt .Err () == nil {
switch {
case count == 0 :
c .busy1st = time .Now ()
case time .Since (c .busy1st ) >= time .Duration (tmout )*time .Millisecond :
return 0
}
if time .Since (c .busylst ) < time .Millisecond {
const sleepIncrement = 2 *1024 *1024 - 1
time .Sleep (time .Duration (rand .Int63 () & sleepIncrement ))
}
c .busylst = time .Now ()
return 1
}
return 0
}
func (c *Conn ) BusyHandler (cb func (ctx context .Context , count int ) (retry bool )) error {
var enable int32
if cb != nil {
enable = 1
}
rc := res_t (c .call ("sqlite3_busy_handler_go" , stk_t (c .handle ), stk_t (enable )))
if err := c .error (rc ); err != nil {
return err
}
c .busy = cb
return nil
}
func busyCallback(ctx context .Context , mod api .Module , pDB ptr_t , count int32 ) (retry int32 ) {
if c , ok := ctx .Value (connKey {}).(*Conn ); ok && c .handle == pDB && c .busy != nil {
if interrupt := c .interrupt ; interrupt .Err () == nil &&
c .busy (interrupt , int (count )) {
retry = 1
}
}
return retry
}
func (c *Conn ) Status (op DBStatus , reset bool ) (current , highwater int , err error ) {
defer c .arena .mark ()()
hiPtr := c .arena .new (intlen )
curPtr := c .arena .new (intlen )
var i int32
if reset {
i = 1
}
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 ))
}
return
}
func (c *Conn ) TableColumnMetadata (schema , table , column string ) (declType , collSeq string , notNull , primaryKey , autoInc bool , err error ) {
defer c .arena .mark ()()
var (
declTypePtr ptr_t
collSeqPtr ptr_t
notNullPtr ptr_t
primaryKeyPtr ptr_t
autoIncPtr ptr_t
columnPtr ptr_t
schemaPtr ptr_t
)
if column != "" {
declTypePtr = c .arena .new (ptrlen )
collSeqPtr = c .arena .new (ptrlen )
notNullPtr = c .arena .new (ptrlen )
primaryKeyPtr = c .arena .new (ptrlen )
autoIncPtr = c .arena .new (ptrlen )
columnPtr = c .arena .string (column )
}
if schema != "" {
schemaPtr = c .arena .string (schema )
}
tablePtr := c .arena .string (table )
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 {
declType = util .ReadString (c .mod , ptr , _MAX_NAME )
}
if ptr := util .Read32 [ptr_t ](c .mod , collSeqPtr ); ptr != 0 {
collSeq = util .ReadString (c .mod , ptr , _MAX_NAME )
}
notNull = util .ReadBool (c .mod , notNullPtr )
autoInc = util .ReadBool (c .mod , autoIncPtr )
primaryKey = util .ReadBool (c .mod , primaryKeyPtr )
}
return
}
func (c *Conn ) error (rc res_t , sql ...string ) error {
return c .sqlite .error (rc , c .handle , sql ...)
}
func (c *Conn ) Stmts () iter .Seq [*Stmt ] {
return func (yield func (*Stmt ) bool ) {
for _ , s := range c .stmts {
if !yield (s ) {
break
}
}
}
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .