The first rung of the libraries shelf is the library most applications reach for first: sqlite. The library ships as three files you copy into your own project and use as they stand — sqlite.mica, the library unit; sqlite.external, the contract for the sqlite3 symbols it calls; and sqlite-shim.c, four small C functions. The demo beside them is the library’s first caller.

The library lives at mica-container/libraries/sqlite; make run builds the library and runs the demo.

The daily road

db := Open("notes.db") on fail e do WriteLn("open failed");

Exec(db, "create table m(name text, reading integer)") on fail e do WriteLn("create failed");
Exec(db, "insert into m values ('alpha', 7), ('beta', 35)") on fail e do WriteLn("insert failed");

q := Query(db, "select name, reading from m where reading > ? order by reading") on fail e do WriteLn("query failed");
BindInt64(q, 1, 6) on fail e do WriteLn("bind failed");

for row in Rows(q) do
begin
    WriteLn("%ls = %lld", ColumnText(row, 0), ColumnInt64(row, 1));
end
on fail e do WriteLn("step failed");

Close(db);

Failure travels the failure channel: Open, Query, Exec, the binds, and every step of an open query fail with the library’s own SqliteError domain instead of answering nil — a locked database mid-walk can never look like a clean end of rows — so a caller consumes trouble with the language’s own forms, and ErrCode(db) answers sqlite’s exact numeric reason beside the channel’s named one. The column readers cover integers, text — decoded from sqlite’s utf-8 into a Mica string — the column count, and the column’s type code; Changes and LastInsertRowId answer the connection facts.

Rows is an exported generator. It crosses the library’s contract as verbatim source and compiles inside your program, stepping the query through the library’s exported surface; a defer returns the statement however your loop ends, an early leave included. The handles are borrows by design: sqlite3 owns every database and statement cell, and the borrowed spelling keeps the ownership analysis truthful — your program owes no dispose for what it never owned.

The callback road

sqlite3_exec hands results back through a function pointer — the C world’s callback shape. The contract declares that parameter a callback slot, and Mica code binds it with a declared callback, the third subprogram kind beside task and generator:

type
    Tally = record
        rows : int64;
    end;

callback CountRow(context : pointer Tally, columns : int32, values : pointer int64, names : pointer int64) : int32;
begin
    context.rows := context.rows + 1;
    CountRow := 0;
end;

rc := SqliteExecRaw(db, "select reading from m", CountRow, address tally, address sink);

A callback is never called from Mica and never a value: the library behind the slot performs every firing, while the exec call itself runs. Its whole environment is the context record its userdata pointer carries — everything is visible on the page, and every binding site names its callback statically.

The two patterns this library establishes

  • The out-parameter shim. Mica’s value model has no pointer-to-pointer spelling, so a C entry answering its handle through one gets a two-line C shim answering the handle directly, NULL carrying the failure. The shim also states pointer-valued sentinels no Mica value can spell, and serves byte-wise reads of text the C side owns — and it is exactly those entry points, nothing more.

  • The state-slot generator. A generator’s inputs are plain values, and an exported generator compiles inside the importing program, reaching only the library’s exported surface. So stateful iteration lives behind a slot: Query opens the statement in the library’s own table and answers a plain ordinal, and Rows(q) steps that ordinal through exported functions. The handle never crosses, and the pull road reads exactly as it should.

Building it into your project

The build is two steps — the library once into an archive with its contract emitted, then your program against the emitted contract and the hand-authored one together:

mica --compile --link --output-modifier archive --emit-contract \
    --external-contract sqlite.external --external static \
    --source sqlite.mica --build build/sqlite

mica --compile --link \
    --external-contract build/sqlite/mica.external,sqlite.external --external static \
    --source your-program.mica --build build

Needs libsqlite3-dev, which the container image carries. The mica.project beside the sources is for your editor: the language server reads it to resolve the contracts, so the files analyze clean as you work on them.