From 45f49ecf790909ffa45a2a69d721cc1b933b5ee5 Mon Sep 17 00:00:00 2001 From: David Abram Date: Tue, 17 Jun 2025 01:13:04 +0200 Subject: [PATCH 1/6] initial setup + tests --- libsql.go | 30 ++++++++++++++++++++++ libsql_test.go | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/libsql.go b/libsql.go index 055c088..9364c41 100644 --- a/libsql.go +++ b/libsql.go @@ -424,6 +424,36 @@ type conn struct { nativePtr C.libsql_connection_t } +type extension struct { + Library string + Entry string +} + +func (c *conn) loadExtensions(exts []extension) error { + for _, ext := range exts { + if err := c.loadExtension(ext.Library, ext.Entry); err != nil { + return err + } + } + return nil +} + +func (c *conn) LoadExtension(lib string, entry string) error { + if err := c.loadExtension(lib, entry); err != nil { + return err + } + return nil +} + +func (c *conn) loadExtension(lib string, entry string) error { + var errMsg *C.char + statusCode := C.libsql_load_extension(c.nativePtr, C.CString(lib), C.CString(entry), nil) + if statusCode != 0 { + return libsqlError(fmt.Sprintf("failed to load extension %s with entry point %s", lib, entry), 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..2f35117 100644 --- a/libsql_test.go +++ b/libsql_test.go @@ -1359,3 +1359,71 @@ func TestErrorRowsNext(t *testing.T) { } }) } + +// To run this, set LIBSQL_TEST_EXTENSION to the full path of a valid SQLite extension +// and (optionally) LIBSQL_TEST_EXTENSION_ENTRY to its init symbol (defaults to "sqlite3_extension_init"). +func TestLoadExtension_Existing(t *testing.T) { + extPath := os.Getenv("LIBSQL_TEST_EXTENSION") + if extPath == "" { + t.Skip("LIBSQL_TEST_EXTENSION not set; skipping existing‐extension load test") + } + entryPoint := os.Getenv("LIBSQL_TEST_EXTENSION_ENTRY") + if entryPoint == "" { + entryPoint = "sqlite3_extension_init" + } + + db, err := sql.Open("libsql", ":memory:") + if err != nil { + t.Fatal(err) + } + defer db.Close() + + ctx := context.Background() + sqlConn, err := db.Conn(ctx) + if err != nil { + t.Fatal(err) + } + defer sqlConn.Close() + + err = sqlConn.Raw(func(driverConn interface{}) error { + cImpl, ok := driverConn.(*conn) + if !ok { + return fmt.Errorf("unexpected driverConn type %T", driverConn) + } + return cImpl.LoadExtension(extPath, entryPoint) + }) + + if err != nil { + t.Fatalf("failed to load existing extension %q (entry %q): %v", extPath, entryPoint, err) + } +} + +func TestLoadExtension_Nonexistent(t *testing.T) { + db, err := sql.Open("libsql", ":memory:") + if err != nil { + t.Fatal(err) + } + defer db.Close() + + ctx := context.Background() + sqlConn, err := db.Conn(ctx) + if err != nil { + t.Fatal(err) + } + defer sqlConn.Close() + + err = sqlConn.Raw(func(driverConn interface{}) error { + cImpl, ok := driverConn.(*conn) + if !ok { + return fmt.Errorf("unexpected driverConn type %T", driverConn) + } + return cImpl.LoadExtension("nonexistent_extension.so", "entry_point") + }) + + if err == nil { + t.Fatal("expected error loading nonexistent extension, got nil") + } + if !strings.Contains(err.Error(), "failed to load extension") { + t.Fatalf("unexpected error loading extension: %v", err) + } +} From 3fb34fb4d866d9c32a6ee84035d35c2cd3fd0421 Mon Sep 17 00:00:00 2001 From: David Abram Date: Tue, 17 Jun 2025 01:39:39 +0200 Subject: [PATCH 2/6] update github ci to use sqlean for testing --- .github/workflows/test.yml | 20 ++++++++++++++++++++ libsql_test.go | 4 ++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 236f146..b37d8de 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -42,6 +42,26 @@ jobs: done echo "sqld is ready!" + - name: Install sqlean + run: | + SQLEAN_VERSION=0.27.2 + case "${{ runner.os }}" in + Linux) OS_SLUG=linux-amd64 ;; + macOS) OS_SLUG=darwin-amd64 ;; + esac + curl -sL https://github.com/nalgeon/sqlean/releases/download/${SQLEAN_VERSION}/sqlean-${OS_SLUG}.tar.gz | tar xz + echo "$PWD/bin" >> $GITHUB_PATH + sqlean --version + + - name: Set extension env + run: | + case "${{ runner.os }}" in + Linux) ext=bin/sqlean.so ;; + macOS) ext=bin/sqlean.dylib ;; + esac + echo "LIBSQL_TEST_EXTENSION=$PWD/$ext" >> $GITHUB_ENV + echo "LIBSQL_TEST_EXTENSION_ENTRY=sqlite3_extension_init" >> $GITHUB_ENV + - name: Build run: go build -v ./... diff --git a/libsql_test.go b/libsql_test.go index 2f35117..716ff8f 100644 --- a/libsql_test.go +++ b/libsql_test.go @@ -1385,7 +1385,7 @@ func TestLoadExtension_Existing(t *testing.T) { } defer sqlConn.Close() - err = sqlConn.Raw(func(driverConn interface{}) error { + err = sqlConn.Raw(func(driverConn any) error { cImpl, ok := driverConn.(*conn) if !ok { return fmt.Errorf("unexpected driverConn type %T", driverConn) @@ -1412,7 +1412,7 @@ func TestLoadExtension_Nonexistent(t *testing.T) { } defer sqlConn.Close() - err = sqlConn.Raw(func(driverConn interface{}) error { + err = sqlConn.Raw(func(driverConn any) error { cImpl, ok := driverConn.(*conn) if !ok { return fmt.Errorf("unexpected driverConn type %T", driverConn) From 818c7f59a3a851b684981dcc8336a0a2d776b357 Mon Sep 17 00:00:00 2001 From: David Abram Date: Tue, 17 Jun 2025 01:47:46 +0200 Subject: [PATCH 3/6] tar to zip --- .github/workflows/test.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b37d8de..637b04a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -46,12 +46,13 @@ jobs: run: | SQLEAN_VERSION=0.27.2 case "${{ runner.os }}" in - Linux) OS_SLUG=linux-amd64 ;; - macOS) OS_SLUG=darwin-amd64 ;; + Linux) OS_SLUG=linux-x86 ;; + macOS) OS_SLUG=macos-x86 ;; esac - curl -sL https://github.com/nalgeon/sqlean/releases/download/${SQLEAN_VERSION}/sqlean-${OS_SLUG}.tar.gz | tar xz - echo "$PWD/bin" >> $GITHUB_PATH - sqlean --version + curl -sL -o sqlean-${OS_SLUG}.zip \ + https://github.com/nalgeon/sqlean/releases/download/${SQLEAN_VERSION}/sqlean-${OS_SLUG}.zipecho "$PWD/bin" >> $GITHUB_PATH + unzip -q sqlean-${OS_SLUG}.zip -d . + echo "$PWD" >> $GITHUB_PATH - name: Set extension env run: | From 46b40a5b369f3a82640598d6b36e68c200e41a86 Mon Sep 17 00:00:00 2001 From: David Abram Date: Tue, 17 Jun 2025 01:49:30 +0200 Subject: [PATCH 4/6] fat fingered --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 637b04a..7d814bb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -50,7 +50,7 @@ jobs: macOS) OS_SLUG=macos-x86 ;; esac curl -sL -o sqlean-${OS_SLUG}.zip \ - https://github.com/nalgeon/sqlean/releases/download/${SQLEAN_VERSION}/sqlean-${OS_SLUG}.zipecho "$PWD/bin" >> $GITHUB_PATH + https://github.com/nalgeon/sqlean/releases/download/${SQLEAN_VERSION}/sqlean-${OS_SLUG}.zip unzip -q sqlean-${OS_SLUG}.zip -d . echo "$PWD" >> $GITHUB_PATH From 54224536f779b231b45c8b9fc35661a007ea52ca Mon Sep 17 00:00:00 2001 From: David Abram Date: Tue, 17 Jun 2025 02:00:14 +0200 Subject: [PATCH 5/6] remove unused code --- libsql.go | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/libsql.go b/libsql.go index 9364c41..b860427 100644 --- a/libsql.go +++ b/libsql.go @@ -424,20 +424,6 @@ type conn struct { nativePtr C.libsql_connection_t } -type extension struct { - Library string - Entry string -} - -func (c *conn) loadExtensions(exts []extension) error { - for _, ext := range exts { - if err := c.loadExtension(ext.Library, ext.Entry); err != nil { - return err - } - } - return nil -} - func (c *conn) LoadExtension(lib string, entry string) error { if err := c.loadExtension(lib, entry); err != nil { return err From cee46f4b7c253e1856d3bcfa94da1445f9972254 Mon Sep 17 00:00:00 2001 From: davidabram Date: Sat, 26 Jul 2025 01:12:46 +0200 Subject: [PATCH 6/6] refactored LoadExtension --- .github/workflows/test.yml | 6 +++--- libsql.go | 12 +++++------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7d814bb..fb2fe0a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -57,11 +57,11 @@ jobs: - name: Set extension env run: | case "${{ runner.os }}" in - Linux) ext=bin/sqlean.so ;; - macOS) ext=bin/sqlean.dylib ;; + Linux) ext=sqlean.so ;; + macOS) ext=sqlean.dylib ;; esac echo "LIBSQL_TEST_EXTENSION=$PWD/$ext" >> $GITHUB_ENV - echo "LIBSQL_TEST_EXTENSION_ENTRY=sqlite3_extension_init" >> $GITHUB_ENV + echo "LIBSQL_TEST_EXTENSION_ENTRY=sqlite3_sqlean_init" >> $GITHUB_ENV - name: Build run: go build -v ./... diff --git a/libsql.go b/libsql.go index b860427..252e8c8 100644 --- a/libsql.go +++ b/libsql.go @@ -425,15 +425,13 @@ type conn struct { } func (c *conn) LoadExtension(lib string, entry string) error { - if err := c.loadExtension(lib, entry); err != nil { - return err - } - return nil -} + libCString := C.CString(lib) + defer C.free(unsafe.Pointer(libCString)) + entryCString := C.CString(entry) + defer C.free(unsafe.Pointer(entryCString)) -func (c *conn) loadExtension(lib string, entry string) error { var errMsg *C.char - statusCode := C.libsql_load_extension(c.nativePtr, C.CString(lib), C.CString(entry), nil) + statusCode := C.libsql_load_extension(c.nativePtr, libCString, entryCString, &errMsg) if statusCode != 0 { return libsqlError(fmt.Sprintf("failed to load extension %s with entry point %s", lib, entry), statusCode, errMsg) }