Skip to content

Multi-Database Driver Migration

Phase 13 of the zypher framework introduces a driver abstraction layer so that the ORM, schema generator, migration runner, and query builder work with any relational database (SQLite, PostgreSQL, MySQL/MariaDB) and optionally with non-relational stores (MongoDB, Redis). After this phase, no source file outside src/orm/driver/ touches a database-specific C API directly.

Invariant

The database driver is always caller-supplied. The RelationalDb, DocumentStore, and KVStore vtable interfaces follow the std.Io / std.mem.Allocator convention: a vtable pointer held by value, no heap allocation for the interface itself. Users select the driver at app-init time via App.useDatabase().

Functions That Gain or Change a Driver Parameter

ORM Core

Module Function Change
query.zig create db: *sqlite.Db → db: RelationalDb
query.zig getById db: *sqlite.Db → db: RelationalDb
query.zig all db: *sqlite.Db → db: RelationalDb
query.zig updateById db: *sqlite.Db → db: RelationalDb
query.zig deleteById db: *sqlite.Db → db: RelationalDb
query.zig order db: *sqlite.Db → db: RelationalDb
query.zig count db: *sqlite.Db → db: RelationalDb
query.zig filter db: *sqlite.Db → db: RelationalDb
query.zig filterLimitOffset db: *sqlite.Db → db: RelationalDb
query.zig filterOrderLimitOffset db: *sqlite.Db → db: RelationalDb
query.zig first db: *sqlite.Db → db: RelationalDb
query.zig save db: *sqlite.Db → db: RelationalDb
query.zig QuerySet db: *sqlite.Db → db: RelationalDb
migration.zig MigrationRunner.init db: *sqlite.Db → db: RelationalDb
migration.zig ensureHistoryTable Uses db.dialect() for DDL
schema.zig createTableSql New: comptime dialect: Dialect parameter
schema.zig insertSql New: comptime dialect: Dialect parameter
schema.zig selectByIdSql New: comptime dialect: Dialect parameter
schema.zig updateByIdSql New: comptime dialect: Dialect parameter
schema.zig migrationHistoryTableSql New: comptime dialect: Dialect parameter

App / Admin / CLI

Module Function Change
app.zig db field ?*sqlite.Db → ?OpenDb
app.zig useDatabase New method accepting DatabaseConfig
app.zig db() Returns RelationalDb instead of *sqlite.Db
app.zig database Removed — replaced by useDatabase
admin/registry.zig setDb *sqlite.Db → RelationalDb
cli/runner.zig cmdMigrate --driver flag; uses DatabaseConfig
cli/runner.zig cmdCreatesuperuser RelationalDb

Hardcoded SQL Type / Placeholder Changes

Current (SQLite-only) After Migration
INTEGER PRIMARY KEY AUTOINCREMENT dialect.pk_type
REAL dialect.float_type
INTEGER (for bool) dialect.bool_type
strftime('%s','now') dialect.timestamp_now
? placeholders dialect.placeholder(i, &buf) → ? (SQLite/MySQL) or $N (PostgreSQL)

Driver Structure

src/orm/
├── driver/
│   ├── interface.zig    # RelationalDb, AnyStmt, Dialect vtable types
│   ├── sqlite.zig       # SQLite driver (moved from src/orm/sqlite.zig)
│   ├── postgres.zig     # PostgreSQL driver (requires -Ddb_postgres=true)
│   └── mysql.zig        # MySQL driver (requires -Ddb_mysql=true)
├── document/
│   ├── interface.zig    # DocumentStore, Collection vtable types
│   └── mongodb.zig      # MongoDB driver (requires -Ddb_mongodb=true)
├── kv/
│   ├── interface.zig    # KVStore vtable types
│   └── redis.zig        # Redis driver (requires -Ddb_redis=true)
├── config.zig           # DatabaseConfig union + openDatabase()
├── schema.zig           # Dialect-parameterised SQL generation
├── query.zig            # RelationalDb-based query builder
├── migration.zig        # RelationalDb-based migration runner
└── sqlite.zig           # Backward-compat re-export shim

Example: Using zypher with PostgreSQL

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

pub fn main(init: std.process.Init) !void {
    var app = zypher.core.App.init(init.gpa, .{ .port = 8080 });
    defer app.deinit();

    try app.useDatabase(init.gpa, init.io, .{
        .postgres = .{ .connstr = "postgresql://user:pass@localhost:5432/mydb" },
    });

    // app.db() returns RelationalDb — works with query.zig and migration.zig
    const db = try app.db();
    _ = db; // use with QuerySet, create, filter, etc.

    try app.listenAndServe(init.io);
}

Build Flags

Flag Default Links Driver
(none) always vendored sqlite3 SQLite
-Ddb_postgres=true false system libpq PostgreSQL
-Ddb_mysql=true false system libmysqlclient MySQL/MariaDB
-Ddb_mongodb=true false system libmongoc MongoDB
-Ddb_redis=true false system libhiredis Redis

CI Guard Exclusions

The zig build test-io-clean step excludes these paths (they legitimately use std.Io for TCP, not banned std.net):

  • src/orm/driver/ — PostgreSQL, MySQL, Redis drivers use std.Io for network I/O
  • src/orm/document/ — MongoDB driver uses std.Io for network I/O
  • src/orm/kv/ — Redis driver uses std.Io for network I/O