Go WASM Driver
Pure Go driver for Stoolap using WebAssembly. No CGO, no shared libraries, no platform-specific binaries. The full Stoolap engine runs as a WASM module inside your Go process via wazero.
For the native CGO driver, see Go Driver.
When to Use
| CGO Driver | WASM Driver | |
|---|---|---|
| Package | github.com/stoolap/stoolap-go |
github.com/stoolap/stoolap-go/wasm |
| CGO required | Yes | No |
| Threading | Full (parallel queries) | Single-threaded |
| File persistence | Full (automatic maintenance) | Works (manual maintenance) |
| Cross-compile | Needs C toolchain per target | Anywhere Go compiles |
| Best for | Production, max throughput | Portability, zero dependencies |
Installation
go get github.com/stoolap/stoolap-go/wasm
A prebuilt stoolap.wasm (5 MB) is included in the module and available on the releases page.
Quick Start
Direct API
package main
import (
"context"
"fmt"
"os"
"github.com/stoolap/stoolap-go/wasm"
)
func main() {
ctx := context.Background()
wasmBytes, _ := os.ReadFile("stoolap.wasm")
engine, _ := wasm.NewEngine(ctx, wasmBytes)
defer engine.Close(ctx)
db, _ := engine.OpenMemory(ctx)
defer db.Close()
db.Exec(ctx, "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)")
db.Exec(ctx, "INSERT INTO users VALUES (1, 'Alice', 30), (2, 'Bob', 25)")
rows, _ := db.Query(ctx, "SELECT id, name, age FROM users ORDER BY id")
defer rows.Close()
for rows.Next() {
var id int64
var name string
var age int64
rows.Scan(&id, &name, &age)
fmt.Printf("id=%d name=%s age=%d\n", id, name, age)
}
}
database/sql Driver
The WASM driver registers as "stoolap-wasm" for use with database/sql. Call SetWASM() once before opening connections:
package main
import (
"context"
"database/sql"
"fmt"
"os"
"github.com/stoolap/stoolap-go/wasm"
)
func main() {
ctx := context.Background()
wasmBytes, _ := os.ReadFile("stoolap.wasm")
wasm.SetWASM(ctx, wasmBytes)
db, _ := sql.Open("stoolap-wasm", "memory://")
defer db.Close()
db.ExecContext(ctx, "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)")
db.ExecContext(ctx, "INSERT INTO users VALUES (1, 'Alice'), (2, 'Bob')")
rows, _ := db.QueryContext(ctx, "SELECT id, name FROM users ORDER BY id")
defer rows.Close()
for rows.Next() {
var id int64
var name string
rows.Scan(&id, &name)
fmt.Printf("id=%d name=%s\n", id, name)
}
}
Connection Strings
| DSN | Description |
|---|---|
memory:// |
In-memory database (unique, isolated instance) |
memory://mydb |
Named in-memory database (same name shares the engine) |
file:///data/mydb |
File-based persistent database (requires NewEngineWithFS) |
See Connection String Reference for all options.
Parameters
Use positional parameters $1, $2, etc.:
// Direct API
db.ExecParams(ctx, "INSERT INTO users VALUES ($1, $2)", []any{int64(1), "Alice"})
rows, _ := db.QueryParams(ctx, "SELECT * FROM users WHERE id = $1", []any{int64(1)})
// database/sql
db.ExecContext(ctx, "INSERT INTO users VALUES ($1, $2)", 1, "Alice")
rows, _ := db.QueryContext(ctx, "SELECT * FROM users WHERE id = $1", 1)
Transactions
// Direct API
tx, _ := db.Begin(ctx)
tx.Exec(ctx, "INSERT INTO users VALUES (1, 'Alice')")
tx.Exec(ctx, "INSERT INTO users VALUES (2, 'Bob')")
tx.Commit()
// Snapshot isolation
tx, _ := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSnapshot})
rows, _ := tx.Query(ctx, "SELECT * FROM users")
// All reads see the same consistent snapshot
tx.Commit()
// Rollback
tx, _ := db.Begin(ctx)
tx.Exec(ctx, "DELETE FROM users")
tx.Rollback() // changes discarded
Prepared Statements
stmt, _ := db.Prepare(ctx, "INSERT INTO users VALUES ($1, $2)")
defer stmt.Close()
for i := int64(1); i <= 1000; i++ {
stmt.ExecContext(ctx, []any{i, "User"})
}
File Persistence
Create an engine with filesystem access for persistent databases:
engine, _ := wasm.NewEngineWithFS(ctx, wasmBytes, "/path/to/data")
db, _ := engine.Open(ctx, "file:///data/mydb")
The directory is mounted at /data inside the WASM sandbox. Use file:///data/... paths in the DSN.
Manual Maintenance
WASM does not support background threads, so the automatic checkpoint cycle and cleanup do not run. You must call these commands periodically:
// Checkpoint: seal hot rows to cold volumes, persist manifests, truncate WAL.
// Without this, all data stays in the hot buffer and WAL grows unbounded.
db.Exec(ctx, "PRAGMA checkpoint")
// Cleanup: remove deleted rows, old versions, compact indexes
db.Exec(ctx, "VACUUM")
// Backup: create a full .bin snapshot for disaster recovery
db.Exec(ctx, "PRAGMA snapshot")
// Statistics: update optimizer cost estimates
db.Exec(ctx, "ANALYZE my_table")
Important: PRAGMA checkpoint is the most critical command for file-based WASM databases. Without it, data stays in the hot buffer (high memory) and the WAL grows indefinitely. Call it periodically (e.g., every 60 seconds or after bulk writes).
For production file-based workloads with automatic background maintenance, use the CGO driver.
FetchAll (Bulk Read)
The FetchAll() method retrieves all remaining rows in a single WASM call and returns them as parsed Go values. The database/sql driver uses this automatically.
rows, _ := db.Query(ctx, "SELECT id, name, age FROM users")
defer rows.Close()
allRows, _ := rows.FetchAll()
for _, row := range allRows {
fmt.Printf("id=%v name=%v age=%v\n", row[0], row[1], row[2])
}
Type Mapping
| SQL Type | Go Type | Nullable Go Type |
|---|---|---|
| INTEGER | int64 |
sql.NullInt64 |
| FLOAT | float64 |
sql.NullFloat64 |
| TEXT | string |
sql.NullString |
| BOOLEAN | bool |
sql.NullBool |
| TIMESTAMP | time.Time |
sql.NullTime |
| JSON | string |
sql.NullString |
| VECTOR/BLOB | []byte |
[]byte (nil for NULL) |
Thread Safety
All WASM operations are serialized through a mutex. The engine is safe for concurrent use from multiple goroutines, but operations execute sequentially.
- Engine: Thread-safe (mutex-protected)
- DB, Tx, Stmt, Rows: Use from any goroutine (engine serializes access)
- database/sql: Thread-safe by default (connection pool creates cloned handles)
Building the WASM Binary from Source
Requires: Rust toolchain, wasm32-wasip1 target, and binaryen (for wasm-opt).
# Install WASI target
rustup target add wasm32-wasip1
# Build (from the stoolap engine repo)
cd stoolap
cargo build --profile max --target wasm32-wasip1 --features ffi --no-default-features
# Optimize (31 MB -> 5 MB)
wasm-opt -Oz target/wasm32-wasip1/max/stoolap.wasm -o stoolap.wasm
Performance
The WASM driver uses several optimizations to minimize overhead:
- FetchAll: All result rows are fetched in a single WASM call as packed binary, then parsed in Go. The
database/sqldriver uses this automatically. - CallWithStack: Pre-allocated call stack eliminates per-call allocations.
- Arena allocator: SQL strings and parameters are written to a pre-allocated WASM memory region with zero malloc/free overhead.
Read-heavy workloads (SELECT, aggregation, JOINs) perform close to the native CGO driver. Write-heavy workloads have ~1.5-2x overhead due to WASM boundary crossing per operation.
Direct API Reference
Engine
| Method | Description |
|---|---|
NewEngine(ctx, wasmBytes) |
Create engine for in-memory databases |
NewEngineWithFS(ctx, wasmBytes, rootDir) |
Create engine with filesystem access |
engine.Close(ctx) |
Release all resources |
engine.Version(ctx) |
Get engine version string |
engine.Open(ctx, dsn) |
Open database by DSN |
engine.OpenMemory(ctx) |
Open new in-memory database |
DB
| Method | Description |
|---|---|
db.Close() |
Close the connection |
db.Clone(ctx) |
Clone handle (shares engine) |
db.Exec(ctx, query) |
Execute without parameters |
db.ExecParams(ctx, query, args) |
Execute with parameters |
db.Query(ctx, query) |
Query without parameters |
db.QueryParams(ctx, query, args) |
Query with parameters |
db.Prepare(ctx, query) |
Create prepared statement |
db.Begin(ctx) |
Begin transaction (Read Committed) |
db.BeginTx(ctx, opts) |
Begin transaction with options |
Rows
| Method | Description |
|---|---|
rows.Next() |
Advance to next row |
rows.Scan(dest...) |
Read current row |
rows.Close() |
Close result set |
rows.Columns() |
Get column names |
rows.FetchAll() |
Fetch all rows as [][]any |
Stmt
| Method | Description |
|---|---|
stmt.ExecContext(ctx, args) |
Execute with parameters |
stmt.QueryContext(ctx, args) |
Query with parameters |
stmt.Close() |
Destroy the statement |
Tx
| Method | Description |
|---|---|
tx.Exec(ctx, query) |
Execute within transaction |
tx.ExecParams(ctx, query, args) |
Execute with parameters |
tx.Query(ctx, query) |
Query within transaction |
tx.QueryParams(ctx, query, args) |
Query with parameters |
tx.Commit() |
Commit |
tx.Rollback() |
Rollback |