Files
sqlite3/module.go

361 lines
8.4 KiB
Go
Raw Normal View History

2023-03-06 18:28:50 +00:00
// Package sqlite3 wraps the C SQLite API.
2023-01-12 13:43:35 +00:00
package sqlite3
import (
"context"
2023-03-07 03:51:07 +00:00
"io"
2023-03-06 17:51:25 +00:00
"math"
2023-01-12 13:43:35 +00:00
"os"
"sync"
2023-03-29 15:01:25 +01:00
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/internal/vfs"
2023-01-12 13:43:35 +00:00
"github.com/tetratelabs/wazero"
2023-01-28 12:47:39 +00:00
"github.com/tetratelabs/wazero/api"
2023-01-12 13:43:35 +00:00
)
2023-03-01 10:34:08 +00:00
// Configure SQLite WASM.
//
// Importing package embed initializes these
// with an appropriate build of SQLite:
//
// import _ "github.com/ncruces/go-sqlite3/embed"
2023-01-12 13:43:35 +00:00
var (
2023-03-01 10:34:08 +00:00
Binary []byte // WASM binary to load.
2023-01-12 13:43:35 +00:00
Path string // Path to load the binary from.
)
2023-03-06 18:28:50 +00:00
var sqlite3 struct {
2023-03-24 21:17:30 +00:00
runtime wazero.Runtime
compiled wazero.CompiledModule
err error
2023-04-11 15:33:38 +01:00
once sync.Once
2023-01-28 12:47:39 +00:00
}
2023-01-12 13:43:35 +00:00
2023-03-06 18:28:50 +00:00
func instantiateModule() (*module, error) {
2023-03-06 12:22:17 +00:00
ctx := context.Background()
2023-03-06 18:28:50 +00:00
sqlite3.once.Do(compileModule)
2023-03-06 12:22:17 +00:00
if sqlite3.err != nil {
return nil, sqlite3.err
2023-01-28 12:47:39 +00:00
}
2023-01-18 01:30:11 +00:00
2023-04-04 18:32:56 +01:00
cfg := wazero.NewModuleConfig()
2023-03-06 12:22:17 +00:00
mod, err := sqlite3.runtime.InstantiateModule(ctx, sqlite3.compiled, cfg)
if err != nil {
return nil, err
}
2023-03-06 18:28:50 +00:00
return newModule(mod)
2023-01-28 12:47:39 +00:00
}
2023-03-06 18:28:50 +00:00
func compileModule() {
ctx := context.Background()
sqlite3.runtime = wazero.NewRuntime(ctx)
2023-04-22 00:15:44 +01:00
env := vfs.Export(sqlite3.runtime.NewHostModuleBuilder("env"))
_, sqlite3.err = env.Instantiate(ctx)
if sqlite3.err != nil {
return
}
2023-01-12 13:43:35 +00:00
2023-01-28 12:47:39 +00:00
bin := Binary
if bin == nil && Path != "" {
2023-03-06 18:28:50 +00:00
bin, sqlite3.err = os.ReadFile(Path)
if sqlite3.err != nil {
2023-01-28 12:47:39 +00:00
return
2023-01-12 13:43:35 +00:00
}
}
2023-02-20 13:38:03 +00:00
if bin == nil {
2023-03-29 15:01:25 +01:00
sqlite3.err = util.BinaryErr
2023-02-20 13:38:03 +00:00
return
}
2023-01-12 13:43:35 +00:00
2023-03-06 18:28:50 +00:00
sqlite3.compiled, sqlite3.err = sqlite3.runtime.CompileModule(ctx, bin)
2023-01-12 13:43:35 +00:00
}
2023-03-06 12:22:17 +00:00
type module struct {
ctx context.Context
2023-03-29 15:01:25 +01:00
mod api.Module
2023-03-07 03:51:07 +00:00
vfs io.Closer
2023-04-11 15:33:38 +01:00
api sqliteAPI
arg []uint64
2023-03-06 12:22:17 +00:00
}
2023-03-06 17:51:25 +00:00
2023-03-06 18:28:50 +00:00
func newModule(mod api.Module) (m *module, err error) {
2023-03-06 23:41:54 +00:00
m = &module{}
2023-03-29 15:01:25 +01:00
m.mod = mod
m.ctx, m.vfs = vfs.Context(context.Background())
2023-03-06 23:41:54 +00:00
2023-03-06 18:28:50 +00:00
getFun := func(name string) api.Function {
2023-03-06 23:41:54 +00:00
f := mod.ExportedFunction(name)
2023-03-06 18:28:50 +00:00
if f == nil {
2023-03-29 15:01:25 +01:00
err = util.NoFuncErr + util.ErrorString(name)
2023-03-06 18:28:50 +00:00
return nil
}
return f
}
getVal := func(name string) uint32 {
2023-04-11 15:33:38 +01:00
g := mod.ExportedGlobal(name)
if g == nil {
2023-03-29 15:01:25 +01:00
err = util.NoGlobalErr + util.ErrorString(name)
2023-03-06 18:28:50 +00:00
return 0
}
2023-04-11 15:33:38 +01:00
return util.ReadUint32(mod, uint32(g.Get()))
2023-03-06 18:28:50 +00:00
}
m.api = sqliteAPI{
free: getFun("free"),
malloc: getFun("malloc"),
2023-04-11 15:33:38 +01:00
destructor: getVal("malloc_destructor"),
2023-03-06 18:28:50 +00:00
errcode: getFun("sqlite3_errcode"),
errstr: getFun("sqlite3_errstr"),
errmsg: getFun("sqlite3_errmsg"),
erroff: getFun("sqlite3_error_offset"),
open: getFun("sqlite3_open_v2"),
close: getFun("sqlite3_close"),
closeZombie: getFun("sqlite3_close_v2"),
prepare: getFun("sqlite3_prepare_v3"),
finalize: getFun("sqlite3_finalize"),
reset: getFun("sqlite3_reset"),
step: getFun("sqlite3_step"),
exec: getFun("sqlite3_exec"),
clearBindings: getFun("sqlite3_clear_bindings"),
bindCount: getFun("sqlite3_bind_parameter_count"),
bindIndex: getFun("sqlite3_bind_parameter_index"),
bindName: getFun("sqlite3_bind_parameter_name"),
bindNull: getFun("sqlite3_bind_null"),
bindInteger: getFun("sqlite3_bind_int64"),
bindFloat: getFun("sqlite3_bind_double"),
bindText: getFun("sqlite3_bind_text64"),
bindBlob: getFun("sqlite3_bind_blob64"),
bindZeroBlob: getFun("sqlite3_bind_zeroblob64"),
columnCount: getFun("sqlite3_column_count"),
columnName: getFun("sqlite3_column_name"),
columnType: getFun("sqlite3_column_type"),
columnInteger: getFun("sqlite3_column_int64"),
columnFloat: getFun("sqlite3_column_double"),
columnText: getFun("sqlite3_column_text"),
columnBlob: getFun("sqlite3_column_blob"),
columnBytes: getFun("sqlite3_column_bytes"),
autocommit: getFun("sqlite3_get_autocommit"),
lastRowid: getFun("sqlite3_last_insert_rowid"),
changes: getFun("sqlite3_changes64"),
blobOpen: getFun("sqlite3_blob_open"),
blobClose: getFun("sqlite3_blob_close"),
blobReopen: getFun("sqlite3_blob_reopen"),
blobBytes: getFun("sqlite3_blob_bytes"),
blobRead: getFun("sqlite3_blob_read"),
blobWrite: getFun("sqlite3_blob_write"),
backupInit: getFun("sqlite3_backup_init"),
backupStep: getFun("sqlite3_backup_step"),
backupFinish: getFun("sqlite3_backup_finish"),
backupRemaining: getFun("sqlite3_backup_remaining"),
backupPageCount: getFun("sqlite3_backup_pagecount"),
interrupt: getVal("sqlite3_interrupt_offset"),
}
if err != nil {
2023-03-07 03:51:07 +00:00
return nil, err
2023-03-06 18:28:50 +00:00
}
2023-03-07 03:51:07 +00:00
return m, nil
2023-03-06 18:28:50 +00:00
}
2023-03-06 23:41:54 +00:00
func (m *module) close() error {
2023-03-29 15:01:25 +01:00
err := m.mod.Close(m.ctx)
2023-03-07 03:51:07 +00:00
m.vfs.Close()
return err
2023-03-06 23:41:54 +00:00
}
2023-03-06 18:28:50 +00:00
func (m *module) error(rc uint64, handle uint32, sql ...string) error {
2023-03-06 17:51:25 +00:00
if rc == _OK {
return nil
}
err := Error{code: rc}
if err.Code() == NOMEM || err.ExtendedCode() == IOERR_NOMEM {
2023-03-29 15:01:25 +01:00
panic(util.OOMErr)
2023-03-06 17:51:25 +00:00
}
var r []uint64
2023-03-07 03:51:07 +00:00
r = m.call(m.api.errstr, rc)
2023-03-06 17:51:25 +00:00
if r != nil {
2023-03-29 15:01:25 +01:00
err.str = util.ReadString(m.mod, uint32(r[0]), _MAX_STRING)
2023-03-06 17:51:25 +00:00
}
2023-03-07 03:51:07 +00:00
r = m.call(m.api.errmsg, uint64(handle))
2023-03-06 17:51:25 +00:00
if r != nil {
2023-03-29 15:01:25 +01:00
err.msg = util.ReadString(m.mod, uint32(r[0]), _MAX_STRING)
2023-03-06 17:51:25 +00:00
}
if sql != nil {
2023-03-07 03:51:07 +00:00
r = m.call(m.api.erroff, uint64(handle))
2023-03-06 17:51:25 +00:00
if r != nil && r[0] != math.MaxUint32 {
err.sql = sql[0][r[0]:]
}
}
switch err.msg {
case err.str, "not an error":
err.msg = ""
}
return &err
}
2023-03-06 18:28:50 +00:00
2023-03-06 19:03:00 +00:00
func (m *module) call(fn api.Function, params ...uint64) []uint64 {
2023-04-11 15:33:38 +01:00
m.arg = append(m.arg[:0], params...)
r, err := fn.Call(m.ctx, m.arg...)
2023-03-06 19:03:00 +00:00
if err != nil {
2023-03-07 14:19:22 +00:00
// The module closed or panicked; release resources.
2023-03-07 03:51:07 +00:00
m.vfs.Close()
2023-03-06 19:03:00 +00:00
panic(err)
}
return r
}
func (m *module) free(ptr uint32) {
if ptr == 0 {
return
}
m.call(m.api.free, uint64(ptr))
}
func (m *module) new(size uint64) uint32 {
if size > _MAX_ALLOCATION_SIZE {
2023-03-29 15:01:25 +01:00
panic(util.OOMErr)
2023-03-06 19:03:00 +00:00
}
r := m.call(m.api.malloc, size)
ptr := uint32(r[0])
if ptr == 0 && size != 0 {
2023-03-29 15:01:25 +01:00
panic(util.OOMErr)
2023-03-06 19:03:00 +00:00
}
return ptr
}
func (m *module) newBytes(b []byte) uint32 {
if b == nil {
return 0
}
ptr := m.new(uint64(len(b)))
2023-03-29 15:01:25 +01:00
util.WriteBytes(m.mod, ptr, b)
2023-03-06 19:03:00 +00:00
return ptr
}
func (m *module) newString(s string) uint32 {
ptr := m.new(uint64(len(s) + 1))
2023-03-29 15:01:25 +01:00
util.WriteString(m.mod, ptr, s)
2023-03-06 19:03:00 +00:00
return ptr
}
func (m *module) newArena(size uint64) arena {
return arena{
m: m,
base: m.new(size),
size: uint32(size),
}
}
type arena struct {
m *module
2023-04-11 15:33:38 +01:00
ptrs []uint32
2023-03-06 19:03:00 +00:00
base uint32
next uint32
size uint32
}
func (a *arena) free() {
if a.m == nil {
return
}
a.reset()
a.m.free(a.base)
a.m = nil
}
func (a *arena) reset() {
for _, ptr := range a.ptrs {
a.m.free(ptr)
}
a.ptrs = nil
a.next = 0
}
func (a *arena) new(size uint64) uint32 {
if size <= uint64(a.size-a.next) {
ptr := a.base + a.next
a.next += uint32(size)
return ptr
}
ptr := a.m.new(size)
a.ptrs = append(a.ptrs, ptr)
return ptr
}
2023-04-21 13:31:45 +01:00
func (a *arena) bytes(b []byte) uint32 {
if b == nil {
return 0
}
ptr := a.new(uint64(len(b)))
util.WriteBytes(a.m.mod, ptr, b)
return ptr
}
2023-03-06 19:03:00 +00:00
func (a *arena) string(s string) uint32 {
ptr := a.new(uint64(len(s) + 1))
2023-03-29 15:01:25 +01:00
util.WriteString(a.m.mod, ptr, s)
2023-03-06 19:03:00 +00:00
return ptr
}
2023-03-06 18:28:50 +00:00
type sqliteAPI struct {
free api.Function
malloc api.Function
errcode api.Function
errstr api.Function
errmsg api.Function
erroff api.Function
open api.Function
close api.Function
closeZombie api.Function
prepare api.Function
finalize api.Function
reset api.Function
step api.Function
exec api.Function
clearBindings api.Function
bindNull api.Function
bindCount api.Function
bindIndex api.Function
bindName api.Function
bindInteger api.Function
bindFloat api.Function
bindText api.Function
bindBlob api.Function
bindZeroBlob api.Function
columnCount api.Function
columnName api.Function
columnType api.Function
columnInteger api.Function
columnFloat api.Function
columnText api.Function
columnBlob api.Function
columnBytes api.Function
autocommit api.Function
lastRowid api.Function
changes api.Function
blobOpen api.Function
blobClose api.Function
blobReopen api.Function
blobBytes api.Function
blobRead api.Function
blobWrite api.Function
backupInit api.Function
backupStep api.Function
backupFinish api.Function
backupRemaining api.Function
backupPageCount api.Function
2023-04-11 15:33:38 +01:00
destructor uint32
2023-03-06 18:28:50 +00:00
interrupt uint32
}