Skip to content

Repository files navigation

SQLITE.ZIG LOGO

SQLite.zig

Documentation Zig Version GitHub stars GitHub issues GitHub pull requests GitHub last commit License CI Supported Platforms CodeQL Latest Release Sponsor GitHub Sponsors Repo Visitors

A fully native, zero-dependency SQLite-compatible database engine written entirely in Zig.

Documentation | API Reference | Quick Start | Contributing | Security

sqlite.zig is a ground-up reimplementation of the SQLite engine in pure Zig, featuring a complete storage engine with the real on-disk .db file format, a hand-written SQL lexer and parser, a bytecode compiler and virtual machine, WAL and rollback-journal durability modes, and a type-safe comptime query builder (DSL) that stays in sync with raw SQL.

Note

Due to ongoing development, limited documentation is available at muhammad-fiaz.github.io/sqlite.zig. You can also generate it locally with zig build docs.

Tip

If you build with sqlite.zig, make sure to give it a star. ⭐

Warning

This project is in early, active development. Core engine components are being implemented tier by tier. Expect missing features, incomplete SQL coverage, and breaking changes between commits.

Caution

Do not use this in production or on data you cannot afford to lose. There is no stability guarantee on the file format, the API, or correctness of edge cases yet. Back up anything important separately.


Related Projects (click to expand)
  • CUDA / GPU computing — For GPU acceleration and CUDA support, check out cuda.zig.
  • Environment variables — For .env file parsing and environment configuration, check out env.zig.
  • TUI — For terminal user interfaces, check out tui.zig.
  • ZON file format — For ZON parsing and serialization, check out zon.zig.
  • Spinners / loading / progress bars — For terminal loading indicators and progress bars, check out loaders.zig.
  • MCP — For Model Context Protocol support, check out mcp.zig.
  • Argument parsing — For command-line argument parsing, check out args.zig.
  • HTTP client / server — For HTTP client and server functionality, check out httpx.zig.
  • API framework — For building APIs, check out api.zig.
  • Web framework — For web application development, check out zix.
  • Archive / compression — For archive handling and compression, check out archive.zig.
  • Compression file formats — For compression-oriented file format support, check out zigx.
  • File downloading — For downloading files, check out downloader.zig.
  • Update checker / auto-updater — For application update checking and automatic updates, check out updater.zig.
  • Numerical computing — For numerical and scientific computing, check out num.zig.
  • Logging — For structured and application logging, check out logly.zig.
  • Data validation / serialization — For data validation and serialization, check out zigantic.
  • Build tooling — For advanced Zig build tooling, check out buildx.zig.
  • Tree-sitter — For Tree-sitter parsing and syntax-tree support, check out tree-sitter.zig.

Features (click to expand)
Feature Description
Pure Zig Implementation Zero C dependencies, zero link-time requirements. The entire engine is written in Zig from scratch.
Real On-Disk Format Full implementation of the SQLite .db/.sqlite file format including 100-byte header, table/index B-tree pages, record encoding, varints, and freelist pages.
SQL Lexer & Parser Hand-written SQL lexer and parser supporting CREATE TABLE, INSERT, SELECT, UPDATE, DELETE, BEGIN, COMMIT, ROLLBACK, JOINs, subqueries, CTEs, views, triggers, and more.
Bytecode Compiler & VM A bytecode virtual machine that compiles parsed SQL into opcodes and executes them against the storage engine, modeled on SQLite's own architecture.
WAL & Rollback Journal SQLite-compatible WAL page headers/frames, native WAL readback, checkpointing through PRAGMA journal_mode=DELETE, and rollback-journal persistence. Multi-process locking/VFS parity is still in progress.
Typed DSL Query Builder A comptime, type-safe Zig query builder that builds the same internal query representation as Raw SQL directly, ensuring compile-time validation of table names, column names, and types.
DISTINCT Joins Full DISTINCT support for JOIN queries with automatic deduplication of result rows.
Transaction Modes BEGIN DEFERRED, BEGIN IMMEDIATE, BEGIN EXCLUSIVE, START TRANSACTION, COMMIT, ROLLBACK, SAVEPOINT, RELEASE, and ROLLBACK TO SAVEPOINT.
Foreign Key Actions CASCADE DELETE, CASCADE UPDATE, SET NULL, SET DEFAULT, RESTRICT, and NO ACTION (plus composite foreign keys).
Composite Constraints Composite PRIMARY KEY, composite UNIQUE, and composite FOREIGN KEY constraints across multiple columns.
Views & Triggers CREATE VIEW and CREATE TRIGGER (BEFORE/AFTER INSERT/UPDATE/DELETE, WHEN filters) with NEW/OLD references.
CTEs & Recursive CTEs Common Table Expressions including recursive CTEs for hierarchical data traversal (tree/graph structures).
Subqueries EXISTS/NOT EXISTS (incl. correlated), IN/NOT IN (lists and subqueries), scalar subqueries, derived tables in FROM, and CTEs (incl. recursive).
Expression Operators = == != <> < <= > >=, AND OR NOT, LIKE NOT LIKE (% _ ESCAPE), GLOB NOT GLOB (* ? []), REGEXP NOT REGEXP, MATCH NOT MATCH, `
Scalar Functions ABS, LENGTH, UPPER, LOWER, SUBSTR/SUBSTRING, REPLACE, TRIM/LTRIM/RTRIM (incl. custom chars), INSTR, HEX, QUOTE, UNICODE, CHAR, PRINTF/FORMAT, ROUND, TYPEOF, CAST, COALESCE/IFNULL, NULLIF, JSON_EXTRACT/JSON_SET in raw SQL, dynamic DSL, and typed DSL.
UPSERT & RETURNING ON CONFLICT DO NOTHING / DO UPDATE with excluded, partial targets, and RETURNING on INSERT/UPDATE/DELETE across all three interfaces.
Compound SELECT UNION, UNION ALL, INTERSECT, and EXCEPT with duplicate elimination, ORDER BY, and LIMIT/OFFSET.
Multi-Table JOINs 3+ table chains across INNER/LEFT/RIGHT/FULL/CROSS/ON/USING/NATURAL with WHERE, GROUP BY, HAVING, and pagination.
CASE & Windows Simple/searched CASE plus 11 window functions (ROW_NUMBER, RANK, LAG/LEAD, NTILE, FIRST/LAST/NTH_VALUE, …) with PARTITION BY, ORDER BY, and ROWS/RANGE/GROUPS frames.
Constraints CHECK, UNIQUE, NOT NULL, DEFAULT, full SQLite type names, generated columns (VIRTUAL/STORED), STRICT tables, and WITHOUT ROWID tables.
PRAGMAs foreign_keys, user_version, application_id, journal_mode, synchronous, cache_size, integrity_check, and foreign_key_check.
Indexed Queries CREATE INDEX and optimized indexed lookups for performance-critical queries.
Query Planning EXPLAIN QUERY PLAN reports index-backed equality searches and table scans.
Virtual Tables Native generate_series virtual tables support raw creation, typed DSL reads, and native reopen. Other modules return Unsupported.
Prepared Statements Parameterized queries with typed binding and automatic memory management.
Schema Lifecycle CREATE TABLE, ALTER TABLE ADD COLUMN, DROP TABLE with full schema persistence and verification.
On-Disk Format Databases use the real SQLite file format (100-byte header, B-tree pages) and reopen losslessly.
Cross-Platform Runs on Linux, Windows, and macOS with the same source code.

Prerequisites and Supported Platforms (click to expand)

Prerequisites

Before using sqlite.zig, ensure you have the following:

Requirement Version Notes
Zig 0.16.0+ Download from ziglang.org
Operating System Windows 10+, Linux, macOS Cross-platform database engine

Supported Platforms

sqlite.zig is validated on these architectures:

Platform x86_64 (64-bit) aarch64 (ARM64)
Linux Yes Yes
Windows Yes Yes
macOS Yes Yes

Cross-Compilation

Zig makes cross-compilation easy. Build for any target from any host:

# Build for Linux ARM64 from Windows
zig build -Dtarget=aarch64-linux

# Build for Windows from Linux
zig build -Dtarget=x86_64-windows

Installation

Method 1: Zig Fetch (Recommended)

Latest Development Version (main branch)

zig fetch --save git+https://github.com/muhammad-fiaz/sqlite.zig.git

Method 2: Manual build.zig.zon Configuration

Add the dependency to your build.zig.zon file.

.dependencies = .{
    .sqlite = .{
        .url = "https://github.com/muhammad-fiaz/sqlite.zig/archive/refs/heads/main.tar.gz",
        .hash = "...", // Run `zig fetch --save <url>` to generate the hash.
    },
},

Method 3: Local Source Checkout

Clone the repository locally.

git clone https://github.com/muhammad-fiaz/sqlite.zig.git
cd sqlite.zig
zig build

To use a local checkout from another project, add a path dependency to your build.zig.zon:

.dependencies = .{
    .sqlite = .{
        .path = "../sqlite.zig",
    },
},

Wire into build.zig

After adding the dependency, import the module in your build.zig:

const sqliteDep = b.dependency("sqlite", .{
    .target = target,
    .optimize = optimize,
});
exe.root_module.addImport("sqlite", sqliteDep.module("sqlite"));

Quick Start

Basic Database Operations

const std = @import("std");
const sqlite = @import("sqlite");

const User = sqlite.table("users", struct { id: i64, name: []const u8 });

pub fn main() !void {
    var db = try sqlite.open(std.heap.page_allocator, "my_database.db");
    defer db.close();

    try db.createTable(User, .{});

    var inserted = try db.from(User).insert(.{ .id = 1, .name = "Alice" });
    inserted.deinit();

    var result = try db.from(User).fetch();
    defer result.deinit();

    for (result.rows) |row| {
        std.debug.print("User: id={d}, name={s}\n", .{ row.id, row.name });
    }
}

Transactions & Joins

const std = @import("std");
const sqlite = @import("sqlite");

const User = sqlite.table("users", struct { id: i64, name: []const u8 });
const Order = sqlite.table("orders", struct { id: i64, user_id: i64, amount: i64 });

pub fn main() !void {
    var db = try sqlite.open(std.heap.page_allocator, "shop.db");
    defer db.close();

    try db.createTable(User, .{});
    try db.createTable(Order, .{});

    // Transaction with rollback safety
    try db.begin();
    var order = try db.from(Order).insert(.{ .id = 1, .user_id = 1, .amount = 100 });
    order.deinit();
    try db.commit();

    // Typed JOIN query
    var joined = try db.from(User)
        .innerJoin(Order, User.id.eq(Order.user_id))
        .selectAll()
        .distinct()
        .fetch();
    defer joined.deinit();

    std.debug.print("Found {d} rows\n", .{joined.count()});
}

Three interfaces: Raw SQL, Dynamic DSL, Typed DSL

// Raw SQL: unrestricted, no struct required.
var rows = try db.exec("SELECT id, name FROM users WHERE age >= 18;");
defer rows.deinit();

// Dynamic DSL: runtime table/column names, no struct required.
// For existing databases, legacy schemas, and ad-hoc queries.
const users = db.table("users");
var dyn = try db.from(users)
    .select(.{ users.column("id"), users.column("name") })
    .where(users.column("age").gte(18))
    .orderBy(users.column("name").asc())
    .fetch();
defer dyn.deinit();

// Typed DSL: Zig structs, compile-time columns, typed rows.
const User = sqlite.table("users", struct { id: i64, name: []const u8, age: i64 });
try db.schema(User).validate();
var typed = try db.from(User)
    .where(User.age.gte(18))
    .orderBy(User.name.asc())
    .fetch();
defer typed.deinit();
for (typed.rows) |user| {
    std.debug.print("{d} {s} {d}\n", .{ user.id, user.name, user.age });
}

Side by side, one engine:

Raw:     SELECT * FROM users WHERE id = 1;
Dynamic: db.from(users).selectAll().where(users.column("id").eq(1))
Typed:   db.from(User).select(User.all()).where(User.id.eq(1))

The DSL builds the same internal query representation as Raw SQL directly, without generating SQL strings. See docs/api/dsl.md.

users.column("id") is the canonical explicit Dynamic column reference: it carries table identity. db.col("id") is optional sugar for an unqualified reference resolved with SQLite name-resolution rules; ambiguous references are an error, never a silent pick. Typed columns are User.id; User.all() is the typed table.* operation and selectAll() is the SELECT * operation on any query; both build the same native projection node. Schema columns are always fields — even a column literally named all stays usable as User.all — while operations are always calls.

Raw SQL

const std = @import("std");
const sqlite = @import("sqlite");

pub fn main() !void {
    var db = try sqlite.open(std.heap.page_allocator, "raw.db");
    defer db.close();

    var result = try db.exec("CREATE TABLE IF NOT EXISTS items (id INTEGER PRIMARY KEY, name TEXT);");
    result.deinit();

    result = try db.exec("INSERT INTO items VALUES (1, 'Widget');");
    result.deinit();

    var rows = try db.exec("SELECT * FROM items WHERE id = 1;");
    defer rows.deinit();

    std.debug.print("Row count: {d}\n", .{rows.count()});
}

Examples

Run any example:

zig build run-01_open_and_exec
zig build run-03_transactions
zig build run-09_dsl_crud
zig build run-all-examples

Validation & Testing

Run all unit tests across the entire codebase:

zig build test

Generate the API documentation:

zig build docs

Note

The hosted documentation site is not ready yet. Run zig build docs to generate and view the current API docs at zig-out/docs/index.html.

Contributing

This project is being built out tier by tier: file format and storage first, then the B-tree engine, then the SQL front end and planner, then higher-level features (views, triggers, DSL), with advanced extensions (FTS5, JSON1, R-Tree) deferred until the core engine is solid.

Warning

Because the internal architecture is still shifting, expect merge conflicts and API churn if you build against internal modules directly (anything outside src/sqlite.zig). Prefer depending only on the public API surface.

Issues and pull requests are welcome. Please check open issues before starting large changes so effort isn't duplicated.

License

MIT License - Copyright (c) 2026 Muhammad Fiaz

Releases

Sponsor this project

Packages

Used by

Contributors

Languages