diff --git a/lib/index.ts b/lib/index.ts index 424db67..e6b1fa1 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -48,6 +48,8 @@ const addon = bindings<{ databaseClose(db: NativeDatabase): void; signalTokenize(value: string): Array; + + setLogger(fn: (code: string, message: string) => void): void; }>(ROOT_DIR); export type RunResult = { @@ -587,4 +589,12 @@ export default class Database { } } -export { Database }; +function setLogger(fn: (code: string, message: string) => void): void { + if (typeof fn !== 'function') { + throw new TypeError('Invalid value'); + } + + return addon.setLogger(fn); +} + +export { Database, setLogger }; diff --git a/src/addon.cc b/src/addon.cc index 8677909..ed734c0 100644 --- a/src/addon.cc +++ b/src/addon.cc @@ -74,6 +74,48 @@ static Napi::Value SignalTokenize(const Napi::CallbackInfo& info) { return result; } +// Global Settings + +thread_local Napi::Reference logger_fn_; + +static void LoggerWrapper(void* _ctx, int code, const char* msg) { + if (logger_fn_.IsEmpty()) { + return; + } + + auto env = logger_fn_.Env(); + Napi::HandleScope scope(env); + +#define CODE_STR(NAME) \ + case NAME: \ + code_name = #NAME; \ + break; + + const char* code_name; + switch (code) { + SQLITE_ERROR_ENUM(CODE_STR) + default: + code_name = "unknown"; + break; + } + +#undef CODE_STR + + logger_fn_.Value().Call({ + Napi::String::New(env, code_name), + Napi::String::New(env, msg), + }); +} + +static void SetLogger(const Napi::CallbackInfo& info) { + auto callback = info[0].As(); + assert(callback.IsFunction()); + + logger_fn_.Reset(callback, 1); + + sqlite3_config(SQLITE_CONFIG_LOG, LoggerWrapper); +} + // Utils Napi::Error FormatError(Napi::Env env, const char* format, ...) { @@ -856,6 +898,7 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) { Database::Init(env, exports); Statement::Init(env, exports); + exports["setLogger"] = Napi::Function::New(env, &SetLogger); exports["signalTokenize"] = Napi::Function::New(env, &SignalTokenize); return exports; } diff --git a/test/memory.test.ts b/test/memory.test.ts index 340ee93..cd1bfdb 100644 --- a/test/memory.test.ts +++ b/test/memory.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test, beforeEach, afterEach } from 'vitest'; -import Database from '../lib/index.js'; +import Database, { setLogger } from '../lib/index.js'; const rows = [ { @@ -180,6 +180,21 @@ test('persistent statement recompilation', () => { }); }); +test('setLogger should log on statement recompilation', () => { + const messages = new Array<{ code: string; message: string }>(); + setLogger((code, message) => messages.push({ code, message })); + + const stmt = db.prepare('SELECT * FROM t', { persistent: true }); + + db.exec(`ALTER TABLE t ADD COLUMN d TEXT DEFAULT 'hello'`); + + expect(stmt.get()).not.toBeUndefined(); + + expect(messages).toHaveLength(1); + expect(messages[0]?.code).toEqual('SQLITE_SCHEMA'); + expect(messages[0]?.message).toMatch(/database schema has changed/); +}); + describe('list parameters', () => { test('correct count', () => { expect(db.prepare('SELECT * FROM t WHERE a > ?').get([2])).toEqual(rows[2]);