1
0
Fork
You've already forked nabla
0
No description
  • C 71.6%
  • Odin 28%
  • Shell 0.4%
2026年06月14日 18:14:05 -03:00
examples feat(eventsource): add streaming SSE client 2026年06月14日 18:14:05 -03:00
pkg feat(eventsource): add streaming SSE client 2026年06月14日 18:14:05 -03:00
scripts refactor(sqlite3): vendor libsqlite3.so alongside the binding (drop system:sqlite3) 2026年06月13日 16:32:40 -03:00
vendor/http feat(eventsource): add streaming SSE client 2026年06月14日 18:14:05 -03:00
.gitignore refactor: move database and fff under pkg/ for pkg: collection imports 2026年06月13日 18:40:49 -03:00
AGENTS.md docs(agents): clarify validation, compatibility, and dependency rules 2026年06月14日 00:08:53 -03:00
odinfmt.json chore: add ols and odinfmt config 2026年06月12日 22:32:32 -03:00
ols.json chore(ols): fix checker_args array to string 2026年06月13日 20:59:42 -03:00
README.md refactor: move SQL packages from db/ to pkg/database/ 2026年06月13日 14:19:16 -03:00

Odin SQL runtime

Experimental Odin-native SQL runtime inspired by Go database/sql public concepts. It is not a line-by-line port.

Package layout

pkg/database/sqlite3
 Raw SQLite C API binding.
pkg/database/sql
 Generic SQL runtime abstraction.
pkg/database/drivers
 SQLite driver adapter from pkg/database/sqlite3 to pkg/database/sql.

Design notes

  • pkg/database/sql is database-agnostic.
  • pkg/database/sql does not know about SQLite or any other concrete database.
  • Concrete drivers live in their own packages and expose sql.Driver descriptors.
  • Open databases with explicit driver descriptors, not string registration.
  • DB is a pool, not a single connection.
  • Pool v1 returns Busy when no connection is available instead of blocking.
  • close(db) returns Busy if there are still active connections pinned by open Rows, Stmt, or Tx handles.
  • Core runtime intentionally avoids reflection and struct scanning magic.
  • Use explicit getters: get_i64, get_string, get_bool, get_f64, get_bytes, is_null.
  • Rows, Stmt, and Tx are transient value handles returned by value.
  • Rows, Stmt, and Tx must not be copied after initialization.
  • rows_destroy, stmt_destroy, and tx_destroy were removed from the public API.
  • get_string and get_bytes copy values into the caller allocator.
  • SQLite is the first supported driver.
  • Code generation/sqlc-like tooling is out of scope for this implementation.

Canonical usage

importsql"pkg/database/sql"importsqlite"pkg/database/drivers"db,err:=sql.open(sqlite.Driver,"file:nabla.db")iferr.kind!=.None{// handle error
}defersql.close(db)

Opening and pool defaults

sql.open now takes a sql.Driver descriptor:

open::proc(driver:Driver,dsn:string,allocator:=context.allocator)->(^DB,Error)

The Driver descriptor carries:

  • name
  • data
  • open
  • close
  • default_max_open_conns
  • default_max_idle_conns

Each concrete driver can provide its own conservative defaults. SQLite currently uses:

  • max_open_conns = 1
  • max_idle_conns = 1

Pool settings can still be overridden after open:

sql.set_max_open_conns(db,1)sql.set_max_idle_conns(db,1)st:=sql.stats(db)

Pool notes:

  • set_max_open_conns(db, 0) means no open-connection limit.
  • set_max_idle_conns(db, 0) means released connections are not kept idle in the pool.

Parameters

SQL parameters use explicit sql.Value constructors:

sql.null()sql.bool(true)sql.int(123)sql.float(7.25)sql.text("hello")sql.bytes([]u8{1,2,3})

Value.text and Value.bytes are borrowed input values. Drivers should copy/bind transiently unless documented otherwise. The SQLite driver uses transient/copy binding semantics for text/blob values.

Querying rows

query returns a transient Rows value handle:

rows,err:=sql.query(db,"select id, name from users order by id",nil)iferr.kind!=.None{// handle error
}defersql.rows_close(&rows)forsql.rows_next(&rows){id,_:=sql.get_i64(&rows,0)name,_:=sql.get_string(&rows,1,context.allocator)_=id_=name}err=sql.rows_err(&rows)iferr.kind!=.None{// handle error
}

Available row helpers:

  • columns
  • rows_next
  • rows_close
  • rows_err
  • is_null
  • get_i64
  • get_f64
  • get_bool
  • get_string
  • get_bytes

columns(&rows, allocator) returns column names owned by the caller allocator.

Prepared statements

prepare returns a transient Stmt value handle:

stmt,err:=sql.prepare(db,"select id from users where name = ?")iferr.kind!=.None{// handle error
}defersql.stmt_close(&stmt)rows,err:=sql.stmt_query(&stmt,[]sql.Value{sql.text("Matheus")})iferr.kind!=.None{// handle error
}defersql.rows_close(&rows)

Statement lifecycle rules:

  • Stmt pins a connection until stmt_close(&stmt).
  • stmt_query(&stmt, ...) marks the statement busy.
  • Rows from stmt_query must be closed before the statement can be reused or closed.
  • stmt_exec and stmt_query reject busy statements with Busy.

Transactions

begin returns a transient Tx value handle:

tx,err:=sql.begin(db)iferr.kind!=.None{// handle error
}defersql.rollback_if_open(&tx)_,err=sql.tx_exec(&tx,"insert into users(name) values(?)",[]sql.Value{sql.text("Commit")})iferr.kind!=.None{// handle error
}err=sql.commit(&tx)iferr.kind!=.None{// handle error
}

Transaction lifecycle rules:

  • Tx pins a connection until commit(&tx) or rollback(&tx).
  • Rows opened via tx_query(&tx, ...) must be closed before commit/rollback.
  • commit and rollback return Busy if transaction rows are still open.

Connection pinning rules

  • Rows from query pin a connection until rows_close(&rows).
  • Stmt pins a connection until stmt_close(&stmt).
  • Rows from stmt_query must be closed before statement reuse.
  • Tx pins a connection until commit(&tx) or rollback(&tx).
  • Rows inside a Tx must be closed before commit/rollback.

SQLite driver behavior

pkg/database/drivers exposes:

Driver::sql.Driver

Normal SQLite usage does not require global registration.

Current SQLite behavior:

  • opens read-write/create/URI
  • sets busy_timeout = 5000ms
  • enables PRAGMA foreign_keys = ON
  • uses conservative pool defaults: 1 / 1
  • preserves Rows/Stmt/Tx value-handle semantics

Testing

  • pkg/database/sql has fake driver tests to validate the generic runtime independently from SQLite.
  • pkg/database/drivers keeps SQLite-specific lifecycle tests.

Non-goals for this implementation

Not included:

  • sqlc/codegen
  • migrations
  • PostgreSQL/MySQL drivers
  • reflection scanning
  • high-level pkg/database/sqlite package
  • blocking pool waits
  • statement cache
  • named parameters
  • async cancellation

API is experimental and expected to change.