Files
sqlite3/module.go

347 lines
8.1 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-02-23 14:13:32 +00:00
"crypto/rand"
2023-03-06 17:51:25 +00:00
"math"
2023-01-12 13:43:35 +00:00
"os"
2023-02-23 14:13:32 +00:00
"runtime"
2023-01-28 12:47:39 +00:00
"strconv"
2023-01-12 13:43:35 +00:00
"sync"
"sync/atomic"
"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-01-28 12:47:39 +00:00
once sync.Once
runtime wazero.Runtime
compiled wazero.CompiledModule
instances atomic.Uint64
err error
}
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-03-06 12:22:17 +00:00
name := "sqlite3-" + strconv.FormatUint(sqlite3.instances.Add(1), 10)
cfg := wazero.NewModuleConfig().WithName(name).
2023-02-23 14:13:32 +00:00
WithSysWalltime().WithSysNanotime().WithSysNanosleep().
WithOsyield(runtime.Gosched).
WithRandSource(rand.Reader)
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)
vfsInstantiate(ctx, sqlite3.runtime)
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-06 18:28:50 +00:00
sqlite3.err = 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
mem memory
api sqliteAPI
}
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{}
m.mem = memory{mod}
m.ctx = context.Background()
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 {
err = noFuncErr + errorString(name)
return nil
}
return f
}
getVal := func(name string) uint32 {
2023-03-06 23:41:54 +00:00
global := mod.ExportedGlobal(name)
2023-03-06 18:28:50 +00:00
if global == nil {
err = noGlobalErr + errorString(name)
return 0
}
return m.mem.readUint32(uint32(global.Get()))
}
m.api = sqliteAPI{
free: getFun("free"),
malloc: getFun("malloc"),
destructor: uint64(getVal("malloc_destructor")),
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 {
m = nil
}
return
}
2023-03-06 23:41:54 +00:00
func (m *module) close() error {
return m.mem.mod.Close(m.ctx)
}
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 {
panic(oomErr)
}
var r []uint64
2023-03-06 18:28:50 +00:00
r, _ = m.api.errstr.Call(m.ctx, rc)
2023-03-06 17:51:25 +00:00
if r != nil {
2023-03-06 18:28:50 +00:00
err.str = m.mem.readString(uint32(r[0]), _MAX_STRING)
2023-03-06 17:51:25 +00:00
}
2023-03-06 18:28:50 +00:00
r, _ = m.api.errmsg.Call(m.ctx, uint64(handle))
2023-03-06 17:51:25 +00:00
if r != nil {
2023-03-06 18:28:50 +00:00
err.msg = m.mem.readString(uint32(r[0]), _MAX_STRING)
2023-03-06 17:51:25 +00:00
}
if sql != nil {
2023-03-06 18:28:50 +00:00
r, _ = m.api.erroff.Call(m.ctx, 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 {
r, err := fn.Call(m.ctx, params...)
if err != nil {
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 {
panic(oomErr)
}
r := m.call(m.api.malloc, size)
ptr := uint32(r[0])
if ptr == 0 && size != 0 {
panic(oomErr)
}
return ptr
}
func (m *module) newBytes(b []byte) uint32 {
if b == nil {
return 0
}
ptr := m.new(uint64(len(b)))
m.mem.writeBytes(ptr, b)
return ptr
}
func (m *module) newString(s string) uint32 {
ptr := m.new(uint64(len(s) + 1))
m.mem.writeString(ptr, s)
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
base uint32
next uint32
size uint32
ptrs []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
}
func (a *arena) string(s string) uint32 {
ptr := a.new(uint64(len(s) + 1))
a.m.mem.writeString(ptr, s)
return ptr
}
2023-03-06 18:28:50 +00:00
type sqliteAPI struct {
free api.Function
malloc api.Function
destructor uint64
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
interrupt uint32
}