Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

76 Commits

Repository files navigation

zig-sqlite

Simple, low-level, explicitly-typed SQLite bindings for Zig.

Table of Contents

Installation

This library is built and tested with Zig version 0.16.0.

zig fetch --save=sqlite \
https://github.com/nDimensional/zig-sqlite/archive/refs/tags/v0.4.0-3530100.tar.gz

Then add sqlite as an import to your root modules in build.zig:

fnbuild(b: *std.Build) void {
constapp=b.addExecutable(.{ ... });
// ...constsqlite=b.dependency("sqlite", .{});
app.root_module.addImport("sqlite", sqlite.module("sqlite"));
}

Usage

Open databases using Database.open and close them with db.close():

constsqlite=@import("sqlite");
{
// in-memory databaseconstdb=trysqlite.Database.open(.{});
deferdb.close();
}
{
// persistent databaseconstdb=trysqlite.Database.open(.{ .path="path/to/db.sqlite" });
deferdb.close();
}

Execute one-off statements using Database.exec:

trydb.exec("CREATE TABLE users (id TEXT PRIMARY KEY, age FLOAT)", .{});

Prepare statements using Database.prepare, and finalize them with stmt.finalize(). Statements must be given explicit comptime params and result types, and are typed as sqlite.Statement(Params, Result).

  • The comptime Params type must be a struct whose fields are (possibly optional) float, integer, sqlite.Blob, or sqlite.Text types.
  • The comptime Result type must either be void, indicating a method that returns no data, or a struct of the same kind as param types, indicating a query that returns rows.

sqlite.Blob and sqlite.Text are wrapper structs with a single field data: []const u8.

Methods

If the Result type is void, use the exec(params: Params): !void method to execute the statement several times with different params.

constUser=struct { id: sqlite.Text, age: ?f32 };
constinsert=trydb.prepare(User, void, "INSERT INTO users VALUES (:id, :age)");
deferinsert.finalize();
tryinsert.exec(.{ .id=sqlite.text("a"), .age=21 });
tryinsert.exec(.{ .id=sqlite.text("b"), .age=null });

Queries

If the Result type is a struct, use stmt.bind(params) in conjunction with defer stmt.reset(), then stmt.step() over the results.

ℹ️ Every bind should be paired with a reset, just like every prepare is paired with a finalize.

constUser=struct { id: sqlite.Text, age: ?f32 };
constselect=trydb.prepare(
struct { min: f32 },
User,
"SELECT * FROM users WHERE age >= :min",
);
deferselect.finalize();
// Get a single row
{
tryselect.bind(.{ .min=0 });
deferselect.reset();
if (tryselect.step()) |user| {
// user.id: sqlite.Text// user.age: ?f32std.log.info("id: {s}, age: {d}", .{ user.id.data, user.ageorelse0 });
}
}
// Iterate over all rows
{
tryselect.bind(.{ .min=0 });
deferselect.reset();
while (tryselect.step()) |user| {
std.log.info("id: {s}, age: {d}", .{ user.id.data, user.ageorelse0 });
}
}
// Iterate again, with different params
{
tryselect.bind(.{ .min=21 });
deferselect.reset();
while (tryselect.step()) |user| {
std.log.info("id: {s}, age: {d}", .{ user.id.data, user.ageorelse0 });
}
}

Text and blob values must not be retained across steps. You are responsible for copying them.

Errors

The basic sqlite errors (SQLITE_ERROR, SQLITE_BUSY, SQLITE_CANTOPEN, etc) are exported as a sqlite.Error error union. If an error has been thrown, you can access a detailed error message with db.errmsg(): ?[*:0]const u8.

Notes

Crafting sensible Zig bindings for SQLite involves making tradeoffs between following the Zig philosophy ("deallocation must succeed") and matching the SQLite API, in which closing databases or finalizing statements may return error codes.

This library takes the following approach:

  • Database.close calls sqlite3_close_v2 and panics if it returns an error code.
  • Statement.finalize calls sqlite3_finalize and panics if it returns an error code.
  • Statement.step automatically calls sqlite3_reset if sqlite3_step returns an error code.
    • In SQLite, sqlite3_reset returns the error code from the most recent call to sqlite3_step. This is handled gracefully.
  • Statement.reset calls both sqlite3_reset and sqlite3_clear_bindings, and panics if either return an error code.

These should only result in panic through gross misuse or in extremely unusual situations, e.g. sqlite3_reset failing internally. All "normal" errors are faithfully surfaced as Zig errors.

Build options

struct {
SQLITE_ENABLE_COLUMN_METADATA: bool=false,
SQLITE_ENABLE_DBSTAT_VTAB: bool=false,
SQLITE_ENABLE_FTS3: bool=false,
SQLITE_ENABLE_FTS4: bool=false,
SQLITE_ENABLE_FTS5: bool=false,
SQLITE_ENABLE_GEOPOLY: bool=false,
SQLITE_ENABLE_ICU: bool=false,
SQLITE_ENABLE_MATH_FUNCTIONS: bool=false,
SQLITE_ENABLE_RBU: bool=false,
SQLITE_ENABLE_RTREE: bool=false,
SQLITE_ENABLE_STAT4: bool=false,
SQLITE_OMIT_DECLTYPE: bool=false,
SQLITE_OMIT_JSON: bool=false,
SQLITE_USE_URI: bool=false,
}

Set these by passing e.g. -DSQLITE_ENABLE_RTREE in the CLI, or by setting .SQLITE_ENABLE_RTREE = true in the args parameter to std.Build.dependency. For example:

pubfnbuild(b: *std.Build) !void {
// ...constsqlite=b.dependency("sqlite", .{ .SQLITE_ENABLE_RTREE=true });
}

License

MIT © nDimensional Studios

About

Simple, low-level, explicitly-typed SQLite bindings for Zig.

Topics

Resources

Stars

52 stars

Watchers

3 watching

Forks

Releases

Contributors

Languages