mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-11 21:49:13 +00:00
More APIs. (#125)
sqlite3_db_cacheflush sqlite3_db_status sqlite3_expanded_sql sqlite3_next_stmt sqlite3_sql sqlite3_table_column_metadata sqlite3_trace_v2 sqlite3_value_frombind
This commit is contained in:
@@ -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).
|
||||||
|
|||||||
50
config.go
50
config.go
@@ -162,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
|
||||||
@@ -176,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)
|
||||||
}
|
}
|
||||||
@@ -188,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
|
||||||
}
|
}
|
||||||
|
|||||||
76
conn.go
76
conn.go
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,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
|
||||||
@@ -414,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
conn_iter.go
Normal file
11
conn_iter.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
//go:build go1.23 || goexperiment.rangefunc
|
||||||
|
|
||||||
|
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
conn_old.go
Normal file
9
conn_old.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
//go:build !(go1.23 || goexperiment.rangefunc)
|
||||||
|
|
||||||
|
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 }
|
||||||
47
const.go
47
const.go
@@ -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
|
||||||
@@ -307,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
|
||||||
@@ -346,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
|
||||||
|
|||||||
@@ -74,11 +74,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}).OpenConnector(dataSourceName)
|
if len(fn) > 2 {
|
||||||
|
return nil, util.ArgErr
|
||||||
|
}
|
||||||
|
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
|
||||||
}
|
}
|
||||||
@@ -88,6 +99,7 @@ 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 func(*sqlite3.Conn) error
|
init func(*sqlite3.Conn) error
|
||||||
|
term func(*sqlite3.Conn) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open implements [database/sql/driver.Driver].
|
// Open implements [database/sql/driver.Driver].
|
||||||
@@ -204,6 +216,14 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Example_json() {
|
func Example_json() {
|
||||||
db, err := driver.Open("file:/test.db?vfs=memdb", nil)
|
db, err := driver.Open("file:/test.db?vfs=memdb")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func ExampleSavepoint() {
|
func ExampleSavepoint() {
|
||||||
db, err := driver.Open("file:/test.db?vfs=memdb", nil)
|
db, err := driver.Open("file:/test.db?vfs=memdb")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,17 +55,20 @@ 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_file_control
|
||||||
sqlite3_filename_database
|
sqlite3_filename_database
|
||||||
sqlite3_filename_journal
|
sqlite3_filename_journal
|
||||||
@@ -101,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.
@@ -37,7 +37,7 @@ func (dialector _Dialector) Initialize(db *gorm.DB) (err error) {
|
|||||||
if dialector.Conn != nil {
|
if dialector.Conn != nil {
|
||||||
db.ConnPool = dialector.Conn
|
db.ConnPool = dialector.Conn
|
||||||
} else {
|
} else {
|
||||||
conn, err := driver.Open(dialector.DSN, nil)
|
conn, err := driver.Open(dialector.DSN)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ func (e ErrorString) Error() string { return string(e) }
|
|||||||
const (
|
const (
|
||||||
NilErr = ErrorString("sqlite3: invalid memory address or null pointer dereference")
|
NilErr = ErrorString("sqlite3: invalid memory address or null pointer dereference")
|
||||||
OOMErr = ErrorString("sqlite3: out of memory")
|
OOMErr = ErrorString("sqlite3: out of memory")
|
||||||
|
ArgErr = ErrorString("sqlite3: invalid argument")
|
||||||
RangeErr = ErrorString("sqlite3: index out of range")
|
RangeErr = ErrorString("sqlite3: index out of range")
|
||||||
NoNulErr = ErrorString("sqlite3: missing NUL terminator")
|
NoNulErr = ErrorString("sqlite3: missing NUL terminator")
|
||||||
NoBinaryErr = ErrorString("sqlite3: no SQLite binary embed/set/loaded")
|
NoBinaryErr = ErrorString("sqlite3: no SQLite binary embed/set/loaded")
|
||||||
|
|||||||
@@ -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),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,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)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ int go_commit_hook(void *);
|
|||||||
void go_rollback_hook(void *);
|
void go_rollback_hook(void *);
|
||||||
void go_update_hook(void *, int, char const *, char const *, sqlite3_int64);
|
void go_update_hook(void *, int, char const *, char const *, sqlite3_int64);
|
||||||
int go_wal_hook(void *, sqlite3 *, const char *, int);
|
int go_wal_hook(void *, sqlite3 *, const char *, int);
|
||||||
|
int go_trace(unsigned, void *, void *, void *);
|
||||||
int go_authorizer(void *, int, const char *, const char *, const char *,
|
int go_authorizer(void *, int, const char *, const char *, const char *,
|
||||||
const char *);
|
const char *);
|
||||||
|
|
||||||
@@ -47,6 +47,10 @@ int sqlite3_set_authorizer_go(sqlite3 *db, bool enable) {
|
|||||||
return sqlite3_set_authorizer(db, enable ? go_authorizer : NULL, /*arg=*/db);
|
return sqlite3_set_authorizer(db, enable ? go_authorizer : NULL, /*arg=*/db);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int sqlite3_trace_go(sqlite3 *db, unsigned mask) {
|
||||||
|
return sqlite3_trace_v2(db, mask, go_trace, /*arg=*/db);
|
||||||
|
}
|
||||||
|
|
||||||
int sqlite3_config_log_go(bool enable) {
|
int sqlite3_config_log_go(bool enable) {
|
||||||
return sqlite3_config(SQLITE_CONFIG_LOG, enable ? go_log : NULL,
|
return sqlite3_config(SQLITE_CONFIG_LOG, enable ? go_log : NULL,
|
||||||
/*arg=*/NULL);
|
/*arg=*/NULL);
|
||||||
|
|||||||
28
stmt.go
28
stmt.go
@@ -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.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -543,6 +543,78 @@ func TestConn_SetAuthorizer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConn_Trace(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
db, err := sqlite3.Open(":memory:")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
rows := 0
|
||||||
|
closed := false
|
||||||
|
err = db.Trace(math.MaxUint32, func(evt sqlite3.TraceEvent, a1 any, a2 any) error {
|
||||||
|
switch evt {
|
||||||
|
case sqlite3.TRACE_CLOSE:
|
||||||
|
closed = true
|
||||||
|
_ = a1.(*sqlite3.Conn)
|
||||||
|
return db.Exec(`PRAGMA optimize`)
|
||||||
|
case sqlite3.TRACE_STMT:
|
||||||
|
stmt := a1.(*sqlite3.Stmt)
|
||||||
|
if sql := a2.(string); sql != stmt.SQL() {
|
||||||
|
t.Errorf("got %q, want %q", sql, stmt.SQL())
|
||||||
|
}
|
||||||
|
if sql := stmt.ExpandedSQL(); sql != `SELECT 1` {
|
||||||
|
t.Errorf("got %q", sql)
|
||||||
|
}
|
||||||
|
case sqlite3.TRACE_PROFILE:
|
||||||
|
_ = a1.(*sqlite3.Stmt)
|
||||||
|
if ns := a2.(int64); ns < 0 {
|
||||||
|
t.Errorf("got %d", ns)
|
||||||
|
}
|
||||||
|
case sqlite3.TRACE_ROW:
|
||||||
|
_ = a1.(*sqlite3.Stmt)
|
||||||
|
if a2 != nil {
|
||||||
|
t.Errorf("got %v", a2)
|
||||||
|
}
|
||||||
|
rows++
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt, _, err := db.Prepare(`SELECT ?`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = stmt.BindInt(1, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = stmt.Exec()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = stmt.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if rows != 1 {
|
||||||
|
t.Error("want 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !closed {
|
||||||
|
t.Error("want closed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestConn_ReleaseMemory(t *testing.T) {
|
func TestConn_ReleaseMemory(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -684,3 +756,96 @@ func TestConn_AutoVacuumPages(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConn_Status(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
db, err := sqlite3.Open(":memory:")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
err = db.Exec(`CREATE TABLE test (col)`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cr, hi, err := db.Status(sqlite3.DBSTATUS_SCHEMA_USED, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("want nil")
|
||||||
|
}
|
||||||
|
if cr == 0 {
|
||||||
|
t.Error("want something")
|
||||||
|
}
|
||||||
|
if hi != 0 {
|
||||||
|
t.Error("want zero")
|
||||||
|
}
|
||||||
|
|
||||||
|
cr, hi, err = db.Status(sqlite3.DBSTATUS_LOOKASIDE_HIT, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("want nil")
|
||||||
|
}
|
||||||
|
if cr != 0 {
|
||||||
|
t.Error("want zero")
|
||||||
|
}
|
||||||
|
if hi == 0 {
|
||||||
|
t.Error("want something")
|
||||||
|
}
|
||||||
|
|
||||||
|
cr, hi, err = db.Status(sqlite3.DBSTATUS_LOOKASIDE_HIT, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("want nil")
|
||||||
|
}
|
||||||
|
if cr != 0 {
|
||||||
|
t.Error("want zero")
|
||||||
|
}
|
||||||
|
if hi != 0 {
|
||||||
|
t.Error("want zero")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConn_TableColumnMetadata(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
db, err := sqlite3.Open(":memory:")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
err = db.Exec(`CREATE TABLE test (col)`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, _, _, _, err = db.TableColumnMetadata("", "table", "")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("want error")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, _, _, _, err = db.TableColumnMetadata("", "test", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Error("want nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
typ, ord, nn, pk, ai, err := db.TableColumnMetadata("", "test", "rowid")
|
||||||
|
if err != nil {
|
||||||
|
t.Error("want nil")
|
||||||
|
}
|
||||||
|
if typ != "INTEGER" {
|
||||||
|
t.Error("want INTEGER")
|
||||||
|
}
|
||||||
|
if ord != "BINARY" {
|
||||||
|
t.Error("want BINARY")
|
||||||
|
}
|
||||||
|
if nn != false {
|
||||||
|
t.Error("want false")
|
||||||
|
}
|
||||||
|
if pk != true {
|
||||||
|
t.Error("want true")
|
||||||
|
}
|
||||||
|
if ai != false {
|
||||||
|
t.Error("want false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ncruces/go-sqlite3"
|
||||||
"github.com/ncruces/go-sqlite3/driver"
|
"github.com/ncruces/go-sqlite3/driver"
|
||||||
_ "github.com/ncruces/go-sqlite3/embed"
|
_ "github.com/ncruces/go-sqlite3/embed"
|
||||||
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
|
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
|
||||||
@@ -15,7 +16,9 @@ func TestDriver(t *testing.T) {
|
|||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
db, err := driver.Open(":memory:", nil)
|
db, err := driver.Open(":memory:", nil, func(c *sqlite3.Conn) error {
|
||||||
|
return c.Exec(`PRAGMA optimize`)
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,9 @@ func TestCreateFunction(t *testing.T) {
|
|||||||
case 10:
|
case 10:
|
||||||
ctx.ResultNull()
|
ctx.ResultNull()
|
||||||
case 11:
|
case 11:
|
||||||
arg.NoChange()
|
if arg.NoChange() || arg.FromBind() {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
ctx.ResultError(sqlite3.FULL)
|
ctx.ResultError(sqlite3.FULL)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func TestJSON(t *testing.T) {
|
|||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
db, err := driver.Open(":memory:", nil)
|
db, err := driver.Open(":memory:")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -503,6 +503,13 @@ func TestStmt(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
db.Stmts()(func(s *sqlite3.Stmt) bool {
|
||||||
|
if s != stmt {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
if err := stmt.Close(); err != nil {
|
if err := stmt.Close(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ func TestTimeFormat_Scanner(t *testing.T) {
|
|||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
db, err := driver.Open(":memory:", nil)
|
db, err := driver.Open(":memory:")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,6 +184,10 @@ func TestConn_Transaction_interrupt(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
err = db.CacheFlush()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
cancel()
|
cancel()
|
||||||
_, err = db.BeginImmediate()
|
_, err = db.BeginImmediate()
|
||||||
|
|||||||
@@ -54,13 +54,13 @@ func TestWAL_readonly(t *testing.T) {
|
|||||||
|
|
||||||
tmp := filepath.ToSlash(filepath.Join(t.TempDir(), "test.db"))
|
tmp := filepath.ToSlash(filepath.Join(t.TempDir(), "test.db"))
|
||||||
|
|
||||||
db1, err := driver.Open("file:"+tmp+"?_pragma=journal_mode(wal)&_txlock=immediate", nil)
|
db1, err := driver.Open("file:" + tmp + "?_pragma=journal_mode(wal)&_txlock=immediate")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer db1.Close()
|
defer db1.Close()
|
||||||
|
|
||||||
db2, err := driver.Open("file:"+tmp+"?_pragma=journal_mode(wal)&mode=ro", nil)
|
db2, err := driver.Open("file:" + tmp + "?_pragma=journal_mode(wal)&mode=ro")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
8
txn.go
8
txn.go
@@ -305,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)
|
||||||
|
}
|
||||||
|
|||||||
8
value.go
8
value.go
@@ -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.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func Test_fileformat(t *testing.T) {
|
|||||||
readervfs.Create("test.db", ioutil.NewSizeReaderAt(strings.NewReader(testDB)))
|
readervfs.Create("test.db", ioutil.NewSizeReaderAt(strings.NewReader(testDB)))
|
||||||
adiantum.Register("radiantum", vfs.Find("reader"), nil)
|
adiantum.Register("radiantum", vfs.Find("reader"), nil)
|
||||||
|
|
||||||
db, err := driver.Open("file:test.db?vfs=radiantum", nil)
|
db, err := driver.Open("file:test.db?vfs=radiantum")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user