A Go module that brings SQLite to your Starlark scripts. It offers both low-level SQL execution and high-level table/record helpers, with parameterized queries, transactions, prepared statements, schema introspection, and custom SQL functions β pure Go, no cgo, all platforms.
- Low-level SQL β
execute,query,query_one, andbatch(multi-statement transactions). - High-level helpers β
create_table,insert,insert_many,update,upsert,delete,select,count, and friends. - Transactions β
beginreturns a transaction whose methods yieldOperationResultobjects for graceful error handling. - Prepared statements β
prepare/prepare_queryfor repeated execution. - Custom SQL functions β
register_functionruns Starlark logic inside SQL queries. - Local & remote β local files / in-memory via
connect; remote libSQL (self-hostedsqldor Turso Cloud) viaconnect_remote, exposing the same object surface. - Safe by default β SQL-injection-resistant parameter binding, automatic SQLite β Starlark type conversion, plus opt-in host hardening (
max_rows,NewModuleWithFileAccess).
For the complete per-builtin reference β signatures, parameters, returns, errors, examples β and the configuration accessors, see docs/API.md.
go get github.com/starpkg/sqliteWire the module into a Starlet interpreter, then load("sqlite", β¦) from a
script:
package main
import (
"fmt"
"github.com/1set/starlet"
"github.com/starpkg/sqlite"
)
func main() {
sqliteModule := sqlite.NewModule()
interpreter := starlet.New(
starlet.WithModuleLoader("sqlite", sqliteModule.LoadModule()),
)
script := `
load("sqlite", "connect")
# Connect to an in-memory database
db = connect(":memory:")
# Create a table and insert with the high-level API
db.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, score INTEGER)")
db.insert("users", {"name": "Alice", "score": 95})
# Query it back
for user in db.query("SELECT name, score FROM users"):
print(user["name"], user["score"])
db.close()
`
if err := interpreter.ExecScript("example.star", script); err != nil {
fmt.Println("Error:", err)
}
}Register a custom SQL function (before opening any connection) and call it from SQL:
load("sqlite", "connect", "register_function")
# Use unique names to avoid clashes; register BEFORE connecting
register_function("DOUBLE_SCORE", lambda x: x * 2 if x else 0, num_args=1, deterministic=True)
db = connect(":memory:")
db.execute("CREATE TABLE users (name TEXT, score INTEGER)")
db.insert("users", {"name": "Alice", "score": 95})
rows = db.query("SELECT name, DOUBLE_SCORE(score) AS doubled FROM users")
print(rows) # [{"name": "Alice", "doubled": 190}]
db.close()Top-level builtins (load("sqlite", β¦)):
connect(database?, timeout?, busy_timeout?, foreign_keys?, journal_mode?, synchronous?, cache_size?)β open a local connection.connect_remote(url, auth_token?)β open a remote libSQL connection (same object API asconnect).register_function(name, func, num_args?, deterministic?)β register a Starlark-backed SQL function (before connecting).
Connection object methods:
execute(query, params?)β run a statement; returns affected rows.query(query, params?)β run a query; returns a list of row dicts.query_one(query, params?)β return the first row dict, orNone.batch(queries)β run many statements in one transaction.prepare(query)/prepare_query(query)β prepared statement / query objects.begin()β start a transaction (commit/rollback, result-object methods).create_table(table, columns, constraints?, indexes?, exist_ok?)β create a table.drop_table(table)/truncate_table(table)β drop a table / delete all its rows.table_exists(table)β whether a table exists.insert(table, values)β insert one record; returns last insert ID.insert_many(table, values_list)β insert many records in one transaction.update(table, values, where?)β update records.upsert(table, values, conflict_columns)β insert or update on conflict.delete(table, where?)β delete records.select(table, columns?, where?, order_by?, limit?, offset?)β filtered/sorted read (order_byis whitelist-validated, not parameterized).count(table, where?)β count matching records.attach(database, alias)/detach(alias)β multi-database operations.tables()/table_info(table)/indices(table)β schema introspection.close()β close the connection.
Prepared statement / query objects expose execute(params?), query(params?),
query_one(params?), and close(). Transaction objects expose execute,
query, query_one, commit, and rollback.
See docs/API.md for the full signatures, return values, errors, and examples of every builtin and method above.
The module's options (database, timeout, busy_timeout, foreign_keys,
journal_mode, synchronous, cache_size, max_rows) are configured via
environment variables (SQLITE_*) or per-option get_<key> / set_<key>
accessor builtins, and serve as defaults for connect / connect_remote. Two
opt-in host levers (max_rows and NewModuleWithFileAccess) bound what an
untrusted script can reach; both default to off. See the
Configuration section of docs/API.md for the full
option table, defaults, accessors, and host-hardening details.
MIT