update go-sqlite3 to v0.18.0 (#3204)

This commit is contained in:
kim 2024-08-15 00:30:58 +00:00 committed by GitHub
parent 09f24e0446
commit 586639ccf0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 645 additions and 143 deletions

4
go.mod
View file

@ -45,7 +45,7 @@ require (
github.com/miekg/dns v1.1.61 github.com/miekg/dns v1.1.61
github.com/minio/minio-go/v7 v7.0.74 github.com/minio/minio-go/v7 v7.0.74
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
github.com/ncruces/go-sqlite3 v0.17.1 github.com/ncruces/go-sqlite3 v0.18.0
github.com/oklog/ulid v1.3.1 github.com/oklog/ulid v1.3.1
github.com/prometheus/client_golang v1.19.1 github.com/prometheus/client_golang v1.19.1
github.com/spf13/cobra v1.8.1 github.com/spf13/cobra v1.8.1
@ -215,7 +215,7 @@ require (
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
golang.org/x/mod v0.18.0 // indirect golang.org/x/mod v0.18.0 // indirect
golang.org/x/sync v0.8.0 // indirect golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.23.0 // indirect golang.org/x/sys v0.24.0 // indirect
golang.org/x/tools v0.22.0 // indirect golang.org/x/tools v0.22.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect

8
go.sum
View file

@ -438,8 +438,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs= github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs=
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
github.com/ncruces/go-sqlite3 v0.17.1 h1:VxTjDpCn87FaFlKMaAYC1jP7ND0d4UNj+6G4IQDHbgI= github.com/ncruces/go-sqlite3 v0.18.0 h1:aH7WGzOC0CYpUPG1LdFg7JApybiuXgYUE2itzLBwhPM=
github.com/ncruces/go-sqlite3 v0.17.1/go.mod h1:FnCyui8SlDoL0mQZ5dTouNo7s7jXS0kJv9lBt1GlM9w= github.com/ncruces/go-sqlite3 v0.18.0/go.mod h1:eEOyZnW1dGTJ+zDpMuzfYamEUBtdFz5zeYhqLBtHxvM=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M= github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
@ -805,8 +805,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=

View file

@ -93,7 +93,7 @@ This project aims for [high test coverage](https://github.com/ncruces/go-sqlite3
It also benefits greatly from [SQLite's](https://sqlite.org/testing.html) and It also benefits greatly from [SQLite's](https://sqlite.org/testing.html) and
[wazero's](https://tetrate.io/blog/introducing-wazero-from-tetrate/#:~:text=Rock%2Dsolid%20test%20approach) thorough testing. [wazero's](https://tetrate.io/blog/introducing-wazero-from-tetrate/#:~:text=Rock%2Dsolid%20test%20approach) thorough testing.
Every commit is [tested](.github/workflows/test.yml) on Every commit is [tested](https://github.com/ncruces/go-sqlite3/wiki/Test-matrix) on
Linux (amd64/arm64/386/riscv64/s390x), macOS (amd64/arm64), Linux (amd64/arm64/386/riscv64/s390x), macOS (amd64/arm64),
Windows (amd64), FreeBSD (amd64), OpenBSD (amd64), NetBSD (amd64), Windows (amd64), FreeBSD (amd64), OpenBSD (amd64), NetBSD (amd64),
illumos (amd64), and Solaris (amd64). illumos (amd64), and Solaris (amd64).

View file

@ -143,6 +143,7 @@ func (b *Blob) WriteTo(w io.Writer) (n int64, err error) {
return n, err return n, err
} }
if int64(m) != want { if int64(m) != want {
// notest // Write misbehaving
return n, io.ErrShortWrite return n, io.ErrShortWrite
} }

View file

@ -4,6 +4,7 @@ import (
"context" "context"
"github.com/ncruces/go-sqlite3/internal/util" "github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/vfs"
"github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/api"
) )
@ -56,6 +57,99 @@ func logCallback(ctx context.Context, mod api.Module, _, iCode, zMsg uint32) {
} }
} }
// FileControl allows low-level control of database files.
// Only a subset of opcodes are supported.
//
// https://sqlite.org/c3ref/file_control.html
func (c *Conn) FileControl(schema string, op FcntlOpcode, arg ...any) (any, error) {
defer c.arena.mark()()
var schemaPtr uint32
if schema != "" {
schemaPtr = c.arena.string(schema)
}
switch op {
case FCNTL_RESET_CACHE:
r := c.call("sqlite3_file_control",
uint64(c.handle), uint64(schemaPtr),
uint64(op), 0)
return nil, c.error(r)
case FCNTL_PERSIST_WAL, FCNTL_POWERSAFE_OVERWRITE:
var flag int
switch {
case len(arg) == 0:
flag = -1
case arg[0]:
flag = 1
}
ptr := c.arena.new(4)
util.WriteUint32(c.mod, ptr, uint32(flag))
r := c.call("sqlite3_file_control",
uint64(c.handle), uint64(schemaPtr),
uint64(op), uint64(ptr))
return util.ReadUint32(c.mod, ptr) != 0, c.error(r)
case FCNTL_CHUNK_SIZE:
ptr := c.arena.new(4)
util.WriteUint32(c.mod, ptr, uint32(arg[0].(int)))
r := c.call("sqlite3_file_control",
uint64(c.handle), uint64(schemaPtr),
uint64(op), uint64(ptr))
return nil, c.error(r)
case FCNTL_RESERVE_BYTES:
bytes := -1
if len(arg) > 0 {
bytes = arg[0].(int)
}
ptr := c.arena.new(4)
util.WriteUint32(c.mod, ptr, uint32(bytes))
r := c.call("sqlite3_file_control",
uint64(c.handle), uint64(schemaPtr),
uint64(op), uint64(ptr))
return int(util.ReadUint32(c.mod, ptr)), c.error(r)
case FCNTL_DATA_VERSION:
ptr := c.arena.new(4)
r := c.call("sqlite3_file_control",
uint64(c.handle), uint64(schemaPtr),
uint64(op), uint64(ptr))
return util.ReadUint32(c.mod, ptr), c.error(r)
case FCNTL_LOCKSTATE:
ptr := c.arena.new(4)
r := c.call("sqlite3_file_control",
uint64(c.handle), uint64(schemaPtr),
uint64(op), uint64(ptr))
return vfs.LockLevel(util.ReadUint32(c.mod, ptr)), c.error(r)
case FCNTL_VFS_POINTER:
ptr := c.arena.new(4)
r := c.call("sqlite3_file_control",
uint64(c.handle), uint64(schemaPtr),
uint64(op), uint64(ptr))
const zNameOffset = 16
ptr = util.ReadUint32(c.mod, ptr)
ptr = util.ReadUint32(c.mod, ptr+zNameOffset)
name := util.ReadString(c.mod, ptr, _MAX_NAME)
return vfs.Find(name), c.error(r)
case FCNTL_FILE_POINTER, FCNTL_JOURNAL_POINTER:
ptr := c.arena.new(4)
r := c.call("sqlite3_file_control",
uint64(c.handle), uint64(schemaPtr),
uint64(op), uint64(ptr))
const fileHandleOffset = 4
ptr = util.ReadUint32(c.mod, ptr)
ptr = util.ReadUint32(c.mod, ptr+fileHandleOffset)
return util.GetHandle(c.ctx, ptr), c.error(r)
}
return nil, MISUSE
}
// Limit allows the size of various constructs to be // Limit allows the size of various constructs to be
// limited on a connection by connection basis. // limited on a connection by connection basis.
// //
@ -68,7 +162,7 @@ func (c *Conn) Limit(id LimitCategory, value int) int {
// SetAuthorizer registers an authorizer callback with the database connection. // SetAuthorizer registers an authorizer callback with the database connection.
// //
// https://sqlite.org/c3ref/set_authorizer.html // https://sqlite.org/c3ref/set_authorizer.html
func (c *Conn) SetAuthorizer(cb func(action AuthorizerActionCode, name3rd, name4th, schema, nameInner string) AuthorizerReturnCode) error { func (c *Conn) SetAuthorizer(cb func(action AuthorizerActionCode, name3rd, name4th, schema, inner string) AuthorizerReturnCode) error {
var enable uint64 var enable uint64
if cb != nil { if cb != nil {
enable = 1 enable = 1
@ -82,9 +176,9 @@ func (c *Conn) SetAuthorizer(cb func(action AuthorizerActionCode, name3rd, name4
} }
func authorizerCallback(ctx context.Context, mod api.Module, pDB uint32, action AuthorizerActionCode, zName3rd, zName4th, zSchema, zNameInner uint32) (rc AuthorizerReturnCode) { func authorizerCallback(ctx context.Context, mod api.Module, pDB uint32, action AuthorizerActionCode, zName3rd, zName4th, zSchema, zInner uint32) (rc AuthorizerReturnCode) {
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.authorizer != nil { if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.authorizer != nil {
var name3rd, name4th, schema, nameInner string var name3rd, name4th, schema, inner string
if zName3rd != 0 { if zName3rd != 0 {
name3rd = util.ReadString(mod, zName3rd, _MAX_NAME) name3rd = util.ReadString(mod, zName3rd, _MAX_NAME)
} }
@ -94,10 +188,48 @@ func authorizerCallback(ctx context.Context, mod api.Module, pDB uint32, action
if zSchema != 0 { if zSchema != 0 {
schema = util.ReadString(mod, zSchema, _MAX_NAME) schema = util.ReadString(mod, zSchema, _MAX_NAME)
} }
if zNameInner != 0 { if zInner != 0 {
nameInner = util.ReadString(mod, zNameInner, _MAX_NAME) inner = util.ReadString(mod, zInner, _MAX_NAME)
}
rc = c.authorizer(action, name3rd, name4th, schema, inner)
}
return rc
}
// Trace registers a trace callback function against the database connection.
//
// https://sqlite.org/c3ref/trace_v2.html
func (c *Conn) Trace(mask TraceEvent, cb func(evt TraceEvent, arg1 any, arg2 any) error) error {
r := c.call("sqlite3_trace_go", uint64(c.handle), uint64(mask))
if err := c.error(r); err != nil {
return err
}
c.trace = cb
return nil
}
func traceCallback(ctx context.Context, mod api.Module, evt TraceEvent, pDB, pArg1, pArg2 uint32) (rc uint32) {
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.trace != nil {
var arg1, arg2 any
if evt == TRACE_CLOSE {
arg1 = c
} else {
for _, s := range c.stmts {
if pArg1 == s.handle {
arg1 = s
switch evt {
case TRACE_STMT:
arg2 = s.SQL()
case TRACE_PROFILE:
arg2 = int64(util.ReadUint64(mod, pArg2))
}
break
}
}
}
if arg1 != nil {
_, rc = errorCode(c.trace(evt, arg1, arg2), ERROR)
} }
rc = c.authorizer(action, name3rd, name4th, schema, nameInner)
} }
return rc return rc
} }

View file

@ -22,14 +22,16 @@ type Conn struct {
interrupt context.Context interrupt context.Context
pending *Stmt pending *Stmt
stmts []*Stmt
busy func(int) bool busy func(int) bool
log func(xErrorCode, string) log func(xErrorCode, string)
collation func(*Conn, 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 authorizer func(AuthorizerActionCode, string, string, string, string) AuthorizerReturnCode
update func(AuthorizerActionCode, string, string, int64) update func(AuthorizerActionCode, string, string, int64)
commit func() bool commit func() bool
rollback func() rollback func()
wal func(*Conn, string, int) error
arena arena arena arena
handle uint32 handle uint32
@ -202,6 +204,7 @@ func (c *Conn) PrepareFlags(sql string, flags PrepareFlag) (stmt *Stmt, tail str
if stmt.handle == 0 { if stmt.handle == 0 {
return nil, "", nil return nil, "", nil
} }
c.stmts = append(c.stmts, stmt)
return stmt, tail, nil return stmt, tail, nil
} }
@ -227,9 +230,8 @@ func (c *Conn) Filename(schema string) *vfs.Filename {
defer c.arena.mark()() defer c.arena.mark()()
ptr = c.arena.string(schema) ptr = c.arena.string(schema)
} }
r := c.call("sqlite3_db_filename", uint64(c.handle), uint64(ptr)) r := c.call("sqlite3_db_filename", uint64(c.handle), uint64(ptr))
return vfs.OpenFilename(c.ctx, c.mod, uint32(r), vfs.OPEN_MAIN_DB) return vfs.GetFilename(c.ctx, c.mod, uint32(r), vfs.OPEN_MAIN_DB)
} }
// ReadOnly determines if a database is read-only. // ReadOnly determines if a database is read-only.
@ -327,7 +329,12 @@ func (c *Conn) SetInterrupt(ctx context.Context) (old context.Context) {
// A busy SQL statement prevents SQLite from ignoring an interrupt // A busy SQL statement prevents SQLite from ignoring an interrupt
// that comes before any other statements are started. // that comes before any other statements are started.
if c.pending == nil { if c.pending == nil {
c.pending, _, _ = c.Prepare(`WITH RECURSIVE c(x) AS (VALUES(0) UNION ALL SELECT x FROM c) SELECT x FROM c`) 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`)
c.call("sqlite3_prepare_v3", uint64(c.handle), uint64(loopPtr), math.MaxUint64, 0, uint64(stmtPtr), 0)
c.pending = &Stmt{c: c}
c.pending.handle = util.ReadUint32(c.mod, stmtPtr)
} }
old = c.interrupt old = c.interrupt
@ -415,10 +422,74 @@ func busyCallback(ctx context.Context, mod api.Module, pDB uint32, count int32)
return retry return retry
} }
// 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()()
hiPtr := c.arena.new(4)
curPtr := c.arena.new(4)
var i uint64
if reset {
i = 1
}
r := c.call("sqlite3_db_status", uint64(c.handle),
uint64(op), uint64(curPtr), uint64(hiPtr), i)
if err = c.error(r); err == nil {
current = int(util.ReadUint32(c.mod, curPtr))
highwater = int(util.ReadUint32(c.mod, hiPtr))
}
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()()
var schemaPtr, columnPtr uint32
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)
if schema != "" {
schemaPtr = c.arena.string(schema)
}
tablePtr := c.arena.string(table)
if column != "" {
columnPtr = c.arena.string(column)
}
r := c.call("sqlite3_table_column_metadata", uint64(c.handle),
uint64(schemaPtr), uint64(tablePtr), uint64(columnPtr),
uint64(declTypePtr), uint64(collSeqPtr),
uint64(notNullPtr), uint64(primaryKeyPtr), uint64(autoIncPtr))
if err = c.error(r); err == nil && column != "" {
declType = util.ReadString(c.mod, util.ReadUint32(c.mod, declTypePtr), _MAX_NAME)
collSeq = util.ReadString(c.mod, util.ReadUint32(c.mod, collSeqPtr), _MAX_NAME)
notNull = util.ReadUint32(c.mod, notNullPtr) != 0
autoInc = util.ReadUint32(c.mod, autoIncPtr) != 0
primaryKey = util.ReadUint32(c.mod, primaryKeyPtr) != 0
}
return
}
func (c *Conn) error(rc uint64, sql ...string) error { func (c *Conn) error(rc uint64, sql ...string) error {
return c.sqlite.error(rc, c.handle, sql...) return c.sqlite.error(rc, c.handle, sql...)
} }
func (c *Conn) stmtsIter(yield func(*Stmt) bool) {
for _, s := range c.stmts {
if !yield(s) {
break
}
}
}
// DriverConn is implemented by the SQLite [database/sql] driver connection. // DriverConn is implemented by the SQLite [database/sql] driver connection.
// //
// It can be used to access SQLite features like [online backup]. // It can be used to access SQLite features like [online backup].

11
vendor/github.com/ncruces/go-sqlite3/conn_iter.go generated vendored Normal file
View file

@ -0,0 +1,11 @@
//go:build (go1.23 || goexperiment.rangefunc) && !vet
package sqlite3
import "iter"
// Stmts returns an iterator for the prepared statements
// associated with the database connection.
//
// https://sqlite.org/c3ref/next_stmt.html
func (c *Conn) Stmts() iter.Seq[*Stmt] { return c.stmtsIter }

9
vendor/github.com/ncruces/go-sqlite3/conn_old.go generated vendored Normal file
View file

@ -0,0 +1,9 @@
//go:build !(go1.23 || goexperiment.rangefunc) || vet
package sqlite3
// Stmts returns an iterator for the prepared statements
// associated with the database connection.
//
// https://sqlite.org/c3ref/next_stmt.html
func (c *Conn) Stmts() func(func(*Stmt) bool) { return c.stmtsIter }

View file

@ -109,7 +109,7 @@ const (
CANTOPEN_ISDIR ExtendedErrorCode = xErrorCode(CANTOPEN) | (2 << 8) CANTOPEN_ISDIR ExtendedErrorCode = xErrorCode(CANTOPEN) | (2 << 8)
CANTOPEN_FULLPATH ExtendedErrorCode = xErrorCode(CANTOPEN) | (3 << 8) CANTOPEN_FULLPATH ExtendedErrorCode = xErrorCode(CANTOPEN) | (3 << 8)
CANTOPEN_CONVPATH ExtendedErrorCode = xErrorCode(CANTOPEN) | (4 << 8) CANTOPEN_CONVPATH ExtendedErrorCode = xErrorCode(CANTOPEN) | (4 << 8)
CANTOPEN_DIRTYWAL ExtendedErrorCode = xErrorCode(CANTOPEN) | (5 << 8) /* Not Used */ // CANTOPEN_DIRTYWAL ExtendedErrorCode = xErrorCode(CANTOPEN) | (5 << 8) /* Not Used */
CANTOPEN_SYMLINK ExtendedErrorCode = xErrorCode(CANTOPEN) | (6 << 8) CANTOPEN_SYMLINK ExtendedErrorCode = xErrorCode(CANTOPEN) | (6 << 8)
CORRUPT_VTAB ExtendedErrorCode = xErrorCode(CORRUPT) | (1 << 8) CORRUPT_VTAB ExtendedErrorCode = xErrorCode(CORRUPT) | (1 << 8)
CORRUPT_SEQUENCE ExtendedErrorCode = xErrorCode(CORRUPT) | (2 << 8) CORRUPT_SEQUENCE ExtendedErrorCode = xErrorCode(CORRUPT) | (2 << 8)
@ -177,11 +177,11 @@ const (
type FunctionFlag uint32 type FunctionFlag uint32
const ( const (
DETERMINISTIC FunctionFlag = 0x000000800 DETERMINISTIC FunctionFlag = 0x000000800
DIRECTONLY FunctionFlag = 0x000080000 DIRECTONLY FunctionFlag = 0x000080000
SUBTYPE FunctionFlag = 0x000100000 INNOCUOUS FunctionFlag = 0x000200000
INNOCUOUS FunctionFlag = 0x000200000 // SUBTYPE FunctionFlag = 0x000100000
RESULT_SUBTYPE FunctionFlag = 0x001000000 // RESULT_SUBTYPE FunctionFlag = 0x001000000
) )
// StmtStatus name counter values associated with the [Stmt.Status] method. // StmtStatus name counter values associated with the [Stmt.Status] method.
@ -201,6 +201,27 @@ const (
STMTSTATUS_MEMUSED StmtStatus = 99 STMTSTATUS_MEMUSED StmtStatus = 99
) )
// DBStatus are the available "verbs" that can be passed to the [Conn.Status] method.
//
// https://sqlite.org/c3ref/c_dbstatus_options.html
type DBStatus uint32
const (
DBSTATUS_LOOKASIDE_USED DBStatus = 0
DBSTATUS_CACHE_USED DBStatus = 1
DBSTATUS_SCHEMA_USED DBStatus = 2
DBSTATUS_STMT_USED DBStatus = 3
DBSTATUS_LOOKASIDE_HIT DBStatus = 4
DBSTATUS_LOOKASIDE_MISS_SIZE DBStatus = 5
DBSTATUS_LOOKASIDE_MISS_FULL DBStatus = 6
DBSTATUS_CACHE_HIT DBStatus = 7
DBSTATUS_CACHE_MISS DBStatus = 8
DBSTATUS_CACHE_WRITE DBStatus = 9
DBSTATUS_DEFERRED_FKS DBStatus = 10
DBSTATUS_CACHE_USED_SHARED DBStatus = 11
DBSTATUS_CACHE_SPILL DBStatus = 12
)
// DBConfig are the available database connection configuration options. // DBConfig are the available database connection configuration options.
// //
// https://sqlite.org/c3ref/c_dbconfig_defensive.html // https://sqlite.org/c3ref/c_dbconfig_defensive.html
@ -229,6 +250,24 @@ const (
DBCONFIG_REVERSE_SCANORDER DBConfig = 1019 DBCONFIG_REVERSE_SCANORDER DBConfig = 1019
) )
// FcntlOpcode are the available opcodes for [Conn.FileControl].
//
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html
type FcntlOpcode uint32
const (
FCNTL_LOCKSTATE FcntlOpcode = 1
FCNTL_CHUNK_SIZE FcntlOpcode = 6
FCNTL_FILE_POINTER FcntlOpcode = 7
FCNTL_PERSIST_WAL FcntlOpcode = 10
FCNTL_POWERSAFE_OVERWRITE FcntlOpcode = 13
FCNTL_VFS_POINTER FcntlOpcode = 27
FCNTL_JOURNAL_POINTER FcntlOpcode = 28
FCNTL_DATA_VERSION FcntlOpcode = 35
FCNTL_RESERVE_BYTES FcntlOpcode = 38
FCNTL_RESET_CACHE FcntlOpcode = 42
)
// LimitCategory are the available run-time limit categories. // LimitCategory are the available run-time limit categories.
// //
// https://sqlite.org/c3ref/c_limit_attached.html // https://sqlite.org/c3ref/c_limit_attached.html
@ -289,8 +328,8 @@ const (
AUTH_DROP_VTABLE AuthorizerActionCode = 30 /* Table Name Module Name */ AUTH_DROP_VTABLE AuthorizerActionCode = 30 /* Table Name Module Name */
AUTH_FUNCTION AuthorizerActionCode = 31 /* NULL Function Name */ AUTH_FUNCTION AuthorizerActionCode = 31 /* NULL Function Name */
AUTH_SAVEPOINT AuthorizerActionCode = 32 /* Operation Savepoint Name */ AUTH_SAVEPOINT AuthorizerActionCode = 32 /* Operation Savepoint Name */
AUTH_COPY AuthorizerActionCode = 0 /* No longer used */
AUTH_RECURSIVE AuthorizerActionCode = 33 /* NULL NULL */ AUTH_RECURSIVE AuthorizerActionCode = 33 /* NULL NULL */
// AUTH_COPY AuthorizerActionCode = 0 /* No longer used */
) )
// AuthorizerReturnCode are the integer codes // AuthorizerReturnCode are the integer codes
@ -328,6 +367,18 @@ const (
TXN_WRITE TxnState = 2 TXN_WRITE TxnState = 2
) )
// TraceEvent identify classes of events that can be monitored with [Conn.Trace].
//
// https://sqlite.org/c3ref/c_trace.html
type TraceEvent uint32
const (
TRACE_STMT TraceEvent = 0x01
TRACE_PROFILE TraceEvent = 0x02
TRACE_ROW TraceEvent = 0x04
TRACE_CLOSE TraceEvent = 0x08
)
// Datatype is a fundamental datatype of SQLite. // Datatype is a fundamental datatype of SQLite.
// //
// https://sqlite.org/c3ref/c_blob.html // https://sqlite.org/c3ref/c_blob.html

View file

@ -130,7 +130,8 @@ func (ctx Context) ResultNull() {
// //
// https://sqlite.org/c3ref/result_blob.html // https://sqlite.org/c3ref/result_blob.html
func (ctx Context) ResultTime(value time.Time, format TimeFormat) { func (ctx Context) ResultTime(value time.Time, format TimeFormat) {
if format == TimeFormatDefault { switch format {
case TimeFormatDefault, TimeFormatAuto, time.RFC3339Nano:
ctx.resultRFC3339Nano(value) ctx.resultRFC3339Nano(value)
return return
} }
@ -165,7 +166,8 @@ func (ctx Context) resultRFC3339Nano(value time.Time) {
// https://sqlite.org/c3ref/result_blob.html // https://sqlite.org/c3ref/result_blob.html
func (ctx Context) ResultPointer(ptr any) { func (ctx Context) ResultPointer(ptr any) {
valPtr := util.AddHandle(ctx.c.ctx, ptr) valPtr := util.AddHandle(ctx.c.ctx, ptr)
ctx.c.call("sqlite3_result_pointer_go", uint64(valPtr)) ctx.c.call("sqlite3_result_pointer_go",
uint64(ctx.handle), uint64(valPtr))
} }
// ResultJSON sets the result of the function to the JSON encoding of value. // ResultJSON sets the result of the function to the JSON encoding of value.
@ -175,7 +177,7 @@ func (ctx Context) ResultJSON(value any) {
data, err := json.Marshal(value) data, err := json.Marshal(value)
if err != nil { if err != nil {
ctx.ResultError(err) ctx.ResultError(err)
return return // notest
} }
ctx.ResultRawText(data) ctx.ResultRawText(data)
} }

View file

@ -8,21 +8,50 @@
// //
// The data source name for "sqlite3" databases can be a filename or a "file:" [URI]. // The data source name for "sqlite3" databases can be a filename or a "file:" [URI].
// //
// # Default transaction mode
//
// The [TRANSACTION] mode can be specified using "_txlock": // The [TRANSACTION] mode can be specified using "_txlock":
// //
// sql.Open("sqlite3", "file:demo.db?_txlock=immediate") // sql.Open("sqlite3", "file:demo.db?_txlock=immediate")
// //
// Possible values are: "deferred", "immediate", "exclusive". // Possible values are: "deferred" (the default), "immediate", "exclusive".
// A [read-only] transaction is always "deferred", regardless of "_txlock". // Regardless of "_txlock":
// - a [linearizable] transaction is always "exclusive";
// - a [serializable] transaction is always "immediate";
// - a [read-only] transaction is always "deferred".
//
// # Working with time
// //
// The time encoding/decoding format can be specified using "_timefmt": // The time encoding/decoding format can be specified using "_timefmt":
// //
// sql.Open("sqlite3", "file:demo.db?_timefmt=sqlite") // sql.Open("sqlite3", "file:demo.db?_timefmt=sqlite")
// //
// Possible values are: "auto" (the default), "sqlite", "rfc3339"; // Possible values are: "auto" (the default), "sqlite", "rfc3339";
// "auto" encodes as RFC 3339 and decodes any [format] supported by SQLite; // - "auto" encodes as RFC 3339 and decodes any [format] supported by SQLite;
// "sqlite" encodes as SQLite and decodes any [format] supported by SQLite; // - "sqlite" encodes as SQLite and decodes any [format] supported by SQLite;
// "rfc3339" encodes and decodes RFC 3339 only. // - "rfc3339" encodes and decodes RFC 3339 only.
//
// If you encode as RFC 3339 (the default),
// consider using the TIME [collating sequence] to produce a time-ordered sequence.
//
// To scan values in other formats, [sqlite3.TimeFormat.Scanner] may be helpful.
// To bind values in other formats, [sqlite3.TimeFormat.Encode] them before binding.
//
// When using a custom time struct, you'll have to implement
// [database/sql/driver.Valuer] and [database/sql.Scanner].
//
// The Value method should ideally serialise to a time [format] supported by SQLite.
// This ensures SQL date and time functions work as they should,
// and that your schema works with other SQLite tools.
// [sqlite3.TimeFormat.Encode] may help.
//
// The Scan method needs to take into account that the value it receives can be of differing types.
// It can already be a [time.Time], if the driver decoded the value according to "_timefmt" rules.
// Or it can be a: string, int64, float64, []byte, nil,
// depending on the column type and what whoever wrote the value.
// [sqlite3.TimeFormat.Decode] may help.
//
// # Setting PRAGMAs
// //
// [PRAGMA] statements can be specified using "_pragma": // [PRAGMA] statements can be specified using "_pragma":
// //
@ -31,13 +60,17 @@
// If no PRAGMAs are specified, a busy timeout of 1 minute is set. // If no PRAGMAs are specified, a busy timeout of 1 minute is set.
// //
// Order matters: // Order matters:
// busy timeout and locking mode should be the first PRAGMAs set, in that order. // encryption keys, busy timeout and locking mode should be the first PRAGMAs set,
// in that order.
// //
// [URI]: https://sqlite.org/uri.html // [URI]: https://sqlite.org/uri.html
// [PRAGMA]: https://sqlite.org/pragma.html // [PRAGMA]: https://sqlite.org/pragma.html
// [format]: https://sqlite.org/lang_datefunc.html#time_values
// [TRANSACTION]: https://sqlite.org/lang_transaction.html#deferred_immediate_and_exclusive_transactions // [TRANSACTION]: https://sqlite.org/lang_transaction.html#deferred_immediate_and_exclusive_transactions
// [linearizable]: https://pkg.go.dev/database/sql#TxOptions
// [serializable]: https://pkg.go.dev/database/sql#TxOptions
// [read-only]: https://pkg.go.dev/database/sql#TxOptions // [read-only]: https://pkg.go.dev/database/sql#TxOptions
// [format]: https://sqlite.org/lang_datefunc.html#time_values
// [collating sequence]: https://sqlite.org/datatype3.html#collating_sequences
package driver package driver
import ( import (
@ -69,11 +102,22 @@ func init() {
// Open opens the SQLite database specified by dataSourceName as a [database/sql.DB]. // Open opens the SQLite database specified by dataSourceName as a [database/sql.DB].
// //
// The init function is called by the driver on new connections. // Open accepts zero, one, or two callbacks (nil callbacks are ignored).
// The first callback is called when the driver opens a new connection.
// The second callback is called before the driver closes a connection.
// The [sqlite3.Conn] can be used to execute queries, register functions, etc. // The [sqlite3.Conn] can be used to execute queries, register functions, etc.
// Any error returned closes the connection and is returned to [database/sql]. func Open(dataSourceName string, fn ...func(*sqlite3.Conn) error) (*sql.DB, error) {
func Open(dataSourceName string, init func(*sqlite3.Conn) error) (*sql.DB, error) { var drv SQLite
c, err := (&SQLite{Init: init}).OpenConnector(dataSourceName) if len(fn) > 2 {
return nil, sqlite3.MISUSE
}
if len(fn) > 1 {
drv.term = fn[1]
}
if len(fn) > 0 {
drv.init = fn[0]
}
c, err := drv.OpenConnector(dataSourceName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -82,10 +126,8 @@ func Open(dataSourceName string, init func(*sqlite3.Conn) error) (*sql.DB, error
// SQLite implements [database/sql/driver.Driver]. // SQLite implements [database/sql/driver.Driver].
type SQLite struct { type SQLite struct {
// Init function is called by the driver on new connections. init func(*sqlite3.Conn) error
// The [sqlite3.Conn] can be used to execute queries, register functions, etc. term func(*sqlite3.Conn) error
// Any error returned closes the connection and is returned to [database/sql].
Init func(*sqlite3.Conn) error
} }
// Open implements [database/sql/driver.Driver]. // Open implements [database/sql/driver.Driver].
@ -119,10 +161,8 @@ func (d *SQLite) newConnector(name string) (*connector, error) {
} }
switch txlock { switch txlock {
case "": case "", "deferred", "concurrent", "immediate", "exclusive":
c.txBegin = "BEGIN" c.txLock = txlock
case "deferred", "immediate", "exclusive":
c.txBegin = "BEGIN " + txlock
default: default:
return nil, fmt.Errorf("sqlite3: invalid _txlock: %s", txlock) return nil, fmt.Errorf("sqlite3: invalid _txlock: %s", txlock)
} }
@ -147,7 +187,7 @@ func (d *SQLite) newConnector(name string) (*connector, error) {
type connector struct { type connector struct {
driver *SQLite driver *SQLite
name string name string
txBegin string txLock string
tmRead sqlite3.TimeFormat tmRead sqlite3.TimeFormat
tmWrite sqlite3.TimeFormat tmWrite sqlite3.TimeFormat
pragmas bool pragmas bool
@ -159,7 +199,7 @@ func (n *connector) Driver() driver.Driver {
func (n *connector) Connect(ctx context.Context) (_ driver.Conn, err error) { func (n *connector) Connect(ctx context.Context) (_ driver.Conn, err error) {
c := &conn{ c := &conn{
txBegin: n.txBegin, txLock: n.txLock,
tmRead: n.tmRead, tmRead: n.tmRead,
tmWrite: n.tmWrite, tmWrite: n.tmWrite,
} }
@ -178,18 +218,18 @@ func (n *connector) Connect(ctx context.Context) (_ driver.Conn, err error) {
defer c.Conn.SetInterrupt(old) defer c.Conn.SetInterrupt(old)
if !n.pragmas { if !n.pragmas {
err = c.Conn.BusyTimeout(60 * time.Second) err = c.Conn.BusyTimeout(time.Minute)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
if n.driver.Init != nil { if n.driver.init != nil {
err = n.driver.Init(c.Conn) err = n.driver.init(c.Conn)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
if n.pragmas || n.driver.Init != nil { if n.pragmas || n.driver.init != nil {
s, _, err := c.Conn.Prepare(`PRAGMA query_only`) s, _, err := c.Conn.Prepare(`PRAGMA query_only`)
if err != nil { if err != nil {
return nil, err return nil, err
@ -204,17 +244,24 @@ func (n *connector) Connect(ctx context.Context) (_ driver.Conn, err error) {
return nil, err return nil, err
} }
} }
if n.driver.term != nil {
err = c.Conn.Trace(sqlite3.TRACE_CLOSE, func(sqlite3.TraceEvent, any, any) error {
return n.driver.term(c.Conn)
})
if err != nil {
return nil, err
}
}
return c, nil return c, nil
} }
type conn struct { type conn struct {
*sqlite3.Conn *sqlite3.Conn
txBegin string txLock string
txCommit string txReset string
txRollback string tmRead sqlite3.TimeFormat
tmRead sqlite3.TimeFormat tmWrite sqlite3.TimeFormat
tmWrite sqlite3.TimeFormat readOnly byte
readOnly byte
} }
var ( var (
@ -231,31 +278,30 @@ func (c *conn) Raw() *sqlite3.Conn {
// Deprecated: use BeginTx instead. // Deprecated: use BeginTx instead.
func (c *conn) Begin() (driver.Tx, error) { func (c *conn) Begin() (driver.Tx, error) {
// notest
return c.BeginTx(context.Background(), driver.TxOptions{}) return c.BeginTx(context.Background(), driver.TxOptions{})
} }
func (c *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) { func (c *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
txBegin := c.txBegin var txLock string
c.txCommit = `COMMIT`
c.txRollback = `ROLLBACK`
if opts.ReadOnly {
txBegin = `
BEGIN deferred;
PRAGMA query_only=on`
c.txRollback = `
ROLLBACK;
PRAGMA query_only=` + string(c.readOnly)
c.txCommit = c.txRollback
}
switch opts.Isolation { switch opts.Isolation {
default: default:
return nil, util.IsolationErr return nil, util.IsolationErr
case case driver.IsolationLevel(sql.LevelLinearizable):
driver.IsolationLevel(sql.LevelDefault), txLock = "exclusive"
driver.IsolationLevel(sql.LevelSerializable): case driver.IsolationLevel(sql.LevelSerializable):
break txLock = "immediate"
case driver.IsolationLevel(sql.LevelDefault):
if !opts.ReadOnly {
txLock = c.txLock
}
}
c.txReset = ``
txBegin := `BEGIN ` + txLock
if opts.ReadOnly {
txBegin += ` ; PRAGMA query_only=on`
c.txReset = `; PRAGMA query_only=` + string(c.readOnly)
} }
old := c.Conn.SetInterrupt(ctx) old := c.Conn.SetInterrupt(ctx)
@ -269,7 +315,7 @@ func (c *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, e
} }
func (c *conn) Commit() error { func (c *conn) Commit() error {
err := c.Conn.Exec(c.txCommit) err := c.Conn.Exec(`COMMIT` + c.txReset)
if err != nil && !c.Conn.GetAutocommit() { if err != nil && !c.Conn.GetAutocommit() {
c.Rollback() c.Rollback()
} }
@ -277,16 +323,17 @@ func (c *conn) Commit() error {
} }
func (c *conn) Rollback() error { func (c *conn) Rollback() error {
err := c.Conn.Exec(c.txRollback) err := c.Conn.Exec(`ROLLBACK` + c.txReset)
if errors.Is(err, sqlite3.INTERRUPT) { if errors.Is(err, sqlite3.INTERRUPT) {
old := c.Conn.SetInterrupt(context.Background()) old := c.Conn.SetInterrupt(context.Background())
defer c.Conn.SetInterrupt(old) defer c.Conn.SetInterrupt(old)
err = c.Conn.Exec(c.txRollback) err = c.Conn.Exec(`ROLLBACK` + c.txReset)
} }
return err return err
} }
func (c *conn) Prepare(query string) (driver.Stmt, error) { func (c *conn) Prepare(query string) (driver.Stmt, error) {
// notest
return c.PrepareContext(context.Background(), query) return c.PrepareContext(context.Background(), query)
} }
@ -329,6 +376,8 @@ func (c *conn) ExecContext(ctx context.Context, query string, args []driver.Name
} }
func (c *conn) CheckNamedValue(arg *driver.NamedValue) error { func (c *conn) CheckNamedValue(arg *driver.NamedValue) error {
// Fast path: short circuit argument verification.
// Arguments will be rejected by conn.ExecContext.
return nil return nil
} }
@ -363,11 +412,13 @@ func (s *stmt) NumInput() int {
// Deprecated: use ExecContext instead. // Deprecated: use ExecContext instead.
func (s *stmt) Exec(args []driver.Value) (driver.Result, error) { func (s *stmt) Exec(args []driver.Value) (driver.Result, error) {
// notest
return s.ExecContext(context.Background(), namedValues(args)) return s.ExecContext(context.Background(), namedValues(args))
} }
// Deprecated: use QueryContext instead. // Deprecated: use QueryContext instead.
func (s *stmt) Query(args []driver.Value) (driver.Rows, error) { func (s *stmt) Query(args []driver.Value) (driver.Rows, error) {
// notest
return s.QueryContext(context.Background(), namedValues(args)) return s.QueryContext(context.Background(), namedValues(args))
} }
@ -561,7 +612,8 @@ func (r *rows) Next(dest []driver.Value) error {
} }
func (r *rows) decodeTime(i int, v any) (_ time.Time, ok bool) { func (r *rows) decodeTime(i int, v any) (_ time.Time, ok bool) {
if r.tmRead == sqlite3.TimeFormatDefault { switch r.tmRead {
case sqlite3.TimeFormatDefault, time.RFC3339Nano:
// handled by maybeTime // handled by maybeTime
return return
} }

View file

@ -16,12 +16,25 @@ func Savepoint(tx *sql.Tx) sqlite3.Savepoint {
return ctx.Savepoint return ctx.Savepoint
} }
// A saveptCtx is never canceled, has no values, and has no deadline.
type saveptCtx struct{ sqlite3.Savepoint } type saveptCtx struct{ sqlite3.Savepoint }
func (*saveptCtx) Deadline() (deadline time.Time, ok bool) { return } func (*saveptCtx) Deadline() (deadline time.Time, ok bool) {
// notest
return
}
func (*saveptCtx) Done() <-chan struct{} { return nil } func (*saveptCtx) Done() <-chan struct{} {
// notest
return nil
}
func (*saveptCtx) Err() error { return nil } func (*saveptCtx) Err() error {
// notest
return nil
}
func (*saveptCtx) Value(key any) any { return nil } func (*saveptCtx) Value(key any) any {
// notest
return nil
}

View file

@ -1,6 +1,6 @@
# Embeddable Wasm build of SQLite # Embeddable Wasm build of SQLite
This folder includes an embeddable Wasm build of SQLite 3.46.0 for use with This folder includes an embeddable Wasm build of SQLite 3.46.1 for use with
[`github.com/ncruces/go-sqlite3`](https://pkg.go.dev/github.com/ncruces/go-sqlite3). [`github.com/ncruces/go-sqlite3`](https://pkg.go.dev/github.com/ncruces/go-sqlite3).
The following optional features are compiled in: The following optional features are compiled in:
@ -17,10 +17,9 @@ The following optional features are compiled in:
- [regexp](https://github.com/sqlite/sqlite/blob/master/ext/misc/regexp.c) - [regexp](https://github.com/sqlite/sqlite/blob/master/ext/misc/regexp.c)
- [series](https://github.com/sqlite/sqlite/blob/master/ext/misc/series.c) - [series](https://github.com/sqlite/sqlite/blob/master/ext/misc/series.c)
- [uint](https://github.com/sqlite/sqlite/blob/master/ext/misc/uint.c) - [uint](https://github.com/sqlite/sqlite/blob/master/ext/misc/uint.c)
- [uuid](https://github.com/sqlite/sqlite/blob/master/ext/misc/uuid.c)
- [time](../sqlite3/time.c) - [time](../sqlite3/time.c)
See the [configuration options](../sqlite3/sqlite_cfg.h), See the [configuration options](../sqlite3/sqlite_opt.h),
and [patches](../sqlite3) applied. and [patches](../sqlite3) applied.
Built using [`wasi-sdk`](https://github.com/WebAssembly/wasi-sdk), Built using [`wasi-sdk`](https://github.com/WebAssembly/wasi-sdk),
@ -28,3 +27,14 @@ and [`binaryen`](https://github.com/WebAssembly/binaryen).
The build is easily reproducible, and verifiable, using The build is easily reproducible, and verifiable, using
[Artifact Attestations](https://github.com/ncruces/go-sqlite3/attestations). [Artifact Attestations](https://github.com/ncruces/go-sqlite3/attestations).
### Customizing the build
You can use your own custom build of SQLite.
Examples of custom builds of SQLite are:
- [`github.com/ncruces/go-sqlite3/embed/bcw2`](https://github.com/ncruces/go-sqlite3/tree/main/embed/bcw2)
built from a branch supporting [`BEGIN CONCURRENT`](https://sqlite.org/src/doc/begin-concurrent/doc/begin_concurrent.md)
and [Wal2](https://www.sqlite.org/cgi/src/doc/wal2/doc/wal2.md).
- [`github.com/asg017/sqlite-vec-go-bindings/ncruces`](https://github.com/asg017/sqlite-vec-go-bindings)
which includes the [`sqlite-vec`](https://github.com/asg017/sqlite-vec) vector search extension.

View file

@ -4,26 +4,27 @@ set -euo pipefail
cd -P -- "$(dirname -- "$0")" cd -P -- "$(dirname -- "$0")"
ROOT=../ ROOT=../
BINARYEN="$ROOT/tools/binaryen-version_117/bin" BINARYEN="$ROOT/tools/binaryen/bin"
WASI_SDK="$ROOT/tools/wasi-sdk-22.0/bin" WASI_SDK="$ROOT/tools/wasi-sdk/bin"
"$WASI_SDK/clang" --target=wasm32-wasi -std=c23 -flto -g0 -O2 \ trap 'rm -f sqlite3.tmp' EXIT
"$WASI_SDK/clang" --target=wasm32-wasi -std=c23 -g0 -O2 \
-Wall -Wextra -Wno-unused-parameter -Wno-unused-function \ -Wall -Wextra -Wno-unused-parameter -Wno-unused-function \
-o sqlite3.wasm "$ROOT/sqlite3/main.c" \ -o sqlite3.wasm "$ROOT/sqlite3/main.c" \
-I"$ROOT/sqlite3" \ -I"$ROOT/sqlite3" \
-mexec-model=reactor \ -mexec-model=reactor \
-msimd128 -mmutable-globals \ -matomics -msimd128 -mmutable-globals \
-mbulk-memory -mreference-types \ -mbulk-memory -mreference-types \
-mnontrapping-fptoint -msign-ext \ -mnontrapping-fptoint -msign-ext \
-fno-stack-protector -fno-stack-clash-protection \ -fno-stack-protector -fno-stack-clash-protection \
-Wl,--initial-memory=327680 \
-Wl,--stack-first \ -Wl,--stack-first \
-Wl,--import-undefined \ -Wl,--import-undefined \
-Wl,--initial-memory=327680 \
-D_HAVE_SQLITE_CONFIG_H \ -D_HAVE_SQLITE_CONFIG_H \
-DSQLITE_CUSTOM_INCLUDE=sqlite_opt.h \ -DSQLITE_CUSTOM_INCLUDE=sqlite_opt.h \
$(awk '{print "-Wl,--export="$0}' exports.txt) $(awk '{print "-Wl,--export="$0}' exports.txt)
trap 'rm -f sqlite3.tmp' EXIT
"$BINARYEN/wasm-ctor-eval" -g -c _initialize sqlite3.wasm -o sqlite3.tmp "$BINARYEN/wasm-ctor-eval" -g -c _initialize sqlite3.wasm -o sqlite3.tmp
"$BINARYEN/wasm-opt" -g --strip --strip-producers -c -O3 \ "$BINARYEN/wasm-opt" -g --strip --strip-producers -c -O3 \
sqlite3.tmp -o sqlite3.wasm \ sqlite3.tmp -o sqlite3.wasm \

View file

@ -55,17 +55,21 @@ sqlite3_create_function_go
sqlite3_create_module_go sqlite3_create_module_go
sqlite3_create_window_function_go sqlite3_create_window_function_go
sqlite3_database_file_object sqlite3_database_file_object
sqlite3_db_cacheflush
sqlite3_db_config sqlite3_db_config
sqlite3_db_filename sqlite3_db_filename
sqlite3_db_name sqlite3_db_name
sqlite3_db_readonly sqlite3_db_readonly
sqlite3_db_release_memory sqlite3_db_release_memory
sqlite3_db_status
sqlite3_declare_vtab sqlite3_declare_vtab
sqlite3_errcode sqlite3_errcode
sqlite3_errmsg sqlite3_errmsg
sqlite3_error_offset sqlite3_error_offset
sqlite3_errstr sqlite3_errstr
sqlite3_exec sqlite3_exec
sqlite3_expanded_sql
sqlite3_file_control
sqlite3_filename_database sqlite3_filename_database
sqlite3_filename_journal sqlite3_filename_journal
sqlite3_filename_wal sqlite3_filename_wal
@ -100,16 +104,18 @@ sqlite3_step
sqlite3_stmt_busy sqlite3_stmt_busy
sqlite3_stmt_readonly sqlite3_stmt_readonly
sqlite3_stmt_status sqlite3_stmt_status
sqlite3_table_column_metadata
sqlite3_total_changes64 sqlite3_total_changes64
sqlite3_trace_go
sqlite3_txn_state sqlite3_txn_state
sqlite3_update_hook_go sqlite3_update_hook_go
sqlite3_uri_key sqlite3_uri_key
sqlite3_uri_parameter
sqlite3_value_blob sqlite3_value_blob
sqlite3_value_bytes sqlite3_value_bytes
sqlite3_value_double sqlite3_value_double
sqlite3_value_dup sqlite3_value_dup
sqlite3_value_free sqlite3_value_free
sqlite3_value_frombind
sqlite3_value_int64 sqlite3_value_int64
sqlite3_value_nochange sqlite3_value_nochange
sqlite3_value_numeric_type sqlite3_value_numeric_type

Binary file not shown.

View file

@ -3,5 +3,7 @@ golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=

View file

@ -32,7 +32,7 @@ func (s *mmapState) new(ctx context.Context, mod api.Module, size int32) *Mapped
// Allocate page aligned memmory. // Allocate page aligned memmory.
alloc := mod.ExportedFunction("aligned_alloc") alloc := mod.ExportedFunction("aligned_alloc")
stack := [2]uint64{ stack := [...]uint64{
uint64(unix.Getpagesize()), uint64(unix.Getpagesize()),
uint64(size), uint64(size),
} }

View file

@ -13,6 +13,7 @@ import (
"github.com/ncruces/go-sqlite3/vfs" "github.com/ncruces/go-sqlite3/vfs"
"github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/experimental"
) )
// Configure SQLite Wasm. // Configure SQLite Wasm.
@ -44,12 +45,14 @@ var instance struct {
} }
func compileSQLite() { func compileSQLite() {
if RuntimeConfig == nil { ctx := context.Background()
RuntimeConfig = wazero.NewRuntimeConfig() cfg := RuntimeConfig
if cfg == nil {
cfg = wazero.NewRuntimeConfig()
} }
ctx := context.Background() instance.runtime = wazero.NewRuntimeWithConfig(ctx,
instance.runtime = wazero.NewRuntimeWithConfig(ctx, RuntimeConfig) cfg.WithCoreFeatures(api.CoreFeaturesV2|experimental.CoreFeaturesThreads))
env := instance.runtime.NewHostModuleBuilder("env") env := instance.runtime.NewHostModuleBuilder("env")
env = vfs.ExportHostFunctions(env) env = vfs.ExportHostFunctions(env)
@ -82,7 +85,7 @@ type sqlite struct {
id [32]*byte id [32]*byte
mask uint32 mask uint32
} }
stack [8]uint64 stack [9]uint64
freer uint32 freer uint32
} }
@ -303,6 +306,7 @@ func exportCallbacks(env wazero.HostModuleBuilder) wazero.HostModuleBuilder {
util.ExportFuncVI(env, "go_rollback_hook", rollbackCallback) util.ExportFuncVI(env, "go_rollback_hook", rollbackCallback)
util.ExportFuncVIIIIJ(env, "go_update_hook", updateCallback) util.ExportFuncVIIIIJ(env, "go_update_hook", updateCallback)
util.ExportFuncIIIII(env, "go_wal_hook", walCallback) util.ExportFuncIIIII(env, "go_wal_hook", walCallback)
util.ExportFuncIIIII(env, "go_trace", traceCallback)
util.ExportFuncIIIIII(env, "go_autovacuum_pages", autoVacuumCallback) util.ExportFuncIIIIII(env, "go_autovacuum_pages", autoVacuumCallback)
util.ExportFuncIIIIIII(env, "go_authorizer", authorizerCallback) util.ExportFuncIIIIIII(env, "go_authorizer", authorizerCallback)
util.ExportFuncVIII(env, "go_log", logCallback) util.ExportFuncVIII(env, "go_log", logCallback)

View file

@ -15,6 +15,7 @@ import (
type Stmt struct { type Stmt struct {
c *Conn c *Conn
err error err error
sql string
handle uint32 handle uint32
} }
@ -29,6 +30,15 @@ func (s *Stmt) Close() error {
} }
r := s.c.call("sqlite3_finalize", uint64(s.handle)) r := s.c.call("sqlite3_finalize", uint64(s.handle))
for i := range s.c.stmts {
if s == s.c.stmts[i] {
l := len(s.c.stmts) - 1
s.c.stmts[i] = s.c.stmts[l]
s.c.stmts[l] = nil
s.c.stmts = s.c.stmts[:l]
break
}
}
s.handle = 0 s.handle = 0
return s.c.error(r) return s.c.error(r)
@ -41,6 +51,24 @@ func (s *Stmt) Conn() *Conn {
return s.c return s.c
} }
// SQL returns the SQL text used to create the prepared statement.
//
// https://sqlite.org/c3ref/expanded_sql.html
func (s *Stmt) SQL() string {
return s.sql
}
// ExpandedSQL returns the SQL text of the prepared statement
// with bound parameters expanded.
//
// https://sqlite.org/c3ref/expanded_sql.html
func (s *Stmt) ExpandedSQL() string {
r := s.c.call("sqlite3_expanded_sql", uint64(s.handle))
sql := util.ReadString(s.c.mod, uint32(r), _MAX_SQL_LENGTH)
s.c.free(uint32(r))
return sql
}
// ReadOnly returns true if and only if the statement // ReadOnly returns true if and only if the statement
// makes no direct changes to the content of the database file. // makes no direct changes to the content of the database file.
// //
@ -283,7 +311,8 @@ func (s *Stmt) BindNull(param int) error {
// //
// https://sqlite.org/c3ref/bind_blob.html // https://sqlite.org/c3ref/bind_blob.html
func (s *Stmt) BindTime(param int, value time.Time, format TimeFormat) error { func (s *Stmt) BindTime(param int, value time.Time, format TimeFormat) error {
if format == TimeFormatDefault { switch format {
case TimeFormatDefault, TimeFormatAuto, time.RFC3339Nano:
return s.bindRFC3339Nano(param, value) return s.bindRFC3339Nano(param, value)
} }
switch v := format.Encode(value).(type) { switch v := format.Encode(value).(type) {

View file

@ -32,6 +32,19 @@ func (c *Conn) Begin() Txn {
return Txn{c} return Txn{c}
} }
// BeginConcurrent starts a concurrent transaction.
//
// Experimental: requires a custom build of SQLite.
//
// https://sqlite.org/cgi/src/doc/begin-concurrent/doc/begin_concurrent.md
func (c *Conn) BeginConcurrent() (Txn, error) {
err := c.Exec(`BEGIN CONCURRENT`)
if err != nil {
return Txn{}, err
}
return Txn{c}, nil
}
// BeginImmediate starts an immediate transaction. // BeginImmediate starts an immediate transaction.
// //
// https://sqlite.org/lang_transaction.html // https://sqlite.org/lang_transaction.html
@ -217,7 +230,7 @@ func (c *Conn) txnExecInterrupted(sql string) error {
return err return err
} }
// TxnState starts a deferred transaction. // TxnState determines the transaction state of a database.
// //
// https://sqlite.org/c3ref/txn_state.html // https://sqlite.org/c3ref/txn_state.html
func (c *Conn) TxnState(schema string) TxnState { func (c *Conn) TxnState(schema string) TxnState {
@ -292,3 +305,11 @@ func updateCallback(ctx context.Context, mod api.Module, pDB uint32, action Auth
c.update(action, schema, table, int64(rowid)) c.update(action, schema, table, int64(rowid))
} }
} }
// CacheFlush flushes caches to disk mid-transaction.
//
// https://sqlite.org/c3ref/db_cacheflush.html
func (c *Conn) CacheFlush() error {
r := c.call("sqlite3_db_cacheflush", uint64(c.handle))
return c.error(r)
}

View file

@ -201,6 +201,14 @@ func (v Value) NoChange() bool {
return r != 0 return r != 0
} }
// FromBind returns true if value originated from a bound parameter.
//
// https://sqlite.org/c3ref/value_blob.html
func (v Value) FromBind() bool {
r := v.c.call("sqlite3_value_frombind", v.protected())
return r != 0
}
// InFirst returns the first element // InFirst returns the first element
// on the right-hand side of an IN constraint. // on the right-hand side of an IN constraint.
// //

View file

@ -69,6 +69,7 @@ func (vfsOS) Access(name string, flags AccessFlag) (bool, error) {
} }
func (vfsOS) Open(name string, flags OpenFlag) (File, OpenFlag, error) { func (vfsOS) Open(name string, flags OpenFlag) (File, OpenFlag, error) {
// notest // OpenFilename is called instead
return nil, 0, _CANTOPEN return nil, 0, _CANTOPEN
} }

View file

@ -20,8 +20,8 @@ type Filename struct {
stack [2]uint64 stack [2]uint64
} }
// OpenFilename is an internal API users should not call directly. // GetFilename is an internal API users should not call directly.
func OpenFilename(ctx context.Context, mod api.Module, id uint32, flags OpenFlag) *Filename { func GetFilename(ctx context.Context, mod api.Module, id uint32, flags OpenFlag) *Filename {
if id == 0 { if id == 0 {
return nil return nil
} }
@ -66,6 +66,10 @@ func (n *Filename) path(method string) string {
if n == nil || n.zPath == 0 { if n == nil || n.zPath == 0 {
return "" return ""
} }
if n.flags&(OPEN_MAIN_DB|OPEN_MAIN_JOURNAL|OPEN_WAL) == 0 {
return ""
}
n.stack[0] = uint64(n.zPath) n.stack[0] = uint64(n.zPath)
fn := n.mod.ExportedFunction(method) fn := n.mod.ExportedFunction(method)
if err := fn.CallWithStack(n.ctx, n.stack[:]); err != nil { if err := fn.CallWithStack(n.ctx, n.stack[:]); err != nil {

View file

@ -10,7 +10,10 @@
package memdb package memdb
import ( import (
"fmt"
"net/url"
"sync" "sync"
"testing"
"github.com/ncruces/go-sqlite3/vfs" "github.com/ncruces/go-sqlite3/vfs"
) )
@ -39,8 +42,9 @@ func Create(name string, data []byte) {
size: int64(len(data)), size: int64(len(data)),
} }
// Convert data from WAL to rollback journal. // Convert data from WAL/2 to rollback journal.
if len(data) >= 20 && data[18] == 2 && data[19] == 2 { if len(data) >= 20 && (data[18] == 2 && data[19] == 2 ||
data[18] == 3 && data[19] == 3) {
data[18] = 1 data[18] = 1
data[19] = 1 data[19] = 1
} }
@ -66,3 +70,30 @@ func Delete(name string) {
defer memoryMtx.Unlock() defer memoryMtx.Unlock()
delete(memoryDBs, name) delete(memoryDBs, name)
} }
// TestDB creates an empty shared memory database for the test to use.
// The database is automatically deleted when the test and all its subtests complete.
// Each subsequent call to TestDB returns a unique database.
func TestDB(tb testing.TB, params ...url.Values) string {
tb.Helper()
name := fmt.Sprintf("%s_%p", tb.Name(), tb)
tb.Cleanup(func() { Delete(name) })
Create(name, nil)
p := url.Values{"vfs": {"memdb"}}
for _, v := range params {
for k, v := range v {
for _, v := range v {
p.Add(k, v)
}
}
}
return (&url.URL{
Scheme: "file",
OmitHost: true,
Path: "/" + name,
RawQuery: p.Encode(),
}).String()
}

View file

@ -30,6 +30,7 @@ func (memVFS) Open(name string, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, err
vfs.OPEN_TEMP_DB | vfs.OPEN_TEMP_DB |
vfs.OPEN_TEMP_JOURNAL vfs.OPEN_TEMP_JOURNAL
if flags&types == 0 { if flags&types == 0 {
// notest // OPEN_MEMORY
return nil, flags, sqlite3.CANTOPEN return nil, flags, sqlite3.CANTOPEN
} }
@ -82,7 +83,7 @@ type memDB struct {
size int64 size int64
// +checklocks:lockMtx // +checklocks:lockMtx
shared int shared int32
// +checklocks:lockMtx // +checklocks:lockMtx
reserved bool reserved bool
// +checklocks:lockMtx // +checklocks:lockMtx
@ -136,7 +137,7 @@ func (m *memFile) ReadAt(b []byte, off int64) (n int, err error) {
} }
n = copy(b, (*m.data[base])[rest:have]) n = copy(b, (*m.data[base])[rest:have])
if n < len(b) { if n < len(b) {
// Assume reads are page aligned. // notest // assume reads are page aligned
return 0, io.ErrNoProgress return 0, io.ErrNoProgress
} }
return n, nil return n, nil
@ -153,7 +154,7 @@ func (m *memFile) WriteAt(b []byte, off int64) (n int, err error) {
} }
n = copy((*m.data[base])[rest:], b) n = copy((*m.data[base])[rest:], b)
if n < len(b) { if n < len(b) {
// Assume writes are page aligned. // notest // assume writes are page aligned
return n, io.ErrShortWrite return n, io.ErrShortWrite
} }
if size := off + int64(len(b)); size > m.size { if size := off + int64(len(b)); size > m.size {
@ -226,9 +227,6 @@ func (m *memFile) Lock(lock vfs.LockLevel) error {
case vfs.LOCK_EXCLUSIVE: case vfs.LOCK_EXCLUSIVE:
if m.lock < vfs.LOCK_PENDING { if m.lock < vfs.LOCK_PENDING {
if m.pending {
return sqlite3.BUSY
}
m.lock = vfs.LOCK_PENDING m.lock = vfs.LOCK_PENDING
m.pending = true m.pending = true
} }
@ -269,6 +267,7 @@ func (m *memFile) Unlock(lock vfs.LockLevel) error {
} }
func (m *memFile) CheckReservedLock() (bool, error) { func (m *memFile) CheckReservedLock() (bool, error) {
// notest // OPEN_MEMORY
if m.lock >= vfs.LOCK_RESERVED { if m.lock >= vfs.LOCK_RESERVED {
return true, nil return true, nil
} }
@ -278,6 +277,7 @@ func (m *memFile) CheckReservedLock() (bool, error) {
} }
func (m *memFile) SectorSize() int { func (m *memFile) SectorSize() int {
// notest // IOCAP_POWERSAFE_OVERWRITE
return sectorSize return sectorSize
} }

View file

@ -5,6 +5,7 @@ package vfs
import ( import (
"io" "io"
"os" "os"
"runtime"
"time" "time"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
@ -68,7 +69,7 @@ func osUnlock(file *os.File, start, len int64) _ErrorCode {
} }
func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, def _ErrorCode) _ErrorCode { func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, def _ErrorCode) _ErrorCode {
lock := flocktimeout_t{fl: unix.Flock_t{ lock := &flocktimeout_t{fl: unix.Flock_t{
Type: typ, Type: typ,
Start: start, Start: start,
Len: len, Len: len,
@ -82,6 +83,7 @@ func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, d
default: default:
lock.timeout = unix.NsecToTimespec(int64(timeout / time.Nanosecond)) lock.timeout = unix.NsecToTimespec(int64(timeout / time.Nanosecond))
err = unix.FcntlFlock(file.Fd(), _F_OFD_SETLKWTIMEOUT, &lock.fl) err = unix.FcntlFlock(file.Fd(), _F_OFD_SETLKWTIMEOUT, &lock.fl)
runtime.KeepAlive(lock)
} }
return osLockErrorCode(err, def) return osLockErrorCode(err, def)
} }

View file

@ -16,6 +16,8 @@ const (
_F2FS_FEATURE_ATOMIC_WRITE = 4 _F2FS_FEATURE_ATOMIC_WRITE = 4
) )
// notest
func osBatchAtomic(file *os.File) bool { func osBatchAtomic(file *os.File) bool {
flags, err := unix.IoctlGetInt(int(file.Fd()), _F2FS_IOC_GET_FEATURES) flags, err := unix.IoctlGetInt(int(file.Fd()), _F2FS_IOC_GET_FEATURES)
return err == nil && flags&_F2FS_FEATURE_ATOMIC_WRITE != 0 return err == nil && flags&_F2FS_FEATURE_ATOMIC_WRITE != 0

View file

@ -50,6 +50,7 @@ func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode {
// indicates that the other process is not following the locking // indicates that the other process is not following the locking
// protocol. If this happens, return IOERR_RDLOCK. Returning // protocol. If this happens, return IOERR_RDLOCK. Returning
// BUSY would confuse the upper layer. // BUSY would confuse the upper layer.
// notest
return _IOERR_RDLOCK return _IOERR_RDLOCK
} }
} }
@ -98,6 +99,7 @@ func osLockErrorCode(err error, def _ErrorCode) _ErrorCode {
case unix.EPERM: case unix.EPERM:
return _PERM return _PERM
} }
// notest // usually EWOULDBLOCK == EAGAIN
if errno == unix.EWOULDBLOCK && unix.EWOULDBLOCK != unix.EAGAIN { if errno == unix.EWOULDBLOCK && unix.EWOULDBLOCK != unix.EAGAIN {
return _BUSY return _BUSY
} }

View file

@ -66,6 +66,7 @@ func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode {
if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK { if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK {
// This should never happen. // This should never happen.
// We should always be able to reacquire the read lock. // We should always be able to reacquire the read lock.
// notest
return _IOERR_RDLOCK return _IOERR_RDLOCK
} }
} }

View file

@ -72,28 +72,28 @@ func (s *vfsShm) Close() error {
return nil return nil
} }
// Unlock everything.
s.shmLock(0, _SHM_NLOCK, _SHM_UNLOCK)
vfsShmFilesMtx.Lock() vfsShmFilesMtx.Lock()
defer vfsShmFilesMtx.Unlock() defer vfsShmFilesMtx.Unlock()
// Unlock everything.
s.shmLock(0, _SHM_NLOCK, _SHM_UNLOCK)
// Decrease reference count. // Decrease reference count.
if s.vfsShmFile.refs > 1 { if s.vfsShmFile.refs > 1 {
s.vfsShmFile.refs-- s.vfsShmFile.refs--
s.vfsShmFile = nil s.vfsShmFile = nil
return nil return nil
} }
err := s.File.Close()
for i, g := range vfsShmFiles { for i, g := range vfsShmFiles {
if g == s.vfsShmFile { if g == s.vfsShmFile {
vfsShmFiles[i] = nil vfsShmFiles[i] = nil
break s.vfsShmFile = nil
return err
} }
} }
panic(util.AssertErr())
err := s.File.Close()
s.vfsShmFile = nil
return err
} }
func (s *vfsShm) shmOpen() (rc _ErrorCode) { func (s *vfsShm) shmOpen() (rc _ErrorCode) {
@ -234,6 +234,8 @@ func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode {
s.vfsShmFile.lock[i] = -1 s.vfsShmFile.lock[i] = -1
s.lock[i] = true s.lock[i] = true
} }
default:
panic(util.AssertErr())
} }
return _OK return _OK
@ -256,5 +258,4 @@ func (s *vfsShm) shmUnmap(delete bool) {
os.Remove(s.path) os.Remove(s.path)
} }
s.Close() s.Close()
s.vfsShmFile = nil
} }

View file

@ -132,26 +132,20 @@ func vfsAccess(ctx context.Context, mod api.Module, pVfs, zPath uint32, flags Ac
func vfsOpen(ctx context.Context, mod api.Module, pVfs, zPath, pFile uint32, flags OpenFlag, pOutFlags, pOutVFS uint32) _ErrorCode { func vfsOpen(ctx context.Context, mod api.Module, pVfs, zPath, pFile uint32, flags OpenFlag, pOutFlags, pOutVFS uint32) _ErrorCode {
vfs := vfsGet(mod, pVfs) vfs := vfsGet(mod, pVfs)
name := GetFilename(ctx, mod, zPath, flags)
var path string
if zPath != 0 {
path = util.ReadString(mod, zPath, _MAX_PATHNAME)
}
var file File var file File
var err error var err error
if ffs, ok := vfs.(VFSFilename); ok { if ffs, ok := vfs.(VFSFilename); ok {
name := OpenFilename(ctx, mod, zPath, flags)
file, flags, err = ffs.OpenFilename(name, flags) file, flags, err = ffs.OpenFilename(name, flags)
} else { } else {
file, flags, err = vfs.Open(path, flags) file, flags, err = vfs.Open(name.String(), flags)
} }
if err != nil { if err != nil {
return vfsErrorCode(err, _CANTOPEN) return vfsErrorCode(err, _CANTOPEN)
} }
if file, ok := file.(FilePowersafeOverwrite); ok { if file, ok := file.(FilePowersafeOverwrite); ok {
name := OpenFilename(ctx, mod, zPath, flags)
if b, ok := util.ParseBool(name.URIParameter("psow")); ok { if b, ok := util.ParseBool(name.URIParameter("psow")); ok {
file.SetPowersafeOverwrite(b) file.SetPowersafeOverwrite(b)
} }
@ -169,10 +163,7 @@ func vfsOpen(ctx context.Context, mod api.Module, pVfs, zPath, pFile uint32, fla
func vfsClose(ctx context.Context, mod api.Module, pFile uint32) _ErrorCode { func vfsClose(ctx context.Context, mod api.Module, pFile uint32) _ErrorCode {
err := vfsFileClose(ctx, mod, pFile) err := vfsFileClose(ctx, mod, pFile)
if err != nil { return vfsErrorCode(err, _IOERR_CLOSE)
return vfsErrorCode(err, _IOERR_CLOSE)
}
return _OK
} }
func vfsRead(ctx context.Context, mod api.Module, pFile, zBuf uint32, iAmt int32, iOfst int64) _ErrorCode { func vfsRead(ctx context.Context, mod api.Module, pFile, zBuf uint32, iAmt int32, iOfst int64) _ErrorCode {
@ -195,10 +186,7 @@ func vfsWrite(ctx context.Context, mod api.Module, pFile, zBuf uint32, iAmt int3
buf := util.View(mod, zBuf, uint64(iAmt)) buf := util.View(mod, zBuf, uint64(iAmt))
_, err := file.WriteAt(buf, iOfst) _, err := file.WriteAt(buf, iOfst)
if err != nil { return vfsErrorCode(err, _IOERR_WRITE)
return vfsErrorCode(err, _IOERR_WRITE)
}
return _OK
} }
func vfsTruncate(ctx context.Context, mod api.Module, pFile uint32, nByte int64) _ErrorCode { func vfsTruncate(ctx context.Context, mod api.Module, pFile uint32, nByte int64) _ErrorCode {

View file

@ -247,7 +247,7 @@ type VTabCursor interface {
// https://sqlite.org/vtab.html#xeof // https://sqlite.org/vtab.html#xeof
EOF() bool EOF() bool
// https://sqlite.org/vtab.html#xcolumn // https://sqlite.org/vtab.html#xcolumn
Column(ctx *Context, n int) error Column(ctx Context, n int) error
// https://sqlite.org/vtab.html#xrowid // https://sqlite.org/vtab.html#xrowid
RowID() (int64, error) RowID() (int64, error)
} }
@ -618,7 +618,7 @@ func cursorNextCallback(ctx context.Context, mod api.Module, pCur uint32) uint32
func cursorColumnCallback(ctx context.Context, mod api.Module, pCur, pCtx uint32, n int32) uint32 { func cursorColumnCallback(ctx context.Context, mod api.Module, pCur, pCtx uint32, n int32) uint32 {
cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor) cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor)
db := ctx.Value(connKey{}).(*Conn) db := ctx.Value(connKey{}).(*Conn)
err := cursor.Column(&Context{db, pCtx}, int(n)) err := cursor.Column(Context{db, pCtx}, int(n))
return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err) return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err)
} }

View file

@ -3807,6 +3807,9 @@ const (
ETHTOOL_MSG_PSE_GET_REPLY = 0x25 ETHTOOL_MSG_PSE_GET_REPLY = 0x25
ETHTOOL_MSG_RSS_GET_REPLY = 0x26 ETHTOOL_MSG_RSS_GET_REPLY = 0x26
ETHTOOL_MSG_KERNEL_MAX = 0x2b ETHTOOL_MSG_KERNEL_MAX = 0x2b
ETHTOOL_FLAG_COMPACT_BITSETS = 0x1
ETHTOOL_FLAG_OMIT_REPLY = 0x2
ETHTOOL_FLAG_STATS = 0x4
ETHTOOL_A_HEADER_UNSPEC = 0x0 ETHTOOL_A_HEADER_UNSPEC = 0x0
ETHTOOL_A_HEADER_DEV_INDEX = 0x1 ETHTOOL_A_HEADER_DEV_INDEX = 0x1
ETHTOOL_A_HEADER_DEV_NAME = 0x2 ETHTOOL_A_HEADER_DEV_NAME = 0x2

View file

@ -2031,6 +2031,50 @@ const (
IF_TYPE_IEEE1394 = 144 IF_TYPE_IEEE1394 = 144
) )
// Enum NL_PREFIX_ORIGIN for [IpAdapterUnicastAddress], see
// https://learn.microsoft.com/en-us/windows/win32/api/nldef/ne-nldef-nl_prefix_origin
const (
IpPrefixOriginOther = 0
IpPrefixOriginManual = 1
IpPrefixOriginWellKnown = 2
IpPrefixOriginDhcp = 3
IpPrefixOriginRouterAdvertisement = 4
IpPrefixOriginUnchanged = 1 << 4
)
// Enum NL_SUFFIX_ORIGIN for [IpAdapterUnicastAddress], see
// https://learn.microsoft.com/en-us/windows/win32/api/nldef/ne-nldef-nl_suffix_origin
const (
NlsoOther = 0
NlsoManual = 1
NlsoWellKnown = 2
NlsoDhcp = 3
NlsoLinkLayerAddress = 4
NlsoRandom = 5
IpSuffixOriginOther = 0
IpSuffixOriginManual = 1
IpSuffixOriginWellKnown = 2
IpSuffixOriginDhcp = 3
IpSuffixOriginLinkLayerAddress = 4
IpSuffixOriginRandom = 5
IpSuffixOriginUnchanged = 1 << 4
)
// Enum NL_DAD_STATE for [IpAdapterUnicastAddress], see
// https://learn.microsoft.com/en-us/windows/win32/api/nldef/ne-nldef-nl_dad_state
const (
NldsInvalid = 0
NldsTentative = 1
NldsDuplicate = 2
NldsDeprecated = 3
NldsPreferred = 4
IpDadStateInvalid = 0
IpDadStateTentative = 1
IpDadStateDuplicate = 2
IpDadStateDeprecated = 3
IpDadStatePreferred = 4
)
type SocketAddress struct { type SocketAddress struct {
Sockaddr *syscall.RawSockaddrAny Sockaddr *syscall.RawSockaddrAny
SockaddrLength int32 SockaddrLength int32

4
vendor/modules.txt vendored
View file

@ -516,7 +516,7 @@ github.com/modern-go/concurrent
# github.com/modern-go/reflect2 v1.0.2 # github.com/modern-go/reflect2 v1.0.2
## explicit; go 1.12 ## explicit; go 1.12
github.com/modern-go/reflect2 github.com/modern-go/reflect2
# github.com/ncruces/go-sqlite3 v0.17.1 # github.com/ncruces/go-sqlite3 v0.18.0
## explicit; go 1.21 ## explicit; go 1.21
github.com/ncruces/go-sqlite3 github.com/ncruces/go-sqlite3
github.com/ncruces/go-sqlite3/driver github.com/ncruces/go-sqlite3/driver
@ -1124,7 +1124,7 @@ golang.org/x/oauth2/internal
## explicit; go 1.18 ## explicit; go 1.18
golang.org/x/sync/errgroup golang.org/x/sync/errgroup
golang.org/x/sync/semaphore golang.org/x/sync/semaphore
# golang.org/x/sys v0.23.0 # golang.org/x/sys v0.24.0
## explicit; go 1.18 ## explicit; go 1.18
golang.org/x/sys/cpu golang.org/x/sys/cpu
golang.org/x/sys/unix golang.org/x/sys/unix