From 6765faa7e27a121c196caf13c94f0feed5b51083 Mon Sep 17 00:00:00 2001
From: kalpak <307643502+kalpakprod@users.noreply.github.com>
Date: Tue, 6 Oct 2026 05:37:05 +0500
Subject: [PATCH] Support Zig 0.17.0
Migrate struct reflection to the 0.17 field_names/field_types/field_attrs
accessors through one small src/compat.zig helper, spell array repetition
with @splat where 0.17 rejects the old form, replace the removed
std.ascii.indexOfIgnoreCase and the moved Fn/StructField attribute types,
and format the tree with the 0.17 zig fmt (CI runs zig fmt --check).
Bump the minimum Zig version to 0.17.0 in build.zig.zon, README, docs,
CONTRIBUTING and the CI workflow.
---
.github/workflows/ci.yml | 6 ++---
CONTRIBUTING.md | 2 +-
README.md | 4 ++--
build.zig.zon | 2 +-
docs/guide/getting-started.md | 2 +-
src/btree/btree.zig | 26 +++++++++++-----------
src/catalog/type_affinity.zig | 16 +++++++-------
src/compat.zig | 18 +++++++++++++++
src/connection/connection.zig | 13 ++++++-----
src/dsl/keys.zig | 9 ++++----
src/dsl/mutation.zig | 27 ++++++++++++-----------
src/dsl/query_builder.zig | 29 ++++++++++++------------
src/dsl/scope.zig | 5 +++--
src/dsl/table.zig | 39 +++++++++++++++++----------------
src/format/page.zig | 2 +-
src/sql/functions/aggregate.zig | 2 +-
src/storage/wal.zig | 12 +++++-----
src/vm/vm.zig | 4 ++--
18 files changed, 121 insertions(+), 97 deletions(-)
create mode 100644 src/compat.zig
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e37ebed..bf86ee0 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -29,7 +29,7 @@ jobs:
- name: Setup Zig
uses: mlugg/setup-zig@v2
with:
- version: 0.16.0
+ version: 0.17.0
- name: Format check
run: zig fmt --check src/ build.zig build.zig.zon
@@ -55,7 +55,7 @@ jobs:
- name: Setup Zig
uses: mlugg/setup-zig@v2
with:
- version: 0.16.0
+ version: 0.17.0
- name: Cache Zig build
uses: actions/cache@v6
@@ -143,7 +143,7 @@ jobs:
- name: Setup Zig
uses: mlugg/setup-zig@v2
with:
- version: 0.16.0
+ version: 0.17.0
- name: Cache Zig build
uses: actions/cache@v6
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 959907a..1be67be 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -5,7 +5,7 @@ zero-dependency SQLite-compatible engine written entirely in Zig.
## Prerequisites
-- Zig **0.16.0+** (see [ziglang.org](https://ziglang.org/download/)).
+- Zig **0.17.0+** (see [ziglang.org](https://ziglang.org/download/)).
- Windows 10+, Linux, or macOS.
- Git.
- [Bun](https://bun.sh) (only if you work on the documentation website in
diff --git a/README.md b/README.md
index 106a64e..a5ca10a 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
# SQLite.zig
-
+
@@ -121,7 +121,7 @@ Before using `sqlite.zig`, ensure you have the following:
| Requirement | Version | Notes |
|-------------|---------|-------|
-| **Zig** | 0.16.0+ | Download from [ziglang.org](https://ziglang.org/download/) |
+| **Zig** | 0.17.0+ | Download from [ziglang.org](https://ziglang.org/download/) |
| **Operating System** | Windows 10+, Linux, macOS | Cross-platform database engine |
---
diff --git a/build.zig.zon b/build.zig.zon
index 16ed869..af7d869 100644
--- a/build.zig.zon
+++ b/build.zig.zon
@@ -2,7 +2,7 @@
.name = .sqlite,
.version = "0.0.1",
.fingerprint = 0xb8bb8682c60b0867,
- .minimum_zig_version = "0.16.0",
+ .minimum_zig_version = "0.17.0",
.dependencies = .{},
.paths = .{
"build.zig",
diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md
index f73122b..10edc8e 100644
--- a/docs/guide/getting-started.md
+++ b/docs/guide/getting-started.md
@@ -18,7 +18,7 @@ description: "Quick start guide for sqlite.zig, a fully native, zero-dependency
## Prerequisites
-- **Zig 0.16.0** or later — download from [ziglang.org](https://ziglang.org/download/)
+- **Zig 0.17.0** or later — download from [ziglang.org](https://ziglang.org/download/)
## Your First Database
diff --git a/src/btree/btree.zig b/src/btree/btree.zig
index 08a0bc6..47b4cf0 100644
--- a/src/btree/btree.zig
+++ b/src/btree/btree.zig
@@ -91,7 +91,7 @@ pub const PageHeader = struct {
const offset = headerOffset(pageNumber);
std.debug.assert(offset + headerSize(self.pageType) <= page.len);
std.debug.assert(self.pageType == .interiorTable or self.pageType == .interiorIndex or self.rightChild == null);
- page[offset] = @intFromEnum(self.pageType);
+ page[offset] = @backingInt(self.pageType);
page[offset + 1] = @truncate(self.firstFreeblock >> 8);
page[offset + 2] = @truncate(self.firstFreeblock);
page[offset + 3] = @truncate(self.cellCount >> 8);
@@ -345,7 +345,7 @@ test "B-tree keeps keys ordered and replaces values" {
}
test "PageHeader encodes and decodes leaf and interior headers" {
- var page = [_]u8{0} ** 4096;
+ var page = @as([4096]u8, @splat(0));
const leafHeader = PageHeader{
.pageType = .leafTable,
.firstFreeblock = 0,
@@ -374,7 +374,7 @@ test "PageHeader encodes and decodes leaf and interior headers" {
}
test "formatLeafTablePage and formatInteriorTablePage construct valid pages" {
- var page = [_]u8{0} ** 4096;
+ var page = @as([4096]u8, @splat(0));
const dummyCell = "cell_payload";
const cells = [_][]const u8{ dummyCell, dummyCell };
try formatLeafTablePage(&page, 2, &cells, 4096);
@@ -382,7 +382,7 @@ test "formatLeafTablePage and formatInteriorTablePage construct valid pages" {
try std.testing.expectEqual(PageType.leafTable, decoded.pageType);
try std.testing.expectEqual(@as(u16, 2), decoded.cellCount);
- var interiorPage = [_]u8{0} ** 4096;
+ var interiorPage = @as([4096]u8, @splat(0));
const leftKids = [_]u32{ 2, 3 };
const keys = [_]u64{ 10, 20 };
try formatInteriorTablePage(&interiorPage, 4, &leftKids, &keys, 5, 4096);
@@ -394,7 +394,7 @@ test "formatLeafTablePage and formatInteriorTablePage construct valid pages" {
test "PageHeader rejects unknown flags and short buffers" {
// Error: every non-SQLite flag byte fails closed.
for ([_]u8{ 0x00, 0x01, 0x09, 0xff }) |flag| {
- var page = [_]u8{0} ** 512;
+ var page = @as([512]u8, @splat(0));
page[0] = flag;
try std.testing.expectError(error.InvalidHeader, PageHeader.decode(&page, 2));
}
@@ -407,7 +407,7 @@ test "PageHeader rejects unknown flags and short buffers" {
shortOne[100] = 0x05; // interior needs 12 bytes, only 1 available
try std.testing.expectError(error.InvalidHeader, PageHeader.decode(&shortOne, 1));
// Normal: page-1 headers decode past the 100-byte reservation.
- var pageOne = [_]u8{0} ** 512;
+ var pageOne = @as([512]u8, @splat(0));
(PageHeader{ .pageType = .leafTable, .cellCount = 1 }).encode(pageOne[0..], 1);
try std.testing.expectEqual(PageType.leafTable, (try PageHeader.decode(pageOne[0..], 1)).pageType);
}
@@ -425,25 +425,25 @@ test "page formatters fail closed on unfittable and short pages" {
const payloads = [_][]const u8{"p"};
try std.testing.expectError(error.PageOverflow, formatInteriorIndexPage(&small, 2, &kids, &payloads, 3, 4096));
// Error: key/child and payload/child length mismatches.
- var page = [_]u8{0} ** 512;
+ var page = @as([512]u8, @splat(0));
const twoKids = [_]u32{ 2, 3 };
const oneKey = [_]u64{9};
try std.testing.expectError(error.InvalidParam, formatInteriorTablePage(&page, 2, &twoKids, &oneKey, 4, 512));
try std.testing.expectError(error.InvalidParam, formatInteriorIndexPage(&page, 2, &twoKids, &payloads, 4, 512));
// Error: a cell larger than the page cannot fit anywhere.
- var big = [_]u8{0} ** 512;
- const hugeCell = [_]u8{0xaa} ** 600;
+ var big = @as([512]u8, @splat(0));
+ const hugeCell = @as([600]u8, @splat(0xaa));
const hugeCells = [_][]const u8{&hugeCell};
try std.testing.expectError(error.PageOverflow, formatLeafTablePage(&big, 2, &hugeCells, 512));
// Boundary: empty pages format with zero cells and null-free headers.
- var blank = [_]u8{0} ** 512;
+ var blank = @as([512]u8, @splat(0));
try formatLeafTablePage(&blank, 2, &[_][]const u8{}, 512);
try std.testing.expectEqual(@as(u16, 0), (try PageHeader.decode(&blank, 2)).cellCount);
// Normal: index leaf + interior pages round-trip their headers.
- var leafIdx = [_]u8{0} ** 512;
+ var leafIdx = @as([512]u8, @splat(0));
try formatLeafIndexPage(&leafIdx, 2, &cells, 512);
try std.testing.expectEqual(PageType.leafIndex, (try PageHeader.decode(&leafIdx, 2)).pageType);
- var interiorIdx = [_]u8{0} ** 512;
+ var interiorIdx = @as([512]u8, @splat(0));
try formatInteriorIndexPage(&interiorIdx, 2, &kids, &payloads, 9, 512);
try std.testing.expectEqual(@as(u32, 9), (try PageHeader.decode(&interiorIdx, 2)).rightChild.?);
}
@@ -614,7 +614,7 @@ test "interior formatters frame children with exact cells" {
try payloads.append(alloc, buf);
}
const right: u32 = rand.int(u32);
- var page = [_]u8{0} ** 4096;
+ var page = @as([4096]u8, @splat(0));
if (tableInterior) {
try formatInteriorTablePage(&page, 2, kids.items, keys.items, right, 4096);
} else {
diff --git a/src/catalog/type_affinity.zig b/src/catalog/type_affinity.zig
index d861111..58603ba 100644
--- a/src/catalog/type_affinity.zig
+++ b/src/catalog/type_affinity.zig
@@ -38,14 +38,14 @@ pub const Affinity = enum {
pub fn fromDeclaration(declaration: []const u8) Affinity {
const trimmed = std.mem.trim(u8, declaration, " \t\r\n");
if (trimmed.len == 0) return .blob;
- if (std.ascii.indexOfIgnoreCase(trimmed, "INT")) |_| return .integer;
- if (std.ascii.indexOfIgnoreCase(trimmed, "CHAR")) |_| return .text;
- if (std.ascii.indexOfIgnoreCase(trimmed, "CLOB")) |_| return .text;
- if (std.ascii.indexOfIgnoreCase(trimmed, "TEXT")) |_| return .text;
- if (std.ascii.indexOfIgnoreCase(trimmed, "BLOB")) |_| return .blob;
- if (std.ascii.indexOfIgnoreCase(trimmed, "REAL")) |_| return .real;
- if (std.ascii.indexOfIgnoreCase(trimmed, "FLOA")) |_| return .real;
- if (std.ascii.indexOfIgnoreCase(trimmed, "DOUB")) |_| return .real;
+ if (std.ascii.findIgnoreCase(trimmed, "INT")) |_| return .integer;
+ if (std.ascii.findIgnoreCase(trimmed, "CHAR")) |_| return .text;
+ if (std.ascii.findIgnoreCase(trimmed, "CLOB")) |_| return .text;
+ if (std.ascii.findIgnoreCase(trimmed, "TEXT")) |_| return .text;
+ if (std.ascii.findIgnoreCase(trimmed, "BLOB")) |_| return .blob;
+ if (std.ascii.findIgnoreCase(trimmed, "REAL")) |_| return .real;
+ if (std.ascii.findIgnoreCase(trimmed, "FLOA")) |_| return .real;
+ if (std.ascii.findIgnoreCase(trimmed, "DOUB")) |_| return .real;
return .numeric;
}
diff --git a/src/compat.zig b/src/compat.zig
new file mode 100644
index 0000000..7b52cbc
--- /dev/null
+++ b/src/compat.zig
@@ -0,0 +1,18 @@
+const std = @import("std");
+
+pub const Field = struct {
+ name: [:0]const u8,
+ type: type,
+ default_value_ptr: ?*const anyopaque,
+ is_comptime: bool,
+};
+
+// Preserve the old field view while using the Zig 0.17 reflection arrays.
+pub fn fields(comptime info: std.builtin.Type.Struct) [info.field_names.len]Field {
+ @setEvalBranchQuota(100000);
+ var result: [info.field_names.len]Field = undefined;
+ for (info.field_names, info.field_types, info.field_attrs, 0..) |name, T, attrs, i| {
+ result[i] = .{ .name = name, .type = T, .default_value_ptr = attrs.default_value_ptr, .is_comptime = attrs.@"comptime" };
+ }
+ return result;
+}
diff --git a/src/connection/connection.zig b/src/connection/connection.zig
index ae6c583..23d16ae 100644
--- a/src/connection/connection.zig
+++ b/src/connection/connection.zig
@@ -1,4 +1,5 @@
//! Engine facade: `Connection` runs SQL against file-backed schemas.
+const reflectionCompat = @import("../compat.zig");
const std = @import("std");
const DatabaseFile = @import("../storage/file.zig").DatabaseFile;
const image = @import("../storage/image.zig");
@@ -691,8 +692,8 @@ pub const Connection = struct {
fn createTypedTable(self: *Connection, comptime TableType: type, options: anytype) !void {
const Row = TableType.rowType;
- const rowFields = @typeInfo(Row).@"struct".fields;
- const colFields = @typeInfo(@TypeOf(TableType.columns)).@"struct".fields;
+ const rowFields = reflectionCompat.fields(@typeInfo(Row).@"struct");
+ const colFields = reflectionCompat.fields(@typeInfo(@TypeOf(TableType.columns)).@"struct");
if (rowFields.len != colFields.len) @compileError("table columns do not match row fields");
var definitions: [colFields.len]ast.ColumnDef = undefined;
inline for (colFields, 0..) |colField, index| {
@@ -752,8 +753,8 @@ pub const Connection = struct {
const Cols = tableMod.columnsTypeOfValue(T);
const tableOpts = tableMod.tableOptionsField(T).value;
const tname: []const u8 = target.tableName;
- const rowFields = @typeInfo(Row).@"struct".fields;
- const colFields = @typeInfo(Cols).@"struct".fields;
+ const rowFields = reflectionCompat.fields(@typeInfo(Row).@"struct");
+ const colFields = reflectionCompat.fields(@typeInfo(Cols).@"struct");
var definitions: [colFields.len]ast.ColumnDef = undefined;
inline for (colFields, 0..) |colField, index| {
const F = colField.type.fieldType;
@@ -1117,8 +1118,8 @@ pub const Connection = struct {
pub fn validate(self: @This()) !void {
const t = self.connection.store.findConst(self.tableName) orelse return error.UnknownTable;
- const rowFields = @typeInfo(Row).@"struct".fields;
- const colFields = @typeInfo(Cols).@"struct".fields;
+ const rowFields = reflectionCompat.fields(@typeInfo(Row).@"struct");
+ const colFields = reflectionCompat.fields(@typeInfo(Cols).@"struct");
if (t.columns.len != colFields.len) return error.SchemaMismatch;
if (rowFields.len != colFields.len) return error.SchemaMismatch;
var pkBuf: [16][]const u8 = undefined;
diff --git a/src/dsl/keys.zig b/src/dsl/keys.zig
index cfed28a..80dc49b 100644
--- a/src/dsl/keys.zig
+++ b/src/dsl/keys.zig
@@ -3,6 +3,7 @@
//! Outputs borrow the inputs; validation only borrows the table.
//! Wrong tables give `UnknownColumn`, bad shapes `InvalidSql`, drift `SchemaMismatch`.
+const reflectionCompat = @import("../compat.zig");
const std = @import("std");
const ast = @import("../sql/ast.zig");
const Value = @import("../vm/value.zig").Value;
@@ -180,7 +181,7 @@ fn referencesWantScope(comptime S: type, comptime T: type) bool {
const info = @typeInfo(T);
if (info == .pointer and info.pointer.size == .one) return referencesWantScope(S, info.pointer.child);
if (info == .@"struct" and info.@"struct".is_tuple) {
- inline for (info.@"struct".fields) |field| {
+ inline for (reflectionCompat.fields(info.@"struct")) |field| {
if (comptime scopeMod.isScopedItem(field.type, S.Row)) return true;
}
}
@@ -554,7 +555,7 @@ pub fn validateKeys(table: *const schemaMod.Table, expected: *const ExpectedKeys
if (col.unique) actualSingleCount += 1;
}
if (actualSingleCount != expected.uniqueSingleCount) return error.SchemaMismatch;
- var matched: [8]bool = .{false} ** 8;
+ var matched: [8]bool = @splat(false);
var actualGroupCount: usize = 0;
for (table.constraints) |*c| {
if (c.kind != .unique) continue;
@@ -572,7 +573,7 @@ pub fn validateKeys(table: *const schemaMod.Table, expected: *const ExpectedKeys
if (actualGroupCount != expected.uniqueGroupCount) return error.SchemaMismatch;
}
if (expected.hasFks) {
- var matched: [8]bool = .{false} ** 8;
+ var matched: [8]bool = @splat(false);
var actualCount: usize = 0;
for (table.columns) |*col| {
if (col.foreignTable == null) continue;
@@ -756,7 +757,7 @@ test "zig type mapping documents the storage contract" {
try std.testing.expectEqualStrings("TEXT", dslTypeName([]const u8));
try std.testing.expectEqualStrings("TEXT", dslTypeName([]u8));
const S = struct { n: i64 = 7, t: []const u8 = "hi", f: f64 = 1.5, o: ?i64 = null, b: bool = true };
- inline for (@typeInfo(S).@"struct".fields) |field| {
+ inline for (reflectionCompat.fields(@typeInfo(S).@"struct")) |field| {
const got = zigDefault(field.type, field.default_value_ptr);
if (std.mem.eql(u8, field.name, "n")) try std.testing.expect(got.?.integer == 7);
if (std.mem.eql(u8, field.name, "t")) try std.testing.expectEqualStrings("hi", got.?.text);
diff --git a/src/dsl/mutation.zig b/src/dsl/mutation.zig
index d867c49..25194d2 100644
--- a/src/dsl/mutation.zig
+++ b/src/dsl/mutation.zig
@@ -19,6 +19,7 @@
//! builder and result. Errors: engine errors from execution; overflow
//! panics like every other builder limit path.
+const reflectionCompat = @import("../compat.zig");
const std = @import("std");
const dslExpr = @import("expr.zig");
const Expr = dslExpr.Expr;
@@ -58,7 +59,7 @@ pub fn rootScopeName(comptime Columns: type) []const u8 {
if (Columns == void) return "";
const info = @typeInfo(Columns);
if (info != .@"struct") return "";
- for (info.@"struct".fields) |field| {
+ for (reflectionCompat.fields(info.@"struct")) |field| {
if (@hasDecl(field.type, "dslTable")) return field.type.dslTable;
}
return "";
@@ -76,7 +77,7 @@ pub fn allMatchesRoot(comptime Columns: type, comptime Marker: type) bool {
/// The marker carries its own columns descriptor, so the qualifier is
/// always the side the marker was built from (table name or alias).
pub fn appendMarkerColumns(projections: []Projection, count: *usize, comptime Marker: type) void {
- inline for (@typeInfo(Marker.Columns).@"struct".fields) |field| {
+ inline for (reflectionCompat.fields(@typeInfo(Marker.Columns).@"struct")) |field| {
if (count.* >= projections.len) @panic("too many DSL projections");
projections[count.*] = toProjection(field.type{});
count.* += 1;
@@ -98,7 +99,7 @@ pub fn checkDynAssignScope(item: anytype, table: []const u8, tableAlias: ?[]cons
/// (`Columns == void`) stay unchecked by design.
pub fn checkDynAssignColumn(comptime Columns: type, name: []const u8) !void {
if (Columns == void) return;
- inline for (@typeInfo(Columns).@"struct".fields) |field| {
+ inline for (reflectionCompat.fields(@typeInfo(Columns).@"struct")) |field| {
if (std.mem.eql(u8, field.type.dslName, name)) return;
}
return error.UnknownColumn;
@@ -117,7 +118,7 @@ pub fn checkDuplicateName(names: []const []const u8, dest: []const u8) !void {
/// prune a trailing `@compileError` during analysis.
pub fn hasAssignColumn(comptime Col: type, comptime Columns: type) bool {
if (Columns == void) return true;
- inline for (@typeInfo(Columns).@"struct".fields) |field| {
+ inline for (reflectionCompat.fields(@typeInfo(Columns).@"struct")) |field| {
if (std.mem.eql(u8, field.type.dslName, Col.dslName)) return true;
}
return false;
@@ -129,7 +130,7 @@ pub fn hasAssignColumn(comptime Col: type, comptime Columns: type) bool {
/// runtime `checkDuplicateName`/inline sweeps at each use site instead.
/// Callers gate explicitly.
pub fn hasDuplicateAssign(comptime Tuple: type, comptime index: usize) bool {
- const fields = @typeInfo(Tuple).@"struct".fields;
+ const fields = reflectionCompat.fields(@typeInfo(Tuple).@"struct");
const needleT = fields[index].type;
if (comptime !columnMod.isAssignValue(needleT)) return false;
const needle = needleT.assignColumn.dslName;
@@ -265,8 +266,8 @@ pub fn isAssignItem(comptime T: type) bool {
pub fn isAssignList(comptime T: type) bool {
const info = @typeInfo(T);
if (info != .@"struct" or !info.@"struct".is_tuple) return false;
- if (info.@"struct".fields.len == 0) return false;
- inline for (info.@"struct".fields) |field| {
+ if (reflectionCompat.fields(info.@"struct").len == 0) return false;
+ inline for (reflectionCompat.fields(info.@"struct")) |field| {
if (!isAssignItem(field.type)) return false;
}
return true;
@@ -495,20 +496,20 @@ pub fn UpsertBuilder(comptime Row: type, comptime Columns: type) type {
return;
}
if (isTyped) {
- inline for (@typeInfo(RowType).@"struct".fields) |field| {
+ inline for (reflectionCompat.fields(@typeInfo(RowType).@"struct")) |field| {
if (!@hasField(Row, field.name)) @compileError("DSL row contains an unknown table column");
}
}
self.setCount = 0;
if (Columns == void) {
- inline for (@typeInfo(RowType).@"struct".fields) |field| {
+ inline for (reflectionCompat.fields(@typeInfo(RowType).@"struct")) |field| {
if (comptime isExplicitDefault(@TypeOf(@field(assignments, field.name)))) continue;
if (self.setCount >= self.sets.len) return error.InvalidSql;
self.sets[self.setCount] = .{ .name = field.name, .value = upsertValueOf(@field(assignments, field.name)) };
self.setCount += 1;
}
} else {
- inline for (@typeInfo(Columns).@"struct".fields) |colField| {
+ inline for (reflectionCompat.fields(@typeInfo(Columns).@"struct")) |colField| {
if (@hasField(RowType, colField.name)) {
if (comptime isExplicitDefault(@TypeOf(@field(assignments, colField.name)))) continue;
if (self.setCount >= self.sets.len) return error.InvalidSql;
@@ -530,12 +531,12 @@ pub fn UpsertBuilder(comptime Row: type, comptime Columns: type) type {
const RowType = @TypeOf(row);
if (@typeInfo(RowType) != .@"struct") @compileError("DSL row must be a struct");
if (isTyped) {
- inline for (@typeInfo(RowType).@"struct".fields) |field| {
+ inline for (reflectionCompat.fields(@typeInfo(RowType).@"struct")) |field| {
if (!@hasField(Row, field.name)) @compileError("DSL row contains an unknown table column");
}
}
if (Columns == void) {
- const fields = @typeInfo(RowType).@"struct".fields;
+ const fields = reflectionCompat.fields(@typeInfo(RowType).@"struct");
var names: [fields.len][]const u8 = undefined;
var vals: [fields.len]dslExpr.SetValue = undefined;
var count: usize = 0;
@@ -556,7 +557,7 @@ pub fn UpsertBuilder(comptime Row: type, comptime Columns: type) type {
defer built.deinit();
return self.executeFn(self.connection, &built.stmt, &.{}, false);
} else {
- const colFields = @typeInfo(Columns).@"struct".fields;
+ const colFields = reflectionCompat.fields(@typeInfo(Columns).@"struct");
var names: [colFields.len][]const u8 = undefined;
var vals: [colFields.len]dslExpr.SetValue = undefined;
var count: usize = 0;
diff --git a/src/dsl/query_builder.zig b/src/dsl/query_builder.zig
index e82f25f..d7abfea 100644
--- a/src/dsl/query_builder.zig
+++ b/src/dsl/query_builder.zig
@@ -1,5 +1,6 @@
//! Value-semantic query and mutation builders over the native AST.
+const reflectionCompat = @import("../compat.zig");
const std = @import("std");
const dslExpr = @import("expr.zig");
const Expr = dslExpr.Expr;
@@ -58,7 +59,7 @@ fn tupleHasForeignAll(comptime Columns: type, comptime T: type) bool {
if (info == .array) return tupleHasForeignAll(Columns, info.array.child);
if (comptime tableMod.isAllProjectionType(T)) return !mutationMod.allMatchesRoot(Columns, T);
if (info == .@"struct" and info.@"struct".is_tuple) {
- inline for (info.@"struct".fields) |field| {
+ inline for (reflectionCompat.fields(info.@"struct")) |field| {
if (comptime tupleHasForeignAll(Columns, field.type)) return true;
}
}
@@ -81,7 +82,7 @@ fn toOrder(item: anytype) Order {
}
fn destSqlFor(comptime Columns: type, want: []const u8) ?[]const u8 {
- inline for (@typeInfo(Columns).@"struct".fields) |colField| {
+ inline for (reflectionCompat.fields(@typeInfo(Columns).@"struct")) |colField| {
if (std.mem.eql(u8, colField.name, want)) return colField.type.dslName;
}
return null;
@@ -125,7 +126,7 @@ fn containsAllMarker(comptime T: type) bool {
if (info == .pointer) return containsAllMarker(info.pointer.child);
if (info == .array) return containsAllMarker(info.array.child);
if (info == .@"struct" and info.@"struct".is_tuple) {
- inline for (info.@"struct".fields) |field| {
+ inline for (reflectionCompat.fields(info.@"struct")) |field| {
if (containsAllMarker(field.type)) return true;
}
}
@@ -1087,7 +1088,7 @@ pub fn Builder(comptime Row: type, comptime Columns: type, comptime mapped: bool
fn validateRow(comptime RowType: type) void {
if (@typeInfo(RowType) != .@"struct") @compileError("DSL row must be a struct");
if (isTyped) {
- inline for (@typeInfo(RowType).@"struct".fields) |field| {
+ inline for (reflectionCompat.fields(@typeInfo(RowType).@"struct")) |field| {
if (!@hasField(Row, field.name)) @compileError("DSL row contains an unknown table column");
}
}
@@ -1124,7 +1125,7 @@ pub fn Builder(comptime Row: type, comptime Columns: type, comptime mapped: bool
pub fn insertFrom(self: Self, source: anytype, mapping: anytype) !Result {
const MappingType = @TypeOf(mapping);
if (@typeInfo(MappingType) != .@"struct") @compileError("insertFrom mapping must be a struct of destination-field = source-column pairs");
- const mapFields = @typeInfo(MappingType).@"struct".fields;
+ const mapFields = reflectionCompat.fields(@typeInfo(MappingType).@"struct");
if (mapFields.len == 0) @compileError("insertFrom mapping must name at least one column");
const SourceT = @TypeOf(source);
const isDynamicSource = SourceT != type and @typeInfo(SourceT) == .@"struct" and @hasDecl(SourceT, "isDynamicTable");
@@ -1362,7 +1363,7 @@ pub fn Builder(comptime Row: type, comptime Columns: type, comptime mapped: bool
if (comptime mutationMod.isAssignItem(RowType)) return self.insertAssigns(.{row}, conflict);
validateRow(RowType);
if (Columns == void) {
- const fields = @typeInfo(RowType).@"struct".fields;
+ const fields = reflectionCompat.fields(@typeInfo(RowType).@"struct");
var names: [fields.len][]const u8 = undefined;
var vals: [fields.len]dslExpr.SetValue = undefined;
var count: usize = 0;
@@ -1377,7 +1378,7 @@ pub fn Builder(comptime Row: type, comptime Columns: type, comptime mapped: bool
defer built.deinit();
return self.executeFn(self.connection, &built.stmt, &.{}, false);
} else {
- const colFields = @typeInfo(Columns).@"struct".fields;
+ const colFields = reflectionCompat.fields(@typeInfo(Columns).@"struct");
var names: [colFields.len][]const u8 = undefined;
var vals: [colFields.len]dslExpr.SetValue = undefined;
var count: usize = 0;
@@ -1450,7 +1451,7 @@ pub fn Builder(comptime Row: type, comptime Columns: type, comptime mapped: bool
}
validateRow(RowType);
if (Columns == void) {
- inline for (@typeInfo(RowType).@"struct".fields) |field| {
+ inline for (reflectionCompat.fields(@typeInfo(RowType).@"struct")) |field| {
if (comptime mutationMod.isExplicitDefault(@TypeOf(@field(assignments, field.name)))) continue;
if (mutation.setCount >= mutation.setNames.len) return error.InvalidSql;
mutation.setNames[mutation.setCount] = field.name;
@@ -1458,7 +1459,7 @@ pub fn Builder(comptime Row: type, comptime Columns: type, comptime mapped: bool
mutation.setCount += 1;
}
} else {
- inline for (@typeInfo(Columns).@"struct".fields) |colField| {
+ inline for (reflectionCompat.fields(@typeInfo(Columns).@"struct")) |colField| {
if (@hasField(RowType, colField.name)) {
if (comptime mutationMod.isExplicitDefault(@TypeOf(@field(assignments, colField.name)))) continue;
if (mutation.setCount >= mutation.setNames.len) return error.InvalidSql;
@@ -1888,7 +1889,7 @@ fn assignMappedValue(allocator: std.mem.Allocator, destination: anytype, value:
}
fn freeMappedRow(comptime Row: type, allocator: std.mem.Allocator, row: *Row) void {
- inline for (@typeInfo(Row).@"struct".fields) |field| {
+ inline for (reflectionCompat.fields(@typeInfo(Row).@"struct")) |field| {
freeMappedValue(allocator, @field(row.*, field.name));
}
}
@@ -1904,18 +1905,18 @@ fn mapResultRows(comptime Row: type, comptime Columns: type, allocator: std.mem.
var destination: Row = undefined;
var assigned: usize = 0;
errdefer {
- inline for (@typeInfo(Row).@"struct".fields, 0..) |field, fi| {
+ inline for (reflectionCompat.fields(@typeInfo(Row).@"struct"), 0..) |field, fi| {
if (fi < assigned) freeMappedValue(allocator, @field(destination, field.name));
}
}
if (Columns == void) {
- inline for (@typeInfo(Row).@"struct".fields, 0..) |field, fi| {
+ inline for (reflectionCompat.fields(@typeInfo(Row).@"struct"), 0..) |field, fi| {
const index = findResultColumn(result.columns, field.name) orelse return error.UnknownColumn;
try assignMappedValue(allocator, &@field(destination, field.name), source[index]);
assigned = fi + 1;
}
} else {
- inline for (@typeInfo(Columns).@"struct".fields, 0..) |colField, fi| {
+ inline for (reflectionCompat.fields(@typeInfo(Columns).@"struct"), 0..) |colField, fi| {
const index = findResultColumn(result.columns, colField.type.dslName) orelse return error.UnknownColumn;
try assignMappedValue(allocator, &@field(destination, colField.name), source[index]);
assigned = fi + 1;
@@ -2195,7 +2196,7 @@ test "select preserves projection order and AllColumns routing" {
const all = starBase.selectAll();
try std.testing.expect(all.allColumns);
try std.testing.expectEqual(@as(usize, 0), all.projectionCount);
- try std.testing.expectEqual(@as(usize, 2), @typeInfo(@TypeOf(T.all()).Columns).@"struct".fields.len);
+ try std.testing.expectEqual(@as(usize, 2), reflectionCompat.fields(@typeInfo(@TypeOf(T.all()).Columns).@"struct").len);
}
test "scoped and explicit selects converge on one projection" {
diff --git a/src/dsl/scope.zig b/src/dsl/scope.zig
index 0d2a3dd..0e0d74f 100644
--- a/src/dsl/scope.zig
+++ b/src/dsl/scope.zig
@@ -48,6 +48,7 @@
//! `.id` lives in column-list positions while predicates always name their
//! table explicitly (`User.id.eq(1)`, `u.id.eq(1)`) or use dynamic columns.
+const reflectionCompat = @import("../compat.zig");
const std = @import("std");
const dslExpr = @import("expr.zig");
const ColumnRef = dslExpr.ColumnRef;
@@ -99,7 +100,7 @@ pub fn isScopedList(comptime T: type, comptime Row: type) bool {
if (info == .array) return isScopedItem(info.array.child, Row);
if (info == .pointer and info.pointer.size == .slice) return isScopedItem(info.pointer.child, Row);
if (info == .@"struct" and info.@"struct".is_tuple) {
- const fields = info.@"struct".fields;
+ const fields = reflectionCompat.fields(info.@"struct");
if (fields.len == 0) return false;
inline for (fields) |field| {
if (!isScopedItem(field.type, Row)) return false;
@@ -113,7 +114,7 @@ pub fn isScopedList(comptime T: type, comptime Row: type) bool {
/// columns descriptor struct). Usable at runtime for slice inputs; returns
/// null when the field is unknown.
pub fn sqlNameOf(comptime Columns: type, zigName: []const u8) ?[]const u8 {
- inline for (@typeInfo(Columns).@"struct".fields) |field| {
+ inline for (reflectionCompat.fields(@typeInfo(Columns).@"struct")) |field| {
if (std.mem.eql(u8, field.name, zigName)) return field.type.dslName;
}
return null;
diff --git a/src/dsl/table.zig b/src/dsl/table.zig
index d6d55b1..43f8801 100644
--- a/src/dsl/table.zig
+++ b/src/dsl/table.zig
@@ -3,6 +3,7 @@
//! Everything is comptime-known borrowed names; no heap, no deinit.
//! Misuse is a compile error.
+const reflectionCompat = @import("../compat.zig");
const std = @import("std");
const Column = @import("column.zig").Column;
@@ -39,7 +40,7 @@ pub fn isAllOpFn(comptime T: type) bool {
if (info != .pointer or info.pointer.size != .one) return false;
const fnInfo = @typeInfo(info.pointer.child);
if (fnInfo != .@"fn") return false;
- if (fnInfo.@"fn".params.len != 0) return false;
+ if (fnInfo.@"fn".param_types.len != 0) return false;
const Ret = fnInfo.@"fn".return_type orelse return false;
return isAllProjectionType(Ret);
}
@@ -91,7 +92,7 @@ pub fn TablePublic(comptime name: []const u8, comptime spec: anytype, comptime o
if (@TypeOf(spec) == type) {
const info = @typeInfo(spec);
if (info != .@"struct") @compileError("sqlite.table row must be a struct");
- return TableTypeFor(name, spec, opts, info.@"struct".fields, null);
+ return TableTypeFor(name, spec, opts, reflectionCompat.fields(info.@"struct"), null);
}
return DescribedTypeFor(name, spec, opts);
}
@@ -102,7 +103,7 @@ pub fn tableWith(comptime name: []const u8, comptime spec: anytype, comptime opt
if (@TypeOf(spec) == type) {
const info = @typeInfo(spec);
if (info != .@"struct") @compileError("sqlite.table row must be a struct");
- return buildTable(name, spec, opts, info.@"struct".fields, null);
+ return buildTable(name, spec, opts, reflectionCompat.fields(info.@"struct"), null);
}
return buildDescribed(name, spec, opts);
}
@@ -122,8 +123,8 @@ fn comptimeStringEq(comptime a: []const u8, comptime b: []const u8) bool {
return true;
}
-fn makeAttrs(comptime n: usize) *const [n]std.builtin.Type.StructField.Attributes {
- const A = [_]std.builtin.Type.StructField.Attributes{.{}} ** n;
+fn makeAttrs(comptime n: usize) *const [n]std.builtin.Type.Struct.FieldAttributes {
+ const A = @as([n]std.builtin.Type.Struct.FieldAttributes, @splat(.{}));
return &A;
}
@@ -167,7 +168,7 @@ fn TableTypeFor(
}
fn DescribedTypeFor(comptime tname: []const u8, comptime spec: anytype, comptime opts: anytype) type {
- const fields = @typeInfo(@TypeOf(spec)).@"struct".fields;
+ const fields = reflectionCompat.fields(@typeInfo(@TypeOf(spec)).@"struct");
const N = fields.len;
const hasUserAll = @hasField(@TypeOf(spec), "all");
const Total = N + metaCount + (if (hasUserAll) 0 else 1);
@@ -227,7 +228,7 @@ fn buildTable(
v.rowType = .{};
v.tableOptions = .{};
v.tableAlias = "";
- inline for (@typeInfo(T).@"struct".fields) |field| {
+ inline for (reflectionCompat.fields(@typeInfo(T).@"struct")) |field| {
if (comptime isColumnField(field.type)) {
@field(v, field.name) = .{};
} else if (comptime isAllOpFn(field.type)) {
@@ -239,7 +240,7 @@ fn buildTable(
fn buildDescribed(comptime tname: []const u8, comptime spec: anytype, comptime opts: anytype) DescribedTypeFor(tname, spec, opts) {
const T = DescribedTypeFor(tname, spec, opts);
- const fields = @typeInfo(@TypeOf(spec)).@"struct".fields;
+ const fields = reflectionCompat.fields(@typeInfo(@TypeOf(spec)).@"struct");
const N = fields.len;
var sqls: [N][]const u8 = undefined;
inline for (fields, 0..) |field, i| {
@@ -251,7 +252,7 @@ fn buildDescribed(comptime tname: []const u8, comptime spec: anytype, comptime o
v.rowType = .{};
v.tableOptions = .{};
v.tableAlias = "";
- inline for (@typeInfo(T).@"struct".fields) |field| {
+ inline for (reflectionCompat.fields(@typeInfo(T).@"struct")) |field| {
if (comptime isColumnField(field.type)) {
@field(v, field.name) = .{};
} else if (comptime isAllOpFn(field.type)) {
@@ -286,7 +287,7 @@ fn isMetaFieldName(comptime name: []const u8) bool {
pub fn AliasedType(comptime T: type, comptime aliasName: []const u8) type {
if (!isTableValue(T)) @compileError("aliased() takes a sqlite.table(...) value");
if (aliasName.len == 0) @compileError("alias must not be empty");
- const fields = @typeInfo(T).@"struct".fields;
+ const fields = reflectionCompat.fields(@typeInfo(T).@"struct");
comptime var names: [fields.len][:0]const u8 = undefined;
comptime var types: [fields.len]type = undefined;
comptime var allIndex: ?usize = null;
@@ -339,8 +340,8 @@ fn allOpForType(comptime Fn: type) Fn {
pub fn aliased(tbl: anytype, comptime aliasName: []const u8) AliasedType(@TypeOf(tbl), aliasName) {
const T = @TypeOf(tbl);
const A = AliasedType(T, aliasName);
- const AF = @typeInfo(A).@"struct".fields;
- const TF = @typeInfo(T).@"struct".fields;
+ const AF = reflectionCompat.fields(@typeInfo(A).@"struct");
+ const TF = reflectionCompat.fields(@typeInfo(T).@"struct");
var v: A = undefined;
inline for (AF, 0..) |afield, i| {
if (comptime isMetaFieldName(afield.name)) {
@@ -366,16 +367,16 @@ pub fn tableNameOfValue(source: anytype) []const u8 {
/// Row struct type carried by a table value type.
pub fn rowTypeOfValue(comptime T: type) type {
- return @typeInfo(T).@"struct".fields[2].type.value;
+ return reflectionCompat.fields(@typeInfo(T).@"struct")[2].type.value;
}
/// Options-holder type carried by a table value type.
pub fn tableOptionsField(comptime T: type) type {
- return @typeInfo(T).@"struct".fields[3].type;
+ return reflectionCompat.fields(@typeInfo(T).@"struct")[3].type;
}
fn hasTrailingAllOp(comptime T: type) bool {
- const f = @typeInfo(T).@"struct".fields;
+ const f = reflectionCompat.fields(@typeInfo(T).@"struct");
if (f.len <= metaCount) return false;
const last = f[f.len - 1];
return comptimeStringEq(last.name, "all") and isAllOpFn(last.type);
@@ -384,7 +385,7 @@ fn hasTrailingAllOp(comptime T: type) bool {
/// Columns-only struct type of a table value (metadata + trailing `all` op
/// stripped). Used by `query_builder` to map result columns positionally.
pub fn columnsTypeOfValue(comptime T: type) type {
- const f = @typeInfo(T).@"struct".fields;
+ const f = reflectionCompat.fields(@typeInfo(T).@"struct");
const skip: usize = if (hasTrailingAllOp(T)) 1 else 0;
const n = f.len - metaCount - skip;
comptime var names: [n][:0]const u8 = undefined;
@@ -398,7 +399,7 @@ pub fn columnsTypeOfValue(comptime T: type) type {
/// Number of column fields in a table value type (excludes metadata/`all`).
pub fn columnCount(comptime T: type) usize {
- const f = @typeInfo(T).@"struct".fields;
+ const f = reflectionCompat.fields(@typeInfo(T).@"struct");
const skip: usize = if (hasTrailingAllOp(T)) 1 else 0;
return f.len - metaCount - skip;
}
@@ -457,10 +458,10 @@ test "all() builds the all-columns operation on collision-free tables" {
try std.testing.expect(isAllProjectionType(@TypeOf(u.all())));
try std.testing.expectEqualStrings("u", @TypeOf(u.all()).qualifierName);
// The marker's columns describe the source scope for expansion.
- inline for (@typeInfo(@TypeOf(u.all()).Columns).@"struct".fields) |f| {
+ inline for (reflectionCompat.fields(@typeInfo(@TypeOf(u.all()).Columns).@"struct")) |f| {
try std.testing.expectEqualStrings("u", f.type.table);
}
- inline for (@typeInfo(@TypeOf(proj).Columns).@"struct".fields) |f| {
+ inline for (reflectionCompat.fields(@typeInfo(@TypeOf(proj).Columns).@"struct")) |f| {
try std.testing.expectEqualStrings("users", f.type.table);
}
}
diff --git a/src/format/page.zig b/src/format/page.zig
index f4734ef..645642f 100644
--- a/src/format/page.zig
+++ b/src/format/page.zig
@@ -42,7 +42,7 @@ pub const Page = struct {
if (self.pageNumber == 0) return error.InvalidPage;
const offset = self.headerOffset();
if (offset >= self.bytes.len) return error.InvalidPage;
- self.bytes[offset] = @intFromEnum(kind);
+ self.bytes[offset] = @backingInt(kind);
}
};
diff --git a/src/sql/functions/aggregate.zig b/src/sql/functions/aggregate.zig
index 6473a14..311219e 100644
--- a/src/sql/functions/aggregate.zig
+++ b/src/sql/functions/aggregate.zig
@@ -56,7 +56,7 @@ const DistinctKey = union(enum) {
const DistinctContext = struct {
pub fn hash(_: @This(), key: DistinctKey) u64 {
var hasher = std.hash.Wyhash.init(0);
- hasher.update(&[_]u8{@intFromEnum(key)});
+ hasher.update(&[_]u8{@backingInt(key)});
switch (key) {
.integer => |n| hasher.update(std.mem.asBytes(&n)),
.real => |bits| hasher.update(std.mem.asBytes(&bits)),
diff --git a/src/storage/wal.zig b/src/storage/wal.zig
index 9c60cdf..24e4971 100644
--- a/src/storage/wal.zig
+++ b/src/storage/wal.zig
@@ -199,12 +199,12 @@ pub fn apply(allocator: std.mem.Allocator, baseImage: []const u8, walImage: []co
test "WAL encodes and applies SQLite page frames" {
const pageSize = 512;
- var image = [_]u8{0} ** (pageSize * 2);
+ var image = @as([(pageSize * 2)]u8, @splat(0));
image[0] = 'S';
image[pageSize + 7] = 42;
const encoded = try encodeImage(std.testing.allocator, &image, pageSize);
defer std.testing.allocator.free(encoded);
- var base = [_]u8{0} ** (pageSize * 2);
+ var base = @as([(pageSize * 2)]u8, @splat(0));
const applied = try apply(std.testing.allocator, &base, encoded);
defer std.testing.allocator.free(applied);
try std.testing.expectEqual(@as(u8, 'S'), applied[0]);
@@ -233,11 +233,11 @@ test "WAL header decode rejects bad magic version geometry and checksum" {
test "WAL apply rejects corrupt frames without unsafe behavior" {
const pageSize = 512;
- var image = [_]u8{0} ** pageSize;
+ var image = @as([pageSize]u8, @splat(0));
image[3] = 0xab;
const encoded = try encodeImage(std.testing.allocator, &image, pageSize);
defer std.testing.allocator.free(encoded);
- var base = [_]u8{0} ** pageSize;
+ var base = @as([pageSize]u8, @splat(0));
// Truncations fail closed, except the exact end-of-header prefix, which
// is a valid zero-frame WAL that applies to a copy of the base image.
var cut: usize = 1;
@@ -285,7 +285,7 @@ test "WAL apply rejects corrupt frames without unsafe behavior" {
test "WAL encodeImage validates geometry and frame headers round trip" {
// Error: bad geometry and unframed images fail before allocating.
- var image = [_]u8{0} ** 512;
+ var image = @as([512]u8, @splat(0));
try std.testing.expectError(error.InvalidPageSize, encodeImage(std.testing.allocator, &image, 100));
try std.testing.expectError(error.InvalidPageSize, encodeImage(std.testing.allocator, &image, 0));
try std.testing.expectError(error.InvalidPageSize, encodeImage(std.testing.allocator, image[0..100], 512));
@@ -304,7 +304,7 @@ test "WAL encodeImage validates geometry and frame headers round trip" {
// Boundary: a zero-frame WAL applies to a copy of the base image.
var hdr: [headerSize]u8 = undefined;
(WalHeader{ .pageSize = 512 }).encode(&hdr);
- var base = [_]u8{0x5a} ** 512;
+ var base = @as([512]u8, @splat(0x5a));
const copied = try apply(std.testing.allocator, &base, &hdr);
defer std.testing.allocator.free(copied);
try std.testing.expectEqualSlices(u8, &base, copied);
diff --git a/src/vm/vm.zig b/src/vm/vm.zig
index f0a98bb..799063b 100644
--- a/src/vm/vm.zig
+++ b/src/vm/vm.zig
@@ -345,8 +345,8 @@ pub const VirtualMachine = struct {
.allocator = allocator,
.arena = std.heap.ArenaAllocator.init(allocator),
.schema = schema,
- .cursors = [_]?Cursor{null} ** 32,
- .aggStates = [_]AggState{.{}} ** 16,
+ .cursors = @as([32]?Cursor, @splat(null)),
+ .aggStates = @as([16]AggState, @splat(.{})),
.changes = 0,
};
}