Skip to content

Commit 3766376

Browse files
committed
More APIs.
1 parent 2213262 commit 3766376

28 files changed

+453
-34
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ This project aims for [high test coverage](https://github.com/ncruces/go-sqlite3
9393
It also benefits greatly from [SQLite's](https://sqlite.org/testing.html) and
9494
[wazero's](https://tetrate.io/blog/introducing-wazero-from-tetrate/#:~:text=Rock%2Dsolid%20test%20approach) thorough testing.
9595

96-
Every commit is [tested](.github/workflows/test.yml) on
96+
Every commit is [tested](https://github.com/ncruces/go-sqlite3/wiki/Test-matrix) on
9797
Linux (amd64/arm64/386/riscv64/s390x), macOS (amd64/arm64),
9898
Windows (amd64), FreeBSD (amd64), OpenBSD (amd64), NetBSD (amd64),
9999
illumos (amd64), and Solaris (amd64).

config.go

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ func (c *Conn) Limit(id LimitCategory, value int) int {
162162
// SetAuthorizer registers an authorizer callback with the database connection.
163163
//
164164
// https://sqlite.org/c3ref/set_authorizer.html
165-
func (c *Conn) SetAuthorizer(cb func(action AuthorizerActionCode, name3rd, name4th, schema, nameInner string) AuthorizerReturnCode) error {
165+
func (c *Conn) SetAuthorizer(cb func(action AuthorizerActionCode, name3rd, name4th, schema, inner string) AuthorizerReturnCode) error {
166166
var enable uint64
167167
if cb != nil {
168168
enable = 1
@@ -176,9 +176,9 @@ func (c *Conn) SetAuthorizer(cb func(action AuthorizerActionCode, name3rd, name4
176176

177177
}
178178

179-
func authorizerCallback(ctx context.Context, mod api.Module, pDB uint32, action AuthorizerActionCode, zName3rd, zName4th, zSchema, zNameInner uint32) (rc AuthorizerReturnCode) {
179+
func authorizerCallback(ctx context.Context, mod api.Module, pDB uint32, action AuthorizerActionCode, zName3rd, zName4th, zSchema, zInner uint32) (rc AuthorizerReturnCode) {
180180
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.authorizer != nil {
181-
var name3rd, name4th, schema, nameInner string
181+
var name3rd, name4th, schema, inner string
182182
if zName3rd != 0 {
183183
name3rd = util.ReadString(mod, zName3rd, _MAX_NAME)
184184
}
@@ -188,10 +188,48 @@ func authorizerCallback(ctx context.Context, mod api.Module, pDB uint32, action
188188
if zSchema != 0 {
189189
schema = util.ReadString(mod, zSchema, _MAX_NAME)
190190
}
191-
if zNameInner != 0 {
192-
nameInner = util.ReadString(mod, zNameInner, _MAX_NAME)
191+
if zInner != 0 {
192+
inner = util.ReadString(mod, zInner, _MAX_NAME)
193+
}
194+
rc = c.authorizer(action, name3rd, name4th, schema, inner)
195+
}
196+
return rc
197+
}
198+
199+
// Trace registers a trace callback function against the database connection.
200+
//
201+
// https://sqlite.org/c3ref/trace_v2.html
202+
func (c *Conn) Trace(mask TraceEvent, cb func(evt TraceEvent, arg1 any, arg2 any) error) error {
203+
r := c.call("sqlite3_trace_go", uint64(c.handle), uint64(mask))
204+
if err := c.error(r); err != nil {
205+
return err
206+
}
207+
c.trace = cb
208+
return nil
209+
}
210+
211+
func traceCallback(ctx context.Context, mod api.Module, evt TraceEvent, pDB, pArg1, pArg2 uint32) (rc uint32) {
212+
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.trace != nil {
213+
var arg1, arg2 any
214+
if evt == TRACE_CLOSE {
215+
arg1 = c
216+
} else {
217+
for _, s := range c.stmts {
218+
if pArg1 == s.handle {
219+
arg1 = s
220+
switch evt {
221+
case TRACE_STMT:
222+
arg2 = s.SQL()
223+
case TRACE_PROFILE:
224+
arg2 = int64(util.ReadUint64(mod, pArg2))
225+
}
226+
break
227+
}
228+
}
229+
}
230+
if arg1 != nil {
231+
_, rc = errorCode(c.trace(evt, arg1, arg2), ERROR)
193232
}
194-
rc = c.authorizer(action, name3rd, name4th, schema, nameInner)
195233
}
196234
return rc
197235
}

conn.go

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,16 @@ type Conn struct {
2222

2323
interrupt context.Context
2424
pending *Stmt
25+
stmts []*Stmt
2526
busy func(int) bool
2627
log func(xErrorCode, string)
2728
collation func(*Conn, string)
29+
wal func(*Conn, string, int) error
30+
trace func(TraceEvent, any, any) error
2831
authorizer func(AuthorizerActionCode, string, string, string, string) AuthorizerReturnCode
2932
update func(AuthorizerActionCode, string, string, int64)
3033
commit func() bool
3134
rollback func()
32-
wal func(*Conn, string, int) error
3335
arena arena
3436

3537
handle uint32
@@ -202,6 +204,7 @@ func (c *Conn) PrepareFlags(sql string, flags PrepareFlag) (stmt *Stmt, tail str
202204
if stmt.handle == 0 {
203205
return nil, "", nil
204206
}
207+
c.stmts = append(c.stmts, stmt)
205208
return stmt, tail, nil
206209
}
207210

@@ -326,7 +329,12 @@ func (c *Conn) SetInterrupt(ctx context.Context) (old context.Context) {
326329
// A busy SQL statement prevents SQLite from ignoring an interrupt
327330
// that comes before any other statements are started.
328331
if c.pending == nil {
329-
c.pending, _, _ = c.Prepare(`WITH RECURSIVE c(x) AS (VALUES(0) UNION ALL SELECT x FROM c) SELECT x FROM c`)
332+
defer c.arena.mark()()
333+
stmtPtr := c.arena.new(ptrlen)
334+
loopPtr := c.arena.string(`WITH RECURSIVE c(x) AS (VALUES(0) UNION ALL SELECT x FROM c) SELECT x FROM c`)
335+
c.call("sqlite3_prepare_v3", uint64(c.handle), uint64(loopPtr), math.MaxUint64, 0, uint64(stmtPtr), 0)
336+
c.pending = &Stmt{c: c}
337+
c.pending.handle = util.ReadUint32(c.mod, stmtPtr)
330338
}
331339

332340
old = c.interrupt
@@ -414,10 +422,74 @@ func busyCallback(ctx context.Context, mod api.Module, pDB uint32, count int32)
414422
return retry
415423
}
416424

425+
// Status retrieves runtime status information about a database connection.
426+
//
427+
// https://sqlite.org/c3ref/db_status.html
428+
func (c *Conn) Status(op DBStatus, reset bool) (current, highwater int, err error) {
429+
defer c.arena.mark()()
430+
hiPtr := c.arena.new(4)
431+
curPtr := c.arena.new(4)
432+
433+
var i uint64
434+
if reset {
435+
i = 1
436+
}
437+
438+
r := c.call("sqlite3_db_status", uint64(c.handle),
439+
uint64(op), uint64(curPtr), uint64(hiPtr), i)
440+
if err = c.error(r); err == nil {
441+
current = int(util.ReadUint32(c.mod, curPtr))
442+
highwater = int(util.ReadUint32(c.mod, hiPtr))
443+
}
444+
return
445+
}
446+
447+
// TableColumnMetadata extracts metadata about a column of a table.
448+
//
449+
// https://sqlite.org/c3ref/table_column_metadata.html
450+
func (c *Conn) TableColumnMetadata(schema, table, column string) (declType, collSeq string, notNull, primaryKey, autoInc bool, err error) {
451+
defer c.arena.mark()()
452+
453+
var schemaPtr, columnPtr uint32
454+
declTypePtr := c.arena.new(ptrlen)
455+
collSeqPtr := c.arena.new(ptrlen)
456+
notNullPtr := c.arena.new(ptrlen)
457+
primaryKeyPtr := c.arena.new(ptrlen)
458+
autoIncPtr := c.arena.new(ptrlen)
459+
if schema != "" {
460+
schemaPtr = c.arena.string(schema)
461+
}
462+
tablePtr := c.arena.string(table)
463+
if column != "" {
464+
columnPtr = c.arena.string(column)
465+
}
466+
467+
r := c.call("sqlite3_table_column_metadata", uint64(c.handle),
468+
uint64(schemaPtr), uint64(tablePtr), uint64(columnPtr),
469+
uint64(declTypePtr), uint64(collSeqPtr),
470+
uint64(notNullPtr), uint64(primaryKeyPtr), uint64(autoIncPtr))
471+
if err = c.error(r); err == nil && column != "" {
472+
declType = util.ReadString(c.mod, util.ReadUint32(c.mod, declTypePtr), _MAX_NAME)
473+
collSeq = util.ReadString(c.mod, util.ReadUint32(c.mod, collSeqPtr), _MAX_NAME)
474+
notNull = util.ReadUint32(c.mod, notNullPtr) != 0
475+
autoInc = util.ReadUint32(c.mod, autoIncPtr) != 0
476+
primaryKey = util.ReadUint32(c.mod, primaryKeyPtr) != 0
477+
}
478+
return
479+
}
480+
417481
func (c *Conn) error(rc uint64, sql ...string) error {
418482
return c.sqlite.error(rc, c.handle, sql...)
419483
}
420484

485+
func (c *Conn) stmtsIter(yield func(*Stmt) bool) {
486+
for _, s := range c.stmts {
487+
if !yield(s) {
488+
break
489+
}
490+
}
491+
}
492+
421493
// DriverConn is implemented by the SQLite [database/sql] driver connection.
422494
//
423495
// It can be used to access SQLite features like [online backup].

conn_iter.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//go:build go1.23 || goexperiment.rangefunc
2+
3+
package sqlite3
4+
5+
import "iter"
6+
7+
// Stmts returns an iterator for the prepared statements
8+
// associated with the database connection.
9+
//
10+
// https://sqlite.org/c3ref/next_stmt.html
11+
func (c *Conn) Stmts() iter.Seq[*Stmt] { return c.stmtsIter }

conn_old.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//go:build !(go1.23 || goexperiment.rangefunc)
2+
3+
package sqlite3
4+
5+
// Stmts returns an iterator for the prepared statements
6+
// associated with the database connection.
7+
//
8+
// https://sqlite.org/c3ref/next_stmt.html
9+
func (c *Conn) Stmts() func(func(*Stmt) bool) { return c.stmtsIter }

const.go

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ const (
109109
CANTOPEN_ISDIR ExtendedErrorCode = xErrorCode(CANTOPEN) | (2 << 8)
110110
CANTOPEN_FULLPATH ExtendedErrorCode = xErrorCode(CANTOPEN) | (3 << 8)
111111
CANTOPEN_CONVPATH ExtendedErrorCode = xErrorCode(CANTOPEN) | (4 << 8)
112-
CANTOPEN_DIRTYWAL ExtendedErrorCode = xErrorCode(CANTOPEN) | (5 << 8) /* Not Used */
112+
// CANTOPEN_DIRTYWAL ExtendedErrorCode = xErrorCode(CANTOPEN) | (5 << 8) /* Not Used */
113113
CANTOPEN_SYMLINK ExtendedErrorCode = xErrorCode(CANTOPEN) | (6 << 8)
114114
CORRUPT_VTAB ExtendedErrorCode = xErrorCode(CORRUPT) | (1 << 8)
115115
CORRUPT_SEQUENCE ExtendedErrorCode = xErrorCode(CORRUPT) | (2 << 8)
@@ -177,11 +177,11 @@ const (
177177
type FunctionFlag uint32
178178

179179
const (
180-
DETERMINISTIC FunctionFlag = 0x000000800
181-
DIRECTONLY FunctionFlag = 0x000080000
182-
SUBTYPE FunctionFlag = 0x000100000
183-
INNOCUOUS FunctionFlag = 0x000200000
184-
RESULT_SUBTYPE FunctionFlag = 0x001000000
180+
DETERMINISTIC FunctionFlag = 0x000000800
181+
DIRECTONLY FunctionFlag = 0x000080000
182+
INNOCUOUS FunctionFlag = 0x000200000
183+
// SUBTYPE FunctionFlag = 0x000100000
184+
// RESULT_SUBTYPE FunctionFlag = 0x001000000
185185
)
186186

187187
// StmtStatus name counter values associated with the [Stmt.Status] method.
@@ -201,6 +201,27 @@ const (
201201
STMTSTATUS_MEMUSED StmtStatus = 99
202202
)
203203

204+
// DBStatus are the available "verbs" that can be passed to the [Conn.Status] method.
205+
//
206+
// https://sqlite.org/c3ref/c_dbstatus_options.html
207+
type DBStatus uint32
208+
209+
const (
210+
DBSTATUS_LOOKASIDE_USED DBStatus = 0
211+
DBSTATUS_CACHE_USED DBStatus = 1
212+
DBSTATUS_SCHEMA_USED DBStatus = 2
213+
DBSTATUS_STMT_USED DBStatus = 3
214+
DBSTATUS_LOOKASIDE_HIT DBStatus = 4
215+
DBSTATUS_LOOKASIDE_MISS_SIZE DBStatus = 5
216+
DBSTATUS_LOOKASIDE_MISS_FULL DBStatus = 6
217+
DBSTATUS_CACHE_HIT DBStatus = 7
218+
DBSTATUS_CACHE_MISS DBStatus = 8
219+
DBSTATUS_CACHE_WRITE DBStatus = 9
220+
DBSTATUS_DEFERRED_FKS DBStatus = 10
221+
DBSTATUS_CACHE_USED_SHARED DBStatus = 11
222+
DBSTATUS_CACHE_SPILL DBStatus = 12
223+
)
224+
204225
// DBConfig are the available database connection configuration options.
205226
//
206227
// https://sqlite.org/c3ref/c_dbconfig_defensive.html
@@ -307,8 +328,8 @@ const (
307328
AUTH_DROP_VTABLE AuthorizerActionCode = 30 /* Table Name Module Name */
308329
AUTH_FUNCTION AuthorizerActionCode = 31 /* NULL Function Name */
309330
AUTH_SAVEPOINT AuthorizerActionCode = 32 /* Operation Savepoint Name */
310-
AUTH_COPY AuthorizerActionCode = 0 /* No longer used */
311331
AUTH_RECURSIVE AuthorizerActionCode = 33 /* NULL NULL */
332+
// AUTH_COPY AuthorizerActionCode = 0 /* No longer used */
312333
)
313334

314335
// AuthorizerReturnCode are the integer codes
@@ -346,6 +367,18 @@ const (
346367
TXN_WRITE TxnState = 2
347368
)
348369

370+
// TraceEvent identify classes of events that can be monitored with [Conn.Trace].
371+
//
372+
// https://sqlite.org/c3ref/c_trace.html
373+
type TraceEvent uint32
374+
375+
const (
376+
TRACE_STMT TraceEvent = 0x01
377+
TRACE_PROFILE TraceEvent = 0x02
378+
TRACE_ROW TraceEvent = 0x04
379+
TRACE_CLOSE TraceEvent = 0x08
380+
)
381+
349382
// Datatype is a fundamental datatype of SQLite.
350383
//
351384
// https://sqlite.org/c3ref/c_blob.html

driver/driver.go

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,22 @@ func init() {
7474

7575
// Open opens the SQLite database specified by dataSourceName as a [database/sql.DB].
7676
//
77-
// The init function is called by the driver on new connections.
77+
// Open accepts zero, one, or two callbacks (nil callbacks are ignored).
78+
// The first callback is called when the driver opens a new connection.
79+
// The second callback is called before the driver closes a connection.
7880
// The [sqlite3.Conn] can be used to execute queries, register functions, etc.
79-
// Any error returned closes the connection and is returned to [database/sql].
80-
func Open(dataSourceName string, init func(*sqlite3.Conn) error) (*sql.DB, error) {
81-
c, err := (&SQLite{init}).OpenConnector(dataSourceName)
81+
func Open(dataSourceName string, fn ...func(*sqlite3.Conn) error) (*sql.DB, error) {
82+
var drv SQLite
83+
if len(fn) > 2 {
84+
return nil, util.ArgErr
85+
}
86+
if len(fn) > 1 {
87+
drv.term = fn[1]
88+
}
89+
if len(fn) > 0 {
90+
drv.init = fn[0]
91+
}
92+
c, err := drv.OpenConnector(dataSourceName)
8293
if err != nil {
8394
return nil, err
8495
}
@@ -88,6 +99,7 @@ func Open(dataSourceName string, init func(*sqlite3.Conn) error) (*sql.DB, error
8899
// SQLite implements [database/sql/driver.Driver].
89100
type SQLite struct {
90101
init func(*sqlite3.Conn) error
102+
term func(*sqlite3.Conn) error
91103
}
92104

93105
// Open implements [database/sql/driver.Driver].
@@ -204,6 +216,14 @@ func (n *connector) Connect(ctx context.Context) (_ driver.Conn, err error) {
204216
return nil, err
205217
}
206218
}
219+
if n.driver.term != nil {
220+
err = c.Conn.Trace(sqlite3.TRACE_CLOSE, func(sqlite3.TraceEvent, any, any) error {
221+
return n.driver.term(c.Conn)
222+
})
223+
if err != nil {
224+
return nil, err
225+
}
226+
}
207227
return c, nil
208228
}
209229

driver/json_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
)
1212

1313
func Example_json() {
14-
db, err := driver.Open("file:/test.db?vfs=memdb", nil)
14+
db, err := driver.Open("file:/test.db?vfs=memdb")
1515
if err != nil {
1616
log.Fatal(err)
1717
}

driver/savepoint_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
)
1111

1212
func ExampleSavepoint() {
13-
db, err := driver.Open("file:/test.db?vfs=memdb", nil)
13+
db, err := driver.Open("file:/test.db?vfs=memdb")
1414
if err != nil {
1515
log.Fatal(err)
1616
}

0 commit comments

Comments
 (0)