comptime sql bindings for zig
ziglang sql

zig comptime#

internal research on zig's compile-time execution for informing zql's design.

core mechanisms#

comptime parameters (generics)#

functions accept comptime parameters that must be known at compile time:

fn max(comptime T: type, a: T, b: T) T {
    return if (a > b) a else b;
}

this implements compile-time duck typing - if T doesn't support >, error at call site.

comptime variables#

comptime var y: i32 = 1;
y += 1; // evaluated during compilation

inline loops#

inline for and inline while unroll at compile time:

inline for (std.meta.fields(T)) |field| {
    @field(value, field.name) = processField(field);
}

type-level programming#

types are first-class values at comptime. functions can return types:

fn ArrayList(comptime T: type) type {
    return struct {
        items: []T,
        len: usize,

        const Self = @This();

        fn append(self: *Self, item: T) !void {
            // ...
        }
    };
}

reflection via @typeInfo#

inspect type structure at compile time:

fn GetBiggerInt(comptime T: type) type {
    const info = @typeInfo(T).Int;
    return @Type(.{
        .Int = .{
            .bits = info.bits + 1,
            .signedness = info.signedness,
        },
    });
}

key builtins#

builtin purpose
@typeInfo(T) returns tagged union describing type structure
@Type(info) reifies a type from @typeInfo output
@TypeOf(expr) returns expression's type
@field(obj, name) dynamic field access (name must be comptime)
@hasField(T, name) checks if type has named field
@hasDecl(T, name) checks if type has named declaration
@compileError(msg) generates compile error with custom message

std.meta utilities#

  • std.meta.fields(T) - returns struct/union/enum field information
  • std.meta.fieldNames(T) - returns slice of field name strings
  • std.meta.FieldEnum(T) - generates enum matching struct fields
  • std.meta.hasFn(T, name) - checks if type has a function declaration
  • std.meta.eql(a, b) - recursive structural equality
  • std.meta.Tuple(&types) - creates tuple type from array of types

limitations#

limitation reason workaround
no I/O hermetic, reproducible builds use build.zig for external I/O
no heap allocation currently unsupported proposals exist (#5873, #5881)
@Type incomplete not implemented for enums, unions, functions use simpler type construction
no runtime type info types only exist at comptime build custom RTTI at comptime

gotchas#

  1. comptime doesn't cross function boundaries without explicit marking
  2. @field requires comptime-known strings - can't use runtime strings
  3. comptime pollution - once something is comptime, everything it uses must be too
  4. branch quota - loops default to 1000 backward branches, use @setEvalBranchQuota()
  5. no declaration-site type checking - errors appear at call sites, not generic definitions

patterns relevant to zql#

comptime string parsing#

std.fmt parses format strings at comptime, validating types before runtime:

std.debug.print("Value: {d}, Name: {s}\n", .{ value, name });
// format string parsed at compile time; type mismatches caught during compilation

this is what zql does with SQL strings.

perfect hash generation#

andrew kelley demonstrated O(1) string switches via comptime perfect hash search:

const ph = perfectHash(&.{ "a", "ab", "abc" });
switch (ph.hash(target)) {
    ph.case("a") => handleA(),
    ph.case("ab") => handleAb(),
    else => unreachable,
}

could be useful for column name lookups.

comptime string interning#

leverage memoization to deduplicate strings:

fn internString(comptime str: []const u8) []const u8 {
    return internStringBuffer(str.len, str[0..str.len].*);
}

fn internStringBuffer(comptime len: comptime_int, comptime items: [len]u8) []const u8 {
    comptime var storage: [len]u8 = items;
    return &storage;
}

since comptime calls are memoized, identical strings return the same address.

schema/orm patterns (tigerbeetle)#

fn DBType(comptime schema: Schema) type {
    return struct {
        tables: generateTables(schema),
        indexes: generateIndexes(schema),

        pub fn query(self: *@This(), comptime filter: Filter) Iterator {
            // ...
        }
    };
}

this is where zql could go - define schema once, generate queries.

sources#

official#

andrew kelley#

community#

std library source#