package sqlite3
import (
"context"
"fmt"
"iter"
"math"
"math/rand"
"net/url"
"runtime"
"strings"
"time"
"github.com/ncruces/go-sqlite3/internal/errutil"
"github.com/ncruces/go-sqlite3/internal/sqlite3_wrap"
"github.com/ncruces/go-sqlite3/vfs"
)
type Conn struct {
wrp *sqlite3_wrap .Wrapper
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 sqlite3_wrap .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 )
}
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 .wrp , err = createWrapper (ctx )
if err != nil {
return nil , err
}
defer func () {
if ret == nil {
c .Close ()
} else {
c .interrupt = context .Background ()
}
}()
c .wrp .DB = c
if logger := defaultLogger .Load (); logger != nil {
c .ConfigLog (*logger )
}
c .arena = c .wrp .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 .wrp .Xsqlite3_open_v2 (int32 (namePtr ), int32 (connPtr ), int32 (flags ), 0 ))
handle := ptr_t (c .wrp .Read32 (connPtr ))
if err := c .errorFor (handle , rc ); err != nil {
c .closeDB (handle )
return 0 , err
}
c .wrp .Xsqlite3_progress_handler_go (int32 (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 .wrp .Xsqlite3_exec (int32 (handle ), int32 (pragmaPtr ), 0 , 0 , 0 ))
if err := c .errorFor (handle , rc , 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 .wrp .Xsqlite3_close_v2 (int32 (handle )))
if err := c .errorFor (handle , rc ); err != nil {
panic (err )
}
}
func (c *Conn ) Close () error {
if c == nil || c .handle == 0 {
return nil
}
rc := res_t (c .wrp .Xsqlite3_close (int32 (c .handle )))
if err := c .error (rc ); err != nil {
return err
}
c .handle = 0
return c .wrp .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 .wrp .Xsqlite3_exec (int32 (c .handle ), int32 (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 .wrp .Xsqlite3_prepare_v3 (int32 (c .handle ),
int32 (textPtr ), int32 (len (sql )+1 ), int32 (flags ),
int32 (stmtPtr ), int32 (tailPtr )))
stmt = &Stmt {c : c , sql : sql }
stmt .handle = ptr_t (c .wrp .Read32 (stmtPtr ))
if sql := sql [ptr_t (c .wrp .Read32 (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 .wrp .Xsqlite3_db_name (int32 (c .handle ), int32 (n )))
if ptr == 0 {
return ""
}
return c .wrp .ReadString (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 .wrp .Xsqlite3_db_filename (int32 (c .handle ), int32 (ptr )))
return vfs .GetFilename (c .wrp , 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 := c .wrp .Xsqlite3_db_readonly (int32 (c .handle ), int32 (ptr ))
return b > 0 , b < 0
}
func (c *Conn ) GetAutocommit () bool {
b := c .wrp .Xsqlite3_get_autocommit (int32 (c .handle ))
return b != 0
}
func (c *Conn ) LastInsertRowID () int64 {
return c .wrp .Xsqlite3_last_insert_rowid (int32 (c .handle ))
}
func (c *Conn ) SetLastInsertRowID (id int64 ) {
c .wrp .Xsqlite3_set_last_insert_rowid (int32 (c .handle ), id )
}
func (c *Conn ) Changes () int64 {
return c .wrp .Xsqlite3_changes64 (int32 (c .handle ))
}
func (c *Conn ) TotalChanges () int64 {
return c .wrp .Xsqlite3_total_changes64 (int32 (c .handle ))
}
func (c *Conn ) ReleaseMemory () error {
rc := res_t (c .wrp .Xsqlite3_db_release_memory (int32 (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 (e *env ) Xgo_progress_handler (_ int32 ) (interrupt int32 ) {
if c , ok := e .DB .(*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 .wrp .Xsqlite3_busy_timeout (int32 (c .handle ), int32 (ms )))
return c .error (rc )
}
func (e *env ) Xgo_busy_timeout (count , tmout int32 ) (retry int32 ) {
if c , ok := e .DB .(*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 .wrp .Xsqlite3_busy_handler_go (int32 (c .handle ), enable ))
if err := c .error (rc ); err != nil {
return err
}
c .busy = cb
return nil
}
func (e *env ) Xgo_busy_handler (pDB , count int32 ) (retry int32 ) {
if c , ok := e .DB .(*Conn ); ok && c .handle == ptr_t (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 int64 , err error ) {
defer c .arena .Mark ()()
hiPtr := c .arena .New (8 )
curPtr := c .arena .New (8 )
var i int32
if reset {
i = 1
}
rc := res_t (c .wrp .Xsqlite3_db_status64 (int32 (c .handle ),
int32 (op ), int32 (curPtr ), int32 (hiPtr ), i ))
if err = c .error (rc ); err == nil {
current = int64 (c .wrp .Read64 (curPtr ))
highwater = int64 (c .wrp .Read64 (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 .wrp .Xsqlite3_table_column_metadata (int32 (c .handle ),
int32 (schemaPtr ), int32 (tablePtr ), int32 (columnPtr ),
int32 (declTypePtr ), int32 (collSeqPtr ),
int32 (notNullPtr ), int32 (primaryKeyPtr ), int32 (autoIncPtr )))
if err = c .error (rc ); err == nil && column != "" {
if ptr := ptr_t (c .wrp .Read32 (declTypePtr )); ptr != 0 {
declType = c .wrp .ReadString (ptr , _MAX_NAME )
}
if ptr := ptr_t (c .wrp .Read32 (collSeqPtr )); ptr != 0 {
collSeq = c .wrp .ReadString (ptr , _MAX_NAME )
}
notNull = c .wrp .ReadBool (notNullPtr )
autoInc = c .wrp .ReadBool (autoIncPtr )
primaryKey = c .wrp .ReadBool (primaryKeyPtr )
}
return
}
func (c *Conn ) error (rc res_t , sql ...string ) error {
return c .errorFor (c .handle , rc , sql ...)
}
func (c *Conn ) errorFor (handle ptr_t , rc res_t , sql ...string ) error {
if rc == _OK {
return nil
}
if ErrorCode (rc ) == NOMEM || xErrorCode (rc ) == IOERR_NOMEM {
panic (errutil .OOMErr )
}
var msg , query string
if handle != 0 {
if ptr := ptr_t (c .wrp .Xsqlite3_errmsg (int32 (handle ))); ptr != 0 {
msg = c .wrp .ReadString (ptr , _MAX_LENGTH )
msg = strings .TrimPrefix (msg , "sqlite3: " )
msg = strings .TrimPrefix (msg , sqlite3_wrap .ErrorCodeString (rc )[len ("sqlite3: " ):])
msg = strings .TrimPrefix (msg , ": " )
if msg == "" || msg == "not an error" {
msg = ""
}
}
if len (sql ) != 0 {
if i := int32 (c .wrp .Xsqlite3_error_offset (int32 (handle ))); i != -1 {
query = sql [0 ][i :]
}
}
}
var sys error
switch ErrorCode (rc ) {
case CANTOPEN , IOERR :
sys = c .wrp .SysError
}
if sys != nil || msg != "" || query != "" {
return &Error {code : rc , sys : sys , msg : msg , sql : query }
}
return xErrorCode (rc )
}
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.4 . (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 .