Skip to content

Commit

Permalink
Enable the load extension API (#39)
Browse files Browse the repository at this point in the history
Expose the C API version of the enable load extension functionality.
The SQL version is not enabled when calling EnableLoadExtension().
  • Loading branch information
ksshannon authored and crawshaw committed Aug 25, 2018
1 parent 9079bee commit ac13ea4
Show file tree
Hide file tree
Showing 3 changed files with 802 additions and 0 deletions.
57 changes: 57 additions & 0 deletions extension.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (c) 2018 David Crawshaw <david@zentus.com>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

package sqlite

import "unsafe"

// #include <stdlib.h>
// #include <sqlite3.h>
// static int db_config_onoff(sqlite3* db, int op, int onoff) {
// return sqlite3_db_config(db, op, onoff);
// }
import "C"

const (
SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION = C.int(C.SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION)
)

// EnableLoadExtension allows extensions to be loaded via LoadExtension(). The
// SQL interface is left disabled as recommended.
// https://www.sqlite.org/c3ref/enable_load_extension.html
func (conn *Conn) EnableLoadExtension(on bool) error {
var enable C.int
if on {
enable = 1
}
res := C.db_config_onoff(conn.conn, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, enable)
return reserr("Conn.EnableLoadExtension", "", "", res)
}

// LoadExtension attempts to load a runtime-loadable extension.
// https://www.sqlite.org/c3ref/load_extension.html
func (conn *Conn) LoadExtension(ext, entry string) error {
cext := C.CString(ext)
defer C.free(unsafe.Pointer(cext))
var centry *C.char
if entry != "" {
centry = C.CString(entry)
defer C.free(unsafe.Pointer(centry))
}
var cerr *C.char
res := C.sqlite3_load_extension(conn.conn, cext, centry, &cerr)
err := C.GoString(cerr)
C.sqlite3_free(unsafe.Pointer(cerr))
return reserr("Conn.LoadExtension", "", err, res)
}
130 changes: 130 additions & 0 deletions extension_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Copyright (c) 2018 David Crawshaw <david@zentus.com>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

package sqlite_test

import (
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"

"crawshaw.io/sqlite"
)

const (
extCode = `
#include "sqlite3ext.h"
SQLITE_EXTENSION_INIT1
#include <stdlib.h>
static void hellofunc(
sqlite3_context *context,
int argc,
sqlite3_value **argv){
(void)argc;
(void)argv;
sqlite3_result_text(context, "Hello, World!", -1, SQLITE_STATIC);
}
#ifdef _WIN32
__declspec(dllexport)
#endif
int sqlite3_hello_init(
sqlite3 *db,
char **pzErrMsg,
const sqlite3_api_routines *pApi
){
int rc = SQLITE_OK;
SQLITE_EXTENSION_INIT2(pApi);
(void)pzErrMsg; /* Unused parameter */
return sqlite3_create_function(db, "hello", 0, SQLITE_UTF8, 0, hellofunc, 0, 0);
}`
)

func TestLoadExtension(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "sqlite")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)
fout, err := os.Create(filepath.Join(tmpdir, "ext.c"))
if err != nil {
t.Fatal(err)
}
if _, err = io.Copy(fout, strings.NewReader(extCode)); err != nil {
t.Fatal(err)
}
if err = fout.Close(); err != nil {
t.Error(err)
}
include, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
var args []string
switch runtime.GOOS {
// See https://www.sqlite.org/loadext.html#build
case "darwin":
args = []string{"gcc", "-g", "-fPIC", "-I" + include, "-dynamiclib", "ext.c", "-o", "libhello.dylib"}
case "linux":
args = []string{"gcc", "-g", "-fPIC", "-I" + include, "-shared", "ext.c", "-o", "libhello.so"}
case "windows":
// TODO: add windows support
fallthrough
default:
t.Skipf("unsupported OS: %s", runtime.GOOS)
}
cmd := exec.Command(args[0], args[1:]...)
cmd.Dir = tmpdir
out, err := cmd.CombinedOutput()
if err != nil {
t.Skipf("no gcc support: %s, %s", string(out), err)
}
c, err := sqlite.OpenConn(":memory:", 0)
if err != nil {
t.Fatal(err)
}
defer func() {
err := c.Close()
if err != nil {
t.Error(err)
}
}()
libPath := filepath.Join(tmpdir, args[len(args)-1])
err = c.LoadExtension(libPath, "")
if err == nil {
t.Error("loaded extension without enabling load extension")
}
err = c.EnableLoadExtension(true)
if err != nil {
t.Fatal(err)
}
err = c.LoadExtension(libPath, "")
if err != nil {
t.Fatal(err)
}
stmt := c.Prep("SELECT hello();")
if _, err := stmt.Step(); err != nil {
t.Fatal(err)
}
if got, want := stmt.ColumnText(0), "Hello, World!"; got != want {
t.Error("failed to load extension, got: %s, want: %s", got, want)
}
}
Loading

0 comments on commit ac13ea4

Please sign in to comment.