Skip to content
Merged
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
55 changes: 55 additions & 0 deletions cgosqlite/logcallback.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package cgosqlite

// #include <sqlite3.h>
//
// void logCallbackGo(void* userData, int errCode, char* msgC);
//
// static void log_callback_into_go(void *userData, int errCode, const char *msg) {
// logCallbackGo(userData, errCode, (char*)msg);
// }
//
// static int ts_sqlite3_config_log(void) {
// // TODO(raggi): if the library gains new uses of sqlite3_config they need to
// // share a mutex.
// return sqlite3_config(SQLITE_CONFIG_LOG, log_callback_into_go, NULL);
// }
import "C"
import (
"sync"
"unsafe"

"github.com/tailscale/sqlite/sqliteh"
)

// LogCallback receives SQLite log messages.
type LogCallback func(code sqliteh.Code, msg string)

var (
logCallbackMu sync.Mutex
logCallback LogCallback
)

//export logCallbackGo
func logCallbackGo(userData unsafe.Pointer, errCode C.int, msgC *C.char) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're not using the userData? This is global and not per-DB?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I figured you'd do like 695e777 did for the WALHook

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's global yeah, it's a sqlite3_config option, thankfully one of the few that can be set after sqlite3_initialize.

logCallbackMu.Lock()
cb := logCallback
logCallbackMu.Unlock()

if cb == nil {
return
}

msg := C.GoString(msgC)
cb(sqliteh.Code(errCode), msg)
}

// SetLogCallback sets the global SQLite log callback.
// If callback is nil, logs are discarded.
func SetLogCallback(callback LogCallback) error {
logCallbackMu.Lock()
logCallback = callback
logCallbackMu.Unlock()

res := C.ts_sqlite3_config_log()
return errCode(res)
}
14 changes: 13 additions & 1 deletion sqlite_cgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,20 @@

package sqlite

import "github.com/tailscale/sqlite/cgosqlite"
import (
"github.com/tailscale/sqlite/cgosqlite"
"github.com/tailscale/sqlite/sqliteh"
)

func init() {
Open = cgosqlite.Open
}

// LogCallback receives SQLite log messages.
type LogCallback func(code sqliteh.Code, msg string)

// SetLogCallback sets the global SQLite log callback.
// If callback is nil, logs are discarded.
func SetLogCallback(callback LogCallback) error {
return cgosqlite.SetLogCallback(cgosqlite.LogCallback(callback))
}
45 changes: 45 additions & 0 deletions sqlite_cgo_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//go:build cgo
// +build cgo

package sqlite

import (
"sync"
"testing"

"github.com/tailscale/sqlite/cgosqlite"
"github.com/tailscale/sqlite/sqliteh"
)

// ensure LogCallback is convertible to cgosqlite.LogCallback
var _ cgosqlite.LogCallback = cgosqlite.LogCallback(LogCallback(func(code sqliteh.Code, msg string) {}))

func TestSetLogCallback(t *testing.T) {
var mu sync.Mutex
var logs []string

err := SetLogCallback(func(code sqliteh.Code, msg string) {
mu.Lock()
defer mu.Unlock()
logs = append(logs, msg)
})
if err != nil {
t.Fatal(err)
}
defer SetLogCallback(nil)

db := openTestDB(t)

_, err = db.Exec("SELECT * FROM nonexistent_table")
if err == nil {
t.Fatal("expected error from invalid SQL")
}

mu.Lock()
gotLogs := len(logs) > 0
mu.Unlock()

if !gotLogs {
t.Fatal("expected to receive log messages")
}
}
Loading