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 Documentation -Zig Version +Zig Version GitHub stars GitHub issues GitHub pull requests @@ -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, }; }