Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SetErrorLog function #1190

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions callback.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package sqlite3
#else
#include <sqlite3.h>
#endif
#include <stdint.h>
#include <stdlib.h>

void _sqlite3_result_text(sqlite3_context* ctx, const char* s);
Expand All @@ -29,9 +30,17 @@ import (
"math"
"reflect"
"sync"
"sync/atomic"
"unsafe"
)

var errorLogCallback atomic.Value

//export errorLogTrampoline
func errorLogTrampoline(_ C.uintptr_t, errCode C.int, msg *C.char) {
errorLogCallback.Load().(func(Error, string))(errorFromCode(errCode), C.GoString(msg))
}

//export callbackTrampoline
func callbackTrampoline(ctx *C.sqlite3_context, argc int, argv **C.sqlite3_value) {
args := (*[(math.MaxInt32 - 1) / unsafe.Sizeof((*C.sqlite3_value)(nil))]*C.sqlite3_value)(unsafe.Pointer(argv))[:argc:argc]
Expand Down
39 changes: 37 additions & 2 deletions sqlite3.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ package sqlite3
# define USE_PWRITE64 1
#endif

void errorLogTrampoline(void *userPtr, int errCode, const char *msg);

static int
_sqlite3_config_log() {
return sqlite3_config(SQLITE_CONFIG_LOG, &errorLogTrampoline, NULL);
}

static int
_sqlite3_open_v2(const char *filename, sqlite3 **ppDb, int flags, const char *zVfs) {
#ifdef SQLITE_OPEN_URI
Expand Down Expand Up @@ -263,14 +270,35 @@ func Version() (libVersion string, libVersionNumber int, sourceID string) {
return libVersion, libVersionNumber, sourceID
}

// SetErrorLog registers the given callback to be invoked with a message whenever SQLite detects an
// anomaly. It is good practice to redirect such messages to the application log. See
// https://sqlite.org/errlog.html.
// The provided callback function receives an SQLite error object, denoting the broad category of
// error, and a message string. It must not call any SQLite functions; in fact, the SQLite docs
// recommend treating the callback function like a signal handler, minimizing the work done in it.
// SetErrorLog must not be called while any other goroutine is running that might be calling into
// the SQLite library.
func SetErrorLog(callback func(err Error, msg string)) error {
errorLogCallback.Store(callback)
if rc := C._sqlite3_config_log(); rc == 0 {
return nil
} else {
return errorFromCode(rc)
}
}

const (
// some common return codes
SQLITE_OK = C.SQLITE_OK
SQLITE_NOTICE = C.SQLITE_NOTICE
SQLITE_WARNING = C.SQLITE_WARNING

// used by authorizer and pre_update_hook
SQLITE_DELETE = C.SQLITE_DELETE
SQLITE_INSERT = C.SQLITE_INSERT
SQLITE_UPDATE = C.SQLITE_UPDATE

// used by authorzier - as return value
SQLITE_OK = C.SQLITE_OK
// used by authorizer as return value, in addition to SQLITE_OK
SQLITE_IGNORE = C.SQLITE_IGNORE
SQLITE_DENY = C.SQLITE_DENY

Expand Down Expand Up @@ -845,6 +873,13 @@ func lastError(db *C.sqlite3) error {
}
}

func errorFromCode(rc C.int) Error {
return Error{
Code: ErrNo(rc & ErrNoMask),
ExtendedCode: ErrNoExtended(rc),
}
}

// Exec implements Execer.
func (c *SQLiteConn) Exec(query string, args []driver.Value) (driver.Result, error) {
list := make([]driver.NamedValue, len(args))
Expand Down
34 changes: 34 additions & 0 deletions sqlite3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1248,6 +1248,40 @@ func TestVersion(t *testing.T) {
}
}

func TestErrorLog(t *testing.T) {
var errorLogged bool
var capturedErr Error
var capturedMsg string
err := SetErrorLog(func(err Error, msg string) {
errorLogged = true
capturedErr = err
capturedMsg = msg
})
if err != nil {
t.Fatal("Failed to set error logger:", err)
}

db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatal("Failed to open database:", err)
}
defer db.Close()

if _, err := db.Exec(`SELECT "foo"`); err != nil {
t.Fatal("SELECT failed:", err)
}

if !errorLogged {
t.Fatal("No error was logged")
}
if capturedErr.Code != SQLITE_WARNING {
t.Errorf("Unexpected error log code: %d", capturedErr.Code)
}
if !strings.Contains(capturedMsg, "double-quoted string literal") {
t.Errorf("Unexpected error log message: '%s'", capturedMsg)
}
}

func TestStringContainingZero(t *testing.T) {
tempFilename := TempFilename(t)
defer os.Remove(tempFilename)
Expand Down