From fc50f93c6849c5352d12b93a9990051b4e83e3cc Mon Sep 17 00:00:00 2001 From: Timo Riski Date: Fri, 25 Sep 2026 22:49:52 +0300 Subject: [PATCH] Expose SQLite extension loading on libsql connections --- libsql.go | 20 ++++++++++++++++++++ libsql_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/libsql.go b/libsql.go index 7ec436d..c2e2720 100644 --- a/libsql.go +++ b/libsql.go @@ -437,6 +437,26 @@ type conn struct { nativePtr C.libsql_connection_t } +// LoadExtension loads a SQLite extension on this connection. Call it through +// sql.Conn.Raw for each connection in a database/sql pool. +func (c *conn) LoadExtension(path, entryPoint string) error { + pathCString := C.CString(path) + defer C.free(unsafe.Pointer(pathCString)) + + var entryPointCString *C.char + if entryPoint != "" { + entryPointCString = C.CString(entryPoint) + defer C.free(unsafe.Pointer(entryPointCString)) + } + + var errMsg *C.char + statusCode := C.libsql_load_extension(c.nativePtr, pathCString, entryPointCString, &errMsg) + if statusCode != 0 { + return libsqlError("failed to load extension", statusCode, errMsg) + } + return nil +} + func (c *conn) Prepare(query string) (sqldriver.Stmt, error) { return c.PrepareContext(context.Background(), query) } diff --git a/libsql_test.go b/libsql_test.go index 9b0d139..d33c433 100644 --- a/libsql_test.go +++ b/libsql_test.go @@ -1187,6 +1187,47 @@ func TestErrorCanNotConnect(t *testing.T) { } } +func TestLoadExtension(t *testing.T) { + db, err := sql.Open("libsql", "file:"+t.TempDir()+"/test.db") + if err != nil { + t.Fatal(err) + } + defer db.Close() + + conn, err := db.Conn(context.Background()) + if err != nil { + t.Fatal(err) + } + defer conn.Close() + + err = conn.Raw(func(driverConn any) error { + loader, ok := driverConn.(interface{ LoadExtension(string, string) error }) + if !ok { + t.Fatal("driver connection does not support extension loading") + } + if err := loader.LoadExtension("/nonexistent/libsql-test-extension", ""); err == nil { + t.Fatal("expected a load error for a missing extension") + } + if path := os.Getenv("LIBSQL_TEST_EXTENSION_PATH"); path != "" { + return loader.LoadExtension(path, "") + } + return nil + }) + if err != nil { + t.Fatal(err) + } + + if os.Getenv("LIBSQL_TEST_EXTENSION_PATH") != "" { + var nanos int64 + if err := conn.QueryRowContext(context.Background(), "SELECT time_get_nano(time_now())").Scan(&nanos); err != nil { + t.Fatal(err) + } + if nanos <= 0 { + t.Fatalf("unexpected nanosecond timestamp %d", nanos) + } + } +} + func TestExec(t *testing.T) { runMemoryAndFileTests(t, func(t *testing.T, db *sql.DB) { if _, err := db.ExecContext(context.Background(), "CREATE TABLE test (id INTEGER, name TEXT)"); err != nil {