Skip to content

Commit 5eeb9cc

Browse files
committed
Add SetErrorLog function
This function sets a callback that SQLite invokes when it detects an anomaly. See https://sqlite.org/errlog.html.
1 parent 7658c06 commit 5eeb9cc

File tree

3 files changed

+80
-2
lines changed

3 files changed

+80
-2
lines changed

Diff for: callback.go

+9
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package sqlite3
1616
#else
1717
#include <sqlite3.h>
1818
#endif
19+
#include <stdint.h>
1920
#include <stdlib.h>
2021
2122
void _sqlite3_result_text(sqlite3_context* ctx, const char* s);
@@ -29,9 +30,17 @@ import (
2930
"math"
3031
"reflect"
3132
"sync"
33+
"sync/atomic"
3234
"unsafe"
3335
)
3436

37+
var errorLogCallback atomic.Value
38+
39+
//export errorLogTrampoline
40+
func errorLogTrampoline(_ C.uintptr_t, errCode C.int, msg *C.char) {
41+
errorLogCallback.Load().(func(Error, string))(errorFromCode(errCode), C.GoString(msg))
42+
}
43+
3544
//export callbackTrampoline
3645
func callbackTrampoline(ctx *C.sqlite3_context, argc int, argv **C.sqlite3_value) {
3746
args := (*[(math.MaxInt32 - 1) / unsafe.Sizeof((*C.sqlite3_value)(nil))]*C.sqlite3_value)(unsafe.Pointer(argv))[:argc:argc]

Diff for: sqlite3.go

+37-2
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@ package sqlite3
5959
# define USE_PWRITE64 1
6060
#endif
6161
62+
void errorLogTrampoline(void *userPtr, int errCode, const char *msg);
63+
64+
static int
65+
_sqlite3_config_log() {
66+
return sqlite3_config(SQLITE_CONFIG_LOG, &errorLogTrampoline, NULL);
67+
}
68+
6269
static int
6370
_sqlite3_open_v2(const char *filename, sqlite3 **ppDb, int flags, const char *zVfs) {
6471
#ifdef SQLITE_OPEN_URI
@@ -263,14 +270,35 @@ func Version() (libVersion string, libVersionNumber int, sourceID string) {
263270
return libVersion, libVersionNumber, sourceID
264271
}
265272

273+
// SetErrorLog registers the given callback to be invoked with a message whenever SQLite detects an
274+
// anomaly. It is good practice to redirect such messages to the application log. See
275+
// https://sqlite.org/errlog.html.
276+
// The provided callback function receives an SQLite error object, denoting the broad category of
277+
// error, and a message string. It must not call any SQLite functions; in fact, the SQLite docs
278+
// recommend treating the callback function like a signal handler, minimizing the work done in it.
279+
// SetErrorLog must not be called while any other goroutine is running that might be calling into
280+
// the SQLite library.
281+
func SetErrorLog(callback func(err Error, msg string)) error {
282+
errorLogCallback.Store(callback)
283+
if rc := C._sqlite3_config_log(); rc == 0 {
284+
return nil
285+
} else {
286+
return errorFromCode(rc)
287+
}
288+
}
289+
266290
const (
291+
// some common return codes
292+
SQLITE_OK = C.SQLITE_OK
293+
SQLITE_NOTICE = C.SQLITE_NOTICE
294+
SQLITE_WARNING = C.SQLITE_WARNING
295+
267296
// used by authorizer and pre_update_hook
268297
SQLITE_DELETE = C.SQLITE_DELETE
269298
SQLITE_INSERT = C.SQLITE_INSERT
270299
SQLITE_UPDATE = C.SQLITE_UPDATE
271300

272-
// used by authorzier - as return value
273-
SQLITE_OK = C.SQLITE_OK
301+
// used by authorizer as return value, in addition to SQLITE_OK
274302
SQLITE_IGNORE = C.SQLITE_IGNORE
275303
SQLITE_DENY = C.SQLITE_DENY
276304

@@ -845,6 +873,13 @@ func lastError(db *C.sqlite3) error {
845873
}
846874
}
847875

876+
func errorFromCode(rc C.int) Error {
877+
return Error{
878+
Code: ErrNo(rc & ErrNoMask),
879+
ExtendedCode: ErrNoExtended(rc),
880+
}
881+
}
882+
848883
// Exec implements Execer.
849884
func (c *SQLiteConn) Exec(query string, args []driver.Value) (driver.Result, error) {
850885
list := make([]driver.NamedValue, len(args))

Diff for: sqlite3_test.go

+34
Original file line numberDiff line numberDiff line change
@@ -1248,6 +1248,40 @@ func TestVersion(t *testing.T) {
12481248
}
12491249
}
12501250

1251+
func TestErrorLog(t *testing.T) {
1252+
var errorLogged bool
1253+
var capturedErr Error
1254+
var capturedMsg string
1255+
err := SetErrorLog(func(err Error, msg string) {
1256+
errorLogged = true
1257+
capturedErr = err
1258+
capturedMsg = msg
1259+
})
1260+
if err != nil {
1261+
t.Fatal("Failed to set error logger:", err)
1262+
}
1263+
1264+
db, err := sql.Open("sqlite3", ":memory:")
1265+
if err != nil {
1266+
t.Fatal("Failed to open database:", err)
1267+
}
1268+
defer db.Close()
1269+
1270+
if _, err := db.Exec(`SELECT "foo"`); err != nil {
1271+
t.Fatal("SELECT failed:", err)
1272+
}
1273+
1274+
if !errorLogged {
1275+
t.Fatal("No error was logged")
1276+
}
1277+
if capturedErr.Code != SQLITE_WARNING {
1278+
t.Errorf("Unexpected error log code: %d", capturedErr.Code)
1279+
}
1280+
if !strings.Contains(capturedMsg, "double-quoted string literal") {
1281+
t.Errorf("Unexpected error log message: '%s'", capturedMsg)
1282+
}
1283+
}
1284+
12511285
func TestStringContainingZero(t *testing.T) {
12521286
tempFilename := TempFilename(t)
12531287
defer os.Remove(tempFilename)

0 commit comments

Comments
 (0)