config.go

  1package sqlite3
  2
  3import (
  4	"context"
  5	"fmt"
  6	"strconv"
  7	"sync/atomic"
  8
  9	"github.com/tetratelabs/wazero/api"
 10
 11	"github.com/ncruces/go-sqlite3/internal/util"
 12	"github.com/ncruces/go-sqlite3/vfs"
 13)
 14
 15// Config makes configuration changes to a database connection.
 16// Only boolean configuration options are supported.
 17// Called with no arg reads the current configuration value,
 18// called with one arg sets and returns the new value.
 19//
 20// https://sqlite.org/c3ref/db_config.html
 21func (c *Conn) Config(op DBConfig, arg ...bool) (bool, error) {
 22	if op < DBCONFIG_ENABLE_FKEY || op > DBCONFIG_REVERSE_SCANORDER {
 23		return false, MISUSE
 24	}
 25
 26	// We need to call sqlite3_db_config, a variadic function.
 27	// We only support the `int int*` variants.
 28	// The int is a three-valued bool: -1 queries, 0/1 sets false/true.
 29	// The int* points to where new state will be written to.
 30	// The vararg is a pointer to an array containing these arguments:
 31	// an int and an int* pointing to that int.
 32
 33	defer c.arena.mark()()
 34	argsPtr := c.arena.new(intlen + ptrlen)
 35
 36	var flag int32
 37	switch {
 38	case len(arg) == 0:
 39		flag = -1
 40	case arg[0]:
 41		flag = 1
 42	}
 43
 44	util.Write32(c.mod, argsPtr+0*ptrlen, flag)
 45	util.Write32(c.mod, argsPtr+1*ptrlen, argsPtr)
 46
 47	rc := res_t(c.call("sqlite3_db_config", stk_t(c.handle),
 48		stk_t(op), stk_t(argsPtr)))
 49	return util.ReadBool(c.mod, argsPtr), c.error(rc)
 50}
 51
 52var defaultLogger atomic.Pointer[func(code ExtendedErrorCode, msg string)]
 53
 54// ConfigLog sets up the default error logging callback for new connections.
 55//
 56// https://sqlite.org/errlog.html
 57func ConfigLog(cb func(code ExtendedErrorCode, msg string)) {
 58	defaultLogger.Store(&cb)
 59}
 60
 61// ConfigLog sets up the error logging callback for the connection.
 62//
 63// https://sqlite.org/errlog.html
 64func (c *Conn) ConfigLog(cb func(code ExtendedErrorCode, msg string)) error {
 65	var enable int32
 66	if cb != nil {
 67		enable = 1
 68	}
 69	rc := res_t(c.call("sqlite3_config_log_go", stk_t(enable)))
 70	if err := c.error(rc); err != nil {
 71		return err
 72	}
 73	c.log = cb
 74	return nil
 75}
 76
 77func logCallback(ctx context.Context, mod api.Module, _ ptr_t, iCode res_t, zMsg ptr_t) {
 78	if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.log != nil {
 79		msg := util.ReadString(mod, zMsg, _MAX_LENGTH)
 80		c.log(xErrorCode(iCode), msg)
 81	}
 82}
 83
 84// Log writes a message into the error log established by [Conn.ConfigLog].
 85//
 86// https://sqlite.org/c3ref/log.html
 87func (c *Conn) Log(code ExtendedErrorCode, format string, a ...any) {
 88	if c.log != nil {
 89		c.log(code, fmt.Sprintf(format, a...))
 90	}
 91}
 92
 93// FileControl allows low-level control of database files.
 94// Only a subset of opcodes are supported.
 95//
 96// https://sqlite.org/c3ref/file_control.html
 97func (c *Conn) FileControl(schema string, op FcntlOpcode, arg ...any) (any, error) {
 98	defer c.arena.mark()()
 99	ptr := c.arena.new(max(ptrlen, intlen))
100
101	var schemaPtr ptr_t
102	if schema != "" {
103		schemaPtr = c.arena.string(schema)
104	}
105
106	var rc res_t
107	var ret any
108	switch op {
109	default:
110		return nil, MISUSE
111
112	case FCNTL_RESET_CACHE:
113		rc = res_t(c.call("sqlite3_file_control",
114			stk_t(c.handle), stk_t(schemaPtr),
115			stk_t(op), 0))
116
117	case FCNTL_PERSIST_WAL, FCNTL_POWERSAFE_OVERWRITE:
118		var flag int32
119		switch {
120		case len(arg) == 0:
121			flag = -1
122		case arg[0]:
123			flag = 1
124		}
125		util.Write32(c.mod, ptr, flag)
126		rc = res_t(c.call("sqlite3_file_control",
127			stk_t(c.handle), stk_t(schemaPtr),
128			stk_t(op), stk_t(ptr)))
129		ret = util.ReadBool(c.mod, ptr)
130
131	case FCNTL_CHUNK_SIZE:
132		util.Write32(c.mod, ptr, int32(arg[0].(int)))
133		rc = res_t(c.call("sqlite3_file_control",
134			stk_t(c.handle), stk_t(schemaPtr),
135			stk_t(op), stk_t(ptr)))
136
137	case FCNTL_RESERVE_BYTES:
138		bytes := -1
139		if len(arg) > 0 {
140			bytes = arg[0].(int)
141		}
142		util.Write32(c.mod, ptr, int32(bytes))
143		rc = res_t(c.call("sqlite3_file_control",
144			stk_t(c.handle), stk_t(schemaPtr),
145			stk_t(op), stk_t(ptr)))
146		ret = int(util.Read32[int32](c.mod, ptr))
147
148	case FCNTL_DATA_VERSION:
149		rc = res_t(c.call("sqlite3_file_control",
150			stk_t(c.handle), stk_t(schemaPtr),
151			stk_t(op), stk_t(ptr)))
152		ret = util.Read32[uint32](c.mod, ptr)
153
154	case FCNTL_LOCKSTATE:
155		rc = res_t(c.call("sqlite3_file_control",
156			stk_t(c.handle), stk_t(schemaPtr),
157			stk_t(op), stk_t(ptr)))
158		ret = util.Read32[vfs.LockLevel](c.mod, ptr)
159
160	case FCNTL_VFS_POINTER:
161		rc = res_t(c.call("sqlite3_file_control",
162			stk_t(c.handle), stk_t(schemaPtr),
163			stk_t(op), stk_t(ptr)))
164		if rc == _OK {
165			const zNameOffset = 16
166			ptr = util.Read32[ptr_t](c.mod, ptr)
167			ptr = util.Read32[ptr_t](c.mod, ptr+zNameOffset)
168			name := util.ReadString(c.mod, ptr, _MAX_NAME)
169			ret = vfs.Find(name)
170		}
171
172	case FCNTL_FILE_POINTER, FCNTL_JOURNAL_POINTER:
173		rc = res_t(c.call("sqlite3_file_control",
174			stk_t(c.handle), stk_t(schemaPtr),
175			stk_t(op), stk_t(ptr)))
176		if rc == _OK {
177			const fileHandleOffset = 4
178			ptr = util.Read32[ptr_t](c.mod, ptr)
179			ptr = util.Read32[ptr_t](c.mod, ptr+fileHandleOffset)
180			ret = util.GetHandle(c.ctx, ptr)
181		}
182	}
183
184	if err := c.error(rc); err != nil {
185		return nil, err
186	}
187	return ret, nil
188}
189
190// Limit allows the size of various constructs to be
191// limited on a connection by connection basis.
192//
193// https://sqlite.org/c3ref/limit.html
194func (c *Conn) Limit(id LimitCategory, value int) int {
195	v := int32(c.call("sqlite3_limit", stk_t(c.handle), stk_t(id), stk_t(value)))
196	return int(v)
197}
198
199// SetAuthorizer registers an authorizer callback with the database connection.
200//
201// https://sqlite.org/c3ref/set_authorizer.html
202func (c *Conn) SetAuthorizer(cb func(action AuthorizerActionCode, name3rd, name4th, schema, inner string) AuthorizerReturnCode) error {
203	var enable int32
204	if cb != nil {
205		enable = 1
206	}
207	rc := res_t(c.call("sqlite3_set_authorizer_go", stk_t(c.handle), stk_t(enable)))
208	if err := c.error(rc); err != nil {
209		return err
210	}
211	c.authorizer = cb
212	return nil
213
214}
215
216func authorizerCallback(ctx context.Context, mod api.Module, pDB ptr_t, action AuthorizerActionCode, zName3rd, zName4th, zSchema, zInner ptr_t) (rc AuthorizerReturnCode) {
217	if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.authorizer != nil {
218		var name3rd, name4th, schema, inner string
219		if zName3rd != 0 {
220			name3rd = util.ReadString(mod, zName3rd, _MAX_NAME)
221		}
222		if zName4th != 0 {
223			name4th = util.ReadString(mod, zName4th, _MAX_NAME)
224		}
225		if zSchema != 0 {
226			schema = util.ReadString(mod, zSchema, _MAX_NAME)
227		}
228		if zInner != 0 {
229			inner = util.ReadString(mod, zInner, _MAX_NAME)
230		}
231		rc = c.authorizer(action, name3rd, name4th, schema, inner)
232	}
233	return rc
234}
235
236// Trace registers a trace callback function against the database connection.
237//
238// https://sqlite.org/c3ref/trace_v2.html
239func (c *Conn) Trace(mask TraceEvent, cb func(evt TraceEvent, arg1 any, arg2 any) error) error {
240	rc := res_t(c.call("sqlite3_trace_go", stk_t(c.handle), stk_t(mask)))
241	if err := c.error(rc); err != nil {
242		return err
243	}
244	c.trace = cb
245	return nil
246}
247
248func traceCallback(ctx context.Context, mod api.Module, evt TraceEvent, pDB, pArg1, pArg2 ptr_t) (rc res_t) {
249	if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.trace != nil {
250		var arg1, arg2 any
251		if evt == TRACE_CLOSE {
252			arg1 = c
253		} else {
254			for _, s := range c.stmts {
255				if pArg1 == s.handle {
256					arg1 = s
257					switch evt {
258					case TRACE_STMT:
259						arg2 = s.SQL()
260					case TRACE_PROFILE:
261						arg2 = util.Read64[int64](mod, pArg2)
262					}
263					break
264				}
265			}
266		}
267		if arg1 != nil {
268			_, rc = errorCode(c.trace(evt, arg1, arg2), ERROR)
269		}
270	}
271	return rc
272}
273
274// WALCheckpoint checkpoints a WAL database.
275//
276// https://sqlite.org/c3ref/wal_checkpoint_v2.html
277func (c *Conn) WALCheckpoint(schema string, mode CheckpointMode) (nLog, nCkpt int, err error) {
278	if c.interrupt.Err() != nil {
279		return 0, 0, INTERRUPT
280	}
281
282	defer c.arena.mark()()
283	nLogPtr := c.arena.new(ptrlen)
284	nCkptPtr := c.arena.new(ptrlen)
285	schemaPtr := c.arena.string(schema)
286	rc := res_t(c.call("sqlite3_wal_checkpoint_v2",
287		stk_t(c.handle), stk_t(schemaPtr), stk_t(mode),
288		stk_t(nLogPtr), stk_t(nCkptPtr)))
289	nLog = int(util.Read32[int32](c.mod, nLogPtr))
290	nCkpt = int(util.Read32[int32](c.mod, nCkptPtr))
291	return nLog, nCkpt, c.error(rc)
292}
293
294// WALAutoCheckpoint configures WAL auto-checkpoints.
295//
296// https://sqlite.org/c3ref/wal_autocheckpoint.html
297func (c *Conn) WALAutoCheckpoint(pages int) error {
298	rc := res_t(c.call("sqlite3_wal_autocheckpoint", stk_t(c.handle), stk_t(pages)))
299	return c.error(rc)
300}
301
302// WALHook registers a callback function to be invoked
303// each time data is committed to a database in WAL mode.
304//
305// https://sqlite.org/c3ref/wal_hook.html
306func (c *Conn) WALHook(cb func(db *Conn, schema string, pages int) error) {
307	var enable int32
308	if cb != nil {
309		enable = 1
310	}
311	c.call("sqlite3_wal_hook_go", stk_t(c.handle), stk_t(enable))
312	c.wal = cb
313}
314
315func walCallback(ctx context.Context, mod api.Module, _, pDB, zSchema ptr_t, pages int32) (rc res_t) {
316	if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.wal != nil {
317		schema := util.ReadString(mod, zSchema, _MAX_NAME)
318		err := c.wal(c, schema, int(pages))
319		_, rc = errorCode(err, ERROR)
320	}
321	return rc
322}
323
324// AutoVacuumPages registers a autovacuum compaction amount callback.
325//
326// https://sqlite.org/c3ref/autovacuum_pages.html
327func (c *Conn) AutoVacuumPages(cb func(schema string, dbPages, freePages, bytesPerPage uint) uint) error {
328	var funcPtr ptr_t
329	if cb != nil {
330		funcPtr = util.AddHandle(c.ctx, cb)
331	}
332	rc := res_t(c.call("sqlite3_autovacuum_pages_go", stk_t(c.handle), stk_t(funcPtr)))
333	return c.error(rc)
334}
335
336func autoVacuumCallback(ctx context.Context, mod api.Module, pApp, zSchema ptr_t, nDbPage, nFreePage, nBytePerPage uint32) uint32 {
337	fn := util.GetHandle(ctx, pApp).(func(schema string, dbPages, freePages, bytesPerPage uint) uint)
338	schema := util.ReadString(mod, zSchema, _MAX_NAME)
339	return uint32(fn(schema, uint(nDbPage), uint(nFreePage), uint(nBytePerPage)))
340}
341
342// SoftHeapLimit imposes a soft limit on heap size.
343//
344// https://sqlite.org/c3ref/hard_heap_limit64.html
345func (c *Conn) SoftHeapLimit(n int64) int64 {
346	return int64(c.call("sqlite3_soft_heap_limit64", stk_t(n)))
347}
348
349// HardHeapLimit imposes a hard limit on heap size.
350//
351// https://sqlite.org/c3ref/hard_heap_limit64.html
352func (c *Conn) HardHeapLimit(n int64) int64 {
353	return int64(c.call("sqlite3_hard_heap_limit64", stk_t(n)))
354}
355
356// EnableChecksums enables checksums on a database.
357//
358// https://sqlite.org/cksumvfs.html
359func (c *Conn) EnableChecksums(schema string) error {
360	r, err := c.FileControl(schema, FCNTL_RESERVE_BYTES)
361	if err != nil {
362		return err
363	}
364	if r == 8 {
365		// Correct value, enabled.
366		return nil
367	}
368	if r == 0 {
369		// Default value, enable.
370		_, err = c.FileControl(schema, FCNTL_RESERVE_BYTES, 8)
371		if err != nil {
372			return err
373		}
374		r, err = c.FileControl(schema, FCNTL_RESERVE_BYTES)
375		if err != nil {
376			return err
377		}
378	}
379	if r != 8 {
380		// Invalid value.
381		return util.ErrorString("sqlite3: reserve bytes must be 8, is: " + strconv.Itoa(r.(int)))
382	}
383
384	// VACUUM the database.
385	if schema != "" {
386		err = c.Exec(`VACUUM ` + QuoteIdentifier(schema))
387	} else {
388		err = c.Exec(`VACUUM`)
389	}
390	if err != nil {
391		return err
392	}
393
394	// Checkpoint the WAL.
395	_, _, err = c.WALCheckpoint(schema, CHECKPOINT_FULL)
396	return err
397}