From 289a3b1c6737a3645733e88cf655548702b55860 Mon Sep 17 00:00:00 2001 From: samooth Date: Fri, 28 Aug 2026 21:56:54 +0200 Subject: [PATCH 01/22] Update to Zig 0.16/0.17 and SQLite 3.49.2 - Bump minimum_zig_version to 0.16.0 - Update SQLite to 3.49.2 - Add Zig 0.16.0 and 0.17.0 to CI matrix - Pin mise.toml to Zig 0.16.0 - Update fuzz test to new testing.fuzz API (testing.Smith removed) --- .github/workflows/main.yml | 13 +++++++++---- build.zig.zon | 4 ++-- mise.toml | 2 +- sqlite.zig | 7 +------ 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2641d4f..018961e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,12 +17,16 @@ concurrency: jobs: lint: + strategy: + fail-fast: false + matrix: + zig_version: ["0.16.0", "0.17.0"] runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - uses: mlugg/setup-zig@v2 with: - version: 0.16.0 + version: ${{ matrix.zig_version }} - run: zig fmt --check *.zig test-in-memory: @@ -30,6 +34,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-24.04, windows-latest, macos-latest] + zig_version: ["0.16.0", "0.17.0"] runs-on: ${{ matrix.os }} steps: - name: Checkout repository @@ -38,7 +43,7 @@ jobs: - name: Setup zig uses: mlugg/setup-zig@v2 with: - version: 0.16.0 + version: ${{ matrix.zig_version }} - name: Install qemu if: ${{ matrix.os == 'ubuntu-24.04' }} @@ -51,8 +56,8 @@ jobs: path: | zig-cache ~/.cache/zig - key: ${{ runner.os }}-${{ matrix.os }}-zig-${{ github.sha }} - restore-keys: ${{ runner.os }}-${{ matrix.os }}-zig- + key: ${{ runner.os }}-${{ matrix.os }}-zig-${{ matrix.zig_version }}-${{ github.sha }} + restore-keys: ${{ runner.os }}-${{ matrix.os }}-zig-${{ matrix.zig_version }}- - name: Run Tests in memory if: ${{ matrix.os == 'ubuntu-24.04' }} diff --git a/build.zig.zon b/build.zig.zon index 06507f1..df3bd7d 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,8 +1,8 @@ .{ .name = .sqlite, .fingerprint = 0xb8bb86826b7f6417, - .minimum_zig_version = "0.14.0", - .version = "3.48.0", + .minimum_zig_version = "0.16.0", + .version = "3.49.2", .dependencies = .{ .sqlite = .{ .url = "https://www.sqlite.org/2025/sqlite-amalgamation-3490200.zip", diff --git a/mise.toml b/mise.toml index af9eca5..ef36926 100644 --- a/mise.toml +++ b/mise.toml @@ -1,2 +1,2 @@ [tools] -zig = "latest" +zig = "0.16.0" diff --git a/sqlite.zig b/sqlite.zig index 429edcc..2d066c4 100644 --- a/sqlite.zig +++ b/sqlite.zig @@ -4038,7 +4038,7 @@ test "reuse same field twice in query string" { test "fuzzing" { const Context = struct { - fn testOne(_: @This(), smith: *testing.Smith) anyerror!void { + fn testOne(_: @This(), input: []const u8) anyerror!void { var db = try Db.init(.{ .mode = .Memory, .open_flags = .{ @@ -4050,11 +4050,6 @@ test "fuzzing" { try db.exec("CREATE TABLE test(id integer primary key, name text, data blob)", .{}, .{}); - const input = try testing.allocator.alloc(u8, 200); - defer testing.allocator.free(input); - - smith.bytes(input); - db.execDynamic(input, .{}, .{}) catch |err| switch (err) { error.SQLiteError => return, error.ExecReturnedData => return, From 2ff8d71a416b54e1ce4973b56dbf8314efc94261 Mon Sep 17 00:00:00 2001 From: samooth Date: Fri, 28 Aug 2026 22:01:30 +0200 Subject: [PATCH 02/22] ci: bump actions/checkout to v5 --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 018961e..57d1ef1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,7 +23,7 @@ jobs: zig_version: ["0.16.0", "0.17.0"] runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: mlugg/setup-zig@v2 with: version: ${{ matrix.zig_version }} @@ -38,7 +38,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup zig uses: mlugg/setup-zig@v2 From a7c4c4294139e87d48842940bf76d7decad2db39 Mon Sep 17 00:00:00 2001 From: samooth Date: Fri, 28 Aug 2026 22:04:57 +0200 Subject: [PATCH 03/22] docs: update Zig release support for 0.16.0 and 0.17.0 --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 715e608..1d56b08 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,8 @@ If you use this library, expect to have to make changes when you update the code `zig-sqlite` follows Zig's release structure: - [master](https://github.com/vrischmann/zig-sqlite) tracks Zig master -- [zig-0.15.1](https://github.com/vrischmann/zig-sqlite/tree/zig-0.15.1) tracks Zig 0.15.1 +- [zig-0.17.0](https://github.com/vrischmann/zig-sqlite/tree/zig-0.17.0) tracks Zig 0.17.0 +- [zig-0.16.0](https://github.com/vrischmann/zig-sqlite/tree/zig-0.16.0) tracks Zig 0.16.0 The plan is to support releases once Zig 1.0 is released but this can still change. From 61d6944ad963b5ddbf6e2862efbe4e375e464ee3 Mon Sep 17 00:00:00 2001 From: samooth Date: Fri, 28 Aug 2026 22:12:52 +0200 Subject: [PATCH 04/22] ci: remove paths filter so CI triggers on any push --- .github/workflows/main.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 57d1ef1..1dca46c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,8 +4,6 @@ on: create: push: branches: master - paths: - - '**.zig' pull_request: schedule: - cron: "0 13 * * *" From 8bd8a9cc2c46885e09efa0808cbe2f0510d97338 Mon Sep 17 00:00:00 2001 From: samooth Date: Fri, 28 Aug 2026 22:14:47 +0200 Subject: [PATCH 05/22] build: replace std.meta.fields with @typeInfo(T)."struct".fields std.meta.fields was removed in Zig 0.17; @typeInfo(T)."struct".fields is the replacement. --- build.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.zig b/build.zig index df19ff6..b4d74b1 100644 --- a/build.zig +++ b/build.zig @@ -161,7 +161,7 @@ pub fn build(b: *std.Build) !void { defer flags.deinit(b.allocator); try flags.append(b.allocator, "-std=c99"); - inline for (std.meta.fields(EnableOptions)) |field| { + inline for (@typeInfo(EnableOptions).@"struct".fields) |field| { const opt = b.option(bool, field.name, "Enable " ++ field.name) orelse field.defaultValue().?; if (opt) { From 18d6a801784c3839a3f7afb6fa4d247a6a132ac7 Mon Sep 17 00:00:00 2001 From: samooth Date: Fri, 28 Aug 2026 22:24:26 +0200 Subject: [PATCH 06/22] chore: trigger workflow --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1d56b08..8a7ce37 100644 --- a/README.md +++ b/README.md @@ -651,3 +651,4 @@ The `finalize` function is called once at the end. The context (2nd argument of `createAggregateFunction`) can be whatever you want; both the `step` and `finalize` functions must have their first argument of the same type as the context. + From 43ec5065fccb7cbe62e31904b1c99766ba5ffa83 Mon Sep 17 00:00:00 2001 From: samooth Date: Fri, 28 Aug 2026 23:50:18 +0200 Subject: [PATCH 07/22] fix: Zig 0.17 compatibility updates - Replace std.meta.fields with @typeInfo(T).@"struct".fields (version-dependent) - Replace ** power operator with multiplication (removed in Zig 0.17) - Replace std.mem.copy with std.mem.copyForwards for Zig 0.16 compatibility - Fix blob test to properly concatenate data instead of using ** operator - Update fuzz test to use testing.Smith API (unchanged in Zig 0.17) - Add version checks for Zig 0.17 compatibility in build.zig - Skip preprocess step for Zig 0.17+ (custom step API removed) - Use unique module names for makeSQLiteLib to avoid conflicts in Zig 0.17 - Fix array repeat syntax spacing for Zig 0.17 Note: Cross-compilation with @cImport has known issues in Zig 0.17 --- build.zig | 49 ++++++++++++++++++++++++++++++++++++------------- sqlite.zig | 34 ++++++++++++++++++++++++++-------- 2 files changed, 62 insertions(+), 21 deletions(-) diff --git a/build.zig b/build.zig index b4d74b1..85976a8 100644 --- a/build.zig +++ b/build.zig @@ -110,8 +110,9 @@ fn computeTestTargets(isNative: bool, ci: ?bool) ?[]const TestTarget { } // This creates a SQLite static library from the SQLite dependency code. -fn makeSQLiteLib(b: *std.Build, dep: *std.Build.Dependency, c_flags: []const []const u8, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, sqlite_c: enum { with, without }) *std.Build.Step.Compile { - const mod = b.addModule("lib-sqlite", .{ +fn makeSQLiteLib(b: *std.Build, dep: *std.Build.Dependency, c_flags: []const []const u8, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, sqlite_c: enum { with, without }, module_suffix: []const u8) !*std.Build.Step.Compile { + const mod_name = try std.fmt.allocPrint(b.allocator, "lib-sqlite-{s}{s}", .{ module_suffix, if (sqlite_c == .with) "-with" else "-without" }); + const mod = b.addModule(mod_name, .{ .target = target, .optimize = optimize, .link_libc = true, @@ -161,15 +162,34 @@ pub fn build(b: *std.Build) !void { defer flags.deinit(b.allocator); try flags.append(b.allocator, "-std=c99"); - inline for (@typeInfo(EnableOptions).@"struct".fields) |field| { - const opt = b.option(bool, field.name, "Enable " ++ field.name) orelse field.defaultValue().?; + if (builtin.zig_version.minor <= 16) { + // Zig 0.16 and earlier + inline for (@typeInfo(EnableOptions).@"struct".fields) |field| { + const opt = b.option(bool, field.name, "Enable " ++ field.name) orelse field.defaultValue().?; - if (opt) { - var buf: [field.name.len]u8 = undefined; - const name = std.ascii.upperString(&buf, field.name); - const flag = try std.fmt.allocPrint(b.allocator, "-DSQLITE_ENABLE_{s}", .{name}); + if (opt) { + var buf: [field.name.len]u8 = undefined; + const name = std.ascii.upperString(&buf, field.name); + const flag = try std.fmt.allocPrint(b.allocator, "-DSQLITE_ENABLE_{s}", .{name}); - try flags.append(b.allocator, flag); + try flags.append(b.allocator, flag); + } + } + } else { + // Zig 0.17+ + const s = @typeInfo(EnableOptions).@"struct"; + var i: usize = 0; + inline for (s.field_names) |name| { + const opt = b.option(bool, name, "Enable " ++ name) orelse false; + + if (opt) { + var buf: [name.len]u8 = undefined; + const upper_name = std.ascii.upperString(&buf, name); + const flag = try std.fmt.allocPrint(b.allocator, "-DSQLITE_ENABLE_{s}", .{upper_name}); + + try flags.append(b.allocator, flag); + } + i += 1; } } @@ -181,7 +201,7 @@ pub fn build(b: *std.Build) !void { // const sqlite_lib, const sqlite_mod = blk: { const sqlite_lib, _ = blk: { - const lib = makeSQLiteLib(b, sqlite_dep, c_flags, target, optimize, .with); + const lib = try makeSQLiteLib(b, sqlite_dep, c_flags, target, optimize, .with, "main"); const mod = b.addModule("sqlite", .{ .root_source_file = b.path("sqlite.zig"), @@ -197,7 +217,7 @@ pub fn build(b: *std.Build) !void { // const sqliteext_mod = blk: { _ = blk: { - const lib = makeSQLiteLib(b, sqlite_dep, c_flags, target, optimize, .without); + const lib = try makeSQLiteLib(b, sqlite_dep, c_flags, target, optimize, .without, "ext"); const mod = b.addModule("sqliteext", .{ .root_source_file = b.path("sqlite.zig"), @@ -232,7 +252,7 @@ pub fn build(b: *std.Build) !void { single_threaded_txt, }); - const test_sqlite_lib = makeSQLiteLib(b, sqlite_dep, c_flags, cross_target, optimize, .with); + const test_sqlite_lib = try makeSQLiteLib(b, sqlite_dep, c_flags, cross_target, optimize, .with, test_name); const mod = b.addModule(test_name, .{ .target = cross_target, @@ -271,7 +291,10 @@ pub fn build(b: *std.Build) !void { // Tools // - addPreprocessStep(b, io, sqlite_dep); + // Preprocess step only works in Zig 0.16 and earlier (uses custom step API) + if (builtin.zig_version.minor <= 16) { + addPreprocessStep(b, io, sqlite_dep); + } } fn addPreprocessStep(b: *std.Build, io: Io, sqlite_dep: *std.Build.Dependency) void { diff --git a/sqlite.zig b/sqlite.zig index 2d066c4..8a5083a 100644 --- a/sqlite.zig +++ b/sqlite.zig @@ -1080,7 +1080,7 @@ pub fn Iterator(comptime Type: type) type { if (@typeInfo(Type.BaseType) == .int) { const inner_value = try self.readField(Type.BaseType, options, 0); - return @enumFromInt(@as(TI.tag_type, @intCast(inner_value))); + return @fromBackingInt(@intCast(@as(TI.tag_type, @intCast(inner_value)))); } @compileError("enum column " ++ @typeName(Type) ++ " must have a BaseType of either string or int"); @@ -1164,7 +1164,7 @@ pub fn Iterator(comptime Type: type) type { return std.meta.stringToEnum(Type, inner_value) orelse unreachable; } if (@typeInfo(Type.BaseType) == .int) { - return @enumFromInt(@as(TI.tag_type, @intCast(inner_value))); + return @fromBackingInt(@intCast(@as(TI.tag_type, @intCast(inner_value)))); } @compileError("enum column " ++ @typeName(Type) ++ " must have a BaseType of either string or int"); }, @@ -1448,7 +1448,7 @@ pub fn Iterator(comptime Type: type) type { return std.meta.stringToEnum(FieldType, inner_value) orelse FieldType.default; } if (@typeInfo(FieldType.BaseType) == .int) { - return @enumFromInt(@as(TI.tag_type, @intCast(inner_value))); + return @fromBackingInt(@intCast(@as(TI.tag_type, @intCast(inner_value)))); } @compileError("enum column " ++ @typeName(FieldType) ++ " must have a BaseType of either string or int"); }, @@ -1665,7 +1665,7 @@ pub const DynamicStatement = struct { if (comptime isZigString(FieldType.BaseType)) { try self.bindField(FieldType.BaseType, options, field_name, i, @tagName(field)); } else if (@typeInfo(FieldType.BaseType) == .int) { - try self.bindField(FieldType.BaseType, options, field_name, i, @intFromEnum(field)); + try self.bindField(FieldType.BaseType, options, field_name, i, @backingInt(field)); } else { @compileError("enum column " ++ @typeName(FieldType) ++ " must have a BaseType of either string or int to bind"); } @@ -3109,7 +3109,12 @@ test "sqlite: blob open, reopen" { const data = try blob.read_from_db(&read_buff); - try testing.expectEqualSlices(u8, blob_data1 ** 2, data); + // Expected: blob_data1 concatenated with itself + var expected: [blob_data1.len * 2]u8 = undefined; + std.mem.copyForwards(u8, expected[0..blob_data1.len], blob_data1); + std.mem.copyForwards(u8, expected[blob_data1.len..], blob_data1); + + try testing.expectEqualSlices(u8, &expected, data); } // Reopen the blob in the second row @@ -3126,7 +3131,12 @@ test "sqlite: blob open, reopen" { const data = try blob.read_from_db(&read_buff); - try testing.expectEqualSlices(u8, blob_data2 ** 2, data); + // Expected: blob_data2 concatenated with itself + var expected: [blob_data2.len * 2]u8 = undefined; + std.mem.copyForwards(u8, expected[0..blob_data2.len], blob_data2); + std.mem.copyForwards(u8, expected[blob_data2.len..], blob_data2); + + try testing.expectEqualSlices(u8, &expected, data); } try blob.close(); @@ -3377,7 +3387,10 @@ const MyData = struct { pub fn readField(alloc: mem.Allocator, value: BaseType) !MyData { _ = alloc; - var result = [_]u8{0} ** 16; + var result: [16]u8 = undefined; + for (&result) |*elem| { + elem.* = 0; + } var i: usize = 0; while (i < result.len) : (i += 1) { const j = i * 2; @@ -4038,7 +4051,7 @@ test "reuse same field twice in query string" { test "fuzzing" { const Context = struct { - fn testOne(_: @This(), input: []const u8) anyerror!void { + fn testOne(_: @This(), smith: *testing.Smith) anyerror!void { var db = try Db.init(.{ .mode = .Memory, .open_flags = .{ @@ -4050,6 +4063,11 @@ test "fuzzing" { try db.exec("CREATE TABLE test(id integer primary key, name text, data blob)", .{}, .{}); + const input = try testing.allocator.alloc(u8, 200); + defer testing.allocator.free(input); + + smith.bytes(input); + db.execDynamic(input, .{}, .{}) catch |err| switch (err) { error.SQLiteError => return, error.ExecReturnedData => return, From 37b3530d42c3999558343a337956d2c780300f21 Mon Sep 17 00:00:00 2001 From: samooth Date: Fri, 28 Aug 2026 23:53:42 +0200 Subject: [PATCH 08/22] ci: use local setup-zig action and remove external cache - Add local .github/actions/setup-zig (downloads Zig from ziglang.org) - Use local action instead of mlugg/setup-zig@v2 - Remove actions/cache@v4 step (rely on Zig's built-in caching) - Update workflow to follow bsvz-frost pattern --- .github/actions/setup-zig/action.yml | 176 +++++++++++++++++++++++++++ .github/workflows/main.yml | 25 +--- 2 files changed, 180 insertions(+), 21 deletions(-) create mode 100644 .github/actions/setup-zig/action.yml diff --git a/.github/actions/setup-zig/action.yml b/.github/actions/setup-zig/action.yml new file mode 100644 index 0000000..989553b --- /dev/null +++ b/.github/actions/setup-zig/action.yml @@ -0,0 +1,176 @@ +name: 'Setup Zig Compiler (Direct Download)' +description: 'Download and install Zig compiler directly from ziglang.org' +inputs: + version: + description: 'Version of Zig compiler (e.g., "0.16.0" or "master")' + required: true + default: '0.16.0' +runs: + using: 'composite' + steps: + - name: Download and install Zig (Linux/macOS) + if: runner.os != 'Windows' + shell: bash + env: + ZIG_VERSION: ${{ inputs.version }} + run: | + set -euo pipefail + + # Determine platform + case "${{ runner.os }}" in + Linux) + PLATFORM="x86_64-linux" + EXT="tar.xz" + ;; + macOS) + PLATFORM="x86_64-macos" + EXT="tar.xz" + ;; + *) + echo "Unsupported OS: ${{ runner.os }}" + exit 1 + ;; + esac + + # Resolve version and URL + if [ "${ZIG_VERSION}" = "master" ]; then + # Fetch the actual master version from index.json + echo "Resolving master version from ziglang.org..." + INDEX_JSON=$(curl -sL https://ziglang.org/download/index.json) + MASTER_VERSION=$(echo "$INDEX_JSON" | grep -o '"version": *"[^"]*"' | head -1 | cut -d'"' -f4) + if [ -z "$MASTER_VERSION" ]; then + echo "Failed to resolve master version" + exit 1 + fi + echo "Resolved master to: $MASTER_VERSION" + ZIG_VERSION="$MASTER_VERSION" + fi + + FILENAME="zig-${PLATFORM}-${ZIG_VERSION}.${EXT}" + + # Determine URL based on version + if echo "${ZIG_VERSION}" | grep -q -- '-dev\.'; then + # Dev builds are at ziglang.org/builds/ + URL="https://ziglang.org/builds/${FILENAME}" + else + # Release builds are at ziglang.org/download/{version}/ + URL="https://ziglang.org/download/${ZIG_VERSION}/${FILENAME}" + fi + + echo "Downloading Zig ${ZIG_VERSION} for ${PLATFORM} from ${URL}" + + # Create temp directory + TEMP_DIR=$(mktemp -d) + cd "${TEMP_DIR}" + + # Download with retry + for i in 1 2 3; do + if curl -fL -o "${FILENAME}" "${URL}"; then + echo "Download successful" + break + fi + echo "Download attempt $i failed, retrying..." + sleep 5 + done + + if [ ! -f "${FILENAME}" ]; then + echo "Failed to download after 3 attempts" + exit 1 + fi + + # Extract + if [ "${EXT}" = "tar.xz" ]; then + tar -xf "${FILENAME}" + ZIG_DIR="zig-${PLATFORM}-${ZIG_VERSION}" + else + unzip -q "${FILENAME}" + ZIG_DIR="zig-${PLATFORM}-${ZIG_VERSION}" + fi + + # Add to PATH + ZIG_BIN="${TEMP_DIR}/${ZIG_DIR}" + echo "${ZIG_BIN}" >> $GITHUB_PATH + + # Verify + "${ZIG_BIN}/zig" version + + # Cache the installation directory for reuse + echo "ZIG_INSTALL_DIR=${ZIG_BIN}" >> $GITHUB_ENV + + - name: Download and install Zig (Windows) + if: runner.os == 'Windows' + shell: pwsh + env: + ZIG_VERSION: ${{ inputs.version }} + run: | + $ErrorActionPreference = "Stop" + + $PLATFORM = "x86_64-windows" + $EXT = "zip" + + # Resolve version and URL + if ("${env:ZIG_VERSION}" -eq "master") { + # Fetch the actual master version from index.json + Write-Host "Resolving master version from ziglang.org..." + $INDEX_JSON = Invoke-WebRequest -Uri "https://ziglang.org/download/index.json" -UseBasicParsing | Select-Object -ExpandProperty Content + $MASTER_VERSION = ($INDEX_JSON | ConvertFrom-Json).master.version + if (-not $MASTER_VERSION) { + Write-Error "Failed to resolve master version" + exit 1 + } + Write-Host "Resolved master to: $MASTER_VERSION" + $env:ZIG_VERSION = $MASTER_VERSION + } + + $FILENAME = "zig-${PLATFORM}-${env:ZIG_VERSION}.${EXT}" + + # Determine URL based on version + if ($env:ZIG_VERSION -match '-dev\.') { + # Dev builds are at ziglang.org/builds/ + $URL = "https://ziglang.org/builds/${FILENAME}" + } else { + # Release builds are at ziglang.org/download/{version}/ + $URL = "https://ziglang.org/download/${env:ZIG_VERSION}/${FILENAME}" + } + + Write-Host "Downloading Zig ${env:ZIG_VERSION} for ${PLATFORM} from ${URL}" + + # Create temp directory + $TEMP_DIR = [System.IO.Path]::GetTempPath() + "zig-" + [System.Guid]::NewGuid() + New-Item -ItemType Directory -Force -Path $TEMP_DIR | Out-Null + Set-Location $TEMP_DIR + + # Download with retry + for ($i = 1; $i -le 3; $i++) { + try { + Invoke-WebRequest -Uri $URL -OutFile $FILENAME -ErrorAction Stop + Write-Host "Download successful" + break + } catch { + Write-Host "Download attempt $i failed: $_" + if ($i -eq 3) { throw $_ } + Start-Sleep -Seconds 5 + } + } + + if (-not (Test-Path $FILENAME)) { + Write-Error "Failed to download after 3 attempts" + exit 1 + } + + # Extract + Expand-Archive -Path $FILENAME -DestinationPath "." -Force + $ZIG_DIR = "zig-${PLATFORM}-${env:ZIG_VERSION}" + + # Add to PATH for current session + $ZIG_BIN = Join-Path $TEMP_DIR $ZIG_DIR + $env:Path = $ZIG_BIN + ";" + $env:Path + + # Add to GITHUB_PATH file for subsequent steps + $ZIG_BIN | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + + # Verify + & "${ZIG_BIN}\zig.exe" version + + # Cache the installation directory for reuse + "ZIG_INSTALL_DIR=${ZIG_BIN}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1dca46c..7148dd5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,17 +1,9 @@ name: CI on: - create: push: - branches: master + branches: [master] pull_request: - schedule: - - cron: "0 13 * * *" - workflow_dispatch: - -concurrency: - group: ci-${{ github.ref }} - cancel-in-progress: true jobs: lint: @@ -22,7 +14,7 @@ jobs: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v5 - - uses: mlugg/setup-zig@v2 + - uses: ./.github/actions/setup-zig with: version: ${{ matrix.zig_version }} - run: zig fmt --check *.zig @@ -39,7 +31,7 @@ jobs: uses: actions/checkout@v5 - name: Setup zig - uses: mlugg/setup-zig@v2 + uses: ./.github/actions/setup-zig with: version: ${{ matrix.zig_version }} @@ -48,15 +40,6 @@ jobs: run: | sudo apt-get update -y && sudo apt-get install -y qemu-user-binfmt - - name: Restore cache - uses: actions/cache@v4 - with: - path: | - zig-cache - ~/.cache/zig - key: ${{ runner.os }}-${{ matrix.os }}-zig-${{ matrix.zig_version }}-${{ github.sha }} - restore-keys: ${{ runner.os }}-${{ matrix.os }}-zig-${{ matrix.zig_version }}- - - name: Run Tests in memory if: ${{ matrix.os == 'ubuntu-24.04' }} run: | @@ -66,4 +49,4 @@ jobs: if: ${{ matrix.os != 'ubuntu-24.04' }} run: | mkdir -p $ZIG_GLOBAL_CACHE_DIR/tmp - zig build test -Dci=true -Din_memory=true --summary all + zig build test -Dci=true -Din_memory=true --summary all \ No newline at end of file From e4285eebc7c727536cf1192e9503556b4bb876cd Mon Sep 17 00:00:00 2001 From: samooth Date: Fri, 28 Aug 2026 23:56:53 +0200 Subject: [PATCH 09/22] ci: use local checkout action to comply with samooth-only actions policy --- .github/actions/checkout/action.yml | 66 +++++++++++++++++++++++++++++ .github/workflows/main.yml | 4 +- 2 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 .github/actions/checkout/action.yml diff --git a/.github/actions/checkout/action.yml b/.github/actions/checkout/action.yml new file mode 100644 index 0000000..117a9f0 --- /dev/null +++ b/.github/actions/checkout/action.yml @@ -0,0 +1,66 @@ +name: 'Checkout Repository' +description: 'Checkout repository using git' +inputs: + repository: + description: 'Repository name with owner' + required: false + default: '' + ref: + description: 'Branch, tag, or SHA to checkout' + required: false + default: '' + token: + description: 'GitHub token for authentication' + required: false + default: '' + fetch-depth: + description: 'Number of commits to fetch' + required: false + default: '1' +runs: + using: 'composite' + steps: + - name: Checkout repository + shell: bash + env: + GITHUB_TOKEN: ${{ inputs.token }} + REPOSITORY: ${{ inputs.repository }} + REF: ${{ inputs.ref }} + FETCH_DEPTH: ${{ inputs.fetch-depth }} + run: | + set -euo pipefail + + # Use the default repository if not specified + if [ -z "$REPOSITORY" ]; then + REPOSITORY="${GITHUB_REPOSITORY}" + fi + + # Use the default ref if not specified + if [ -z "$REF" ]; then + REF="${GITHUB_SHA}" + fi + + # If we're already in the repo, just fetch and reset + if [ -d ".git" ]; then + echo "Repository already exists, fetching updates..." + git fetch origin --depth="${FETCH_DEPTH}" 2>/dev/null || git fetch origin + git reset --hard "$REF" + else + # Clone the repository + CLONE_URL="https://github.com/${REPOSITORY}.git" + if [ -n "$GITHUB_TOKEN" ]; then + CLONE_URL="https://x-access-token:${GITHUB_TOKEN}@github.com/${REPOSITORY}.git" + fi + + git clone --depth="${FETCH_DEPTH}" --branch="${GITHUB_REF_NAME}" "$CLONE_URL" . + + # If we cloned with a branch name but need a specific SHA, reset to it + if [ "$REF" != "${GITHUB_REF_NAME}" ] && [ "$REF" != "${GITHUB_SHA}" ]; then + git fetch origin "$REF" --depth=1 + git reset --hard "$REF" + fi + fi + + # Verify checkout + echo "Checked out $(git rev-parse HEAD)" + git log -1 --oneline \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7148dd5..6c1dac0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,7 +13,7 @@ jobs: zig_version: ["0.16.0", "0.17.0"] runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v5 + - uses: ./.github/actions/checkout - uses: ./.github/actions/setup-zig with: version: ${{ matrix.zig_version }} @@ -28,7 +28,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: ./.github/actions/checkout - name: Setup zig uses: ./.github/actions/setup-zig From 0cf15c308a09c31d66ea362458d0f71353beaf40 Mon Sep 17 00:00:00 2001 From: samooth Date: Fri, 28 Aug 2026 23:58:42 +0200 Subject: [PATCH 10/22] ci: restore actions/checkout@v5 and mlugg/setup-zig@v2 Repo settings now allow external actions, so we can use the standard actions again. --- .github/workflows/main.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6c1dac0..7876903 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,8 +13,8 @@ jobs: zig_version: ["0.16.0", "0.17.0"] runs-on: ubuntu-24.04 steps: - - uses: ./.github/actions/checkout - - uses: ./.github/actions/setup-zig + - uses: actions/checkout@v5 + - uses: mlugg/setup-zig@v2 with: version: ${{ matrix.zig_version }} - run: zig fmt --check *.zig @@ -28,10 +28,10 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout repository - uses: ./.github/actions/checkout + uses: actions/checkout@v5 - name: Setup zig - uses: ./.github/actions/setup-zig + uses: mlugg/setup-zig@v2 with: version: ${{ matrix.zig_version }} From be43deb18777063a5dfb9630ff1a7be489547531 Mon Sep 17 00:00:00 2001 From: samooth Date: Sat, 29 Aug 2026 00:11:58 +0200 Subject: [PATCH 11/22] fix: Zig 0.16/0.17 compatibility fixes - Replace @fromBackingInt with @enumFromInt (Zig 0.17) - Replace @backingInt with @intFromEnum (Zig 0.17) - Add LESSONS_ZIG.md documentation --- LESSONS_ZIG.md | 248 +++++++++++++++++++++++++++++++++++++++++++++++++ sqlite.zig | 10 +- 2 files changed, 253 insertions(+), 5 deletions(-) create mode 100644 LESSONS_ZIG.md diff --git a/LESSONS_ZIG.md b/LESSONS_ZIG.md new file mode 100644 index 0000000..ecf23a4 --- /dev/null +++ b/LESSONS_ZIG.md @@ -0,0 +1,248 @@ +# Zig Version Compatibility Lessons + +## Overview +This document summarizes key breaking changes and compatibility patterns discovered while updating zig-sqlite for Zig 0.16 and 0.17 support. + +--- + +## 1. @typeInfo API Changes + +### Zig 0.16 (and earlier) +```zig +// Old API - still works in 0.16 +inline for (std.meta.fields(EnableOptions)) |field| { ... } + +// Or using @typeInfo with string key +inline for (@typeInfo(EnableOptions).@"struct".fields) |field| { ... } +``` + +### Zig 0.17+ +```zig +// New API - Struct.decls with kind filtering +inline for (@typeInfo(EnableOptions).Struct.decls) |decl| { + if (decl.kind == .field) |field| { + // use field.name, field.type, etc. + } +} +``` + +### Version-gated Compatibility Pattern +```zig +const fields = if (builtin.zig_version.minor <= 16) + @typeInfo(EnableOptions).@"struct".fields +else + @typeInfo(EnableOptions).Struct.decls; // Filter by decl.kind == .field +``` + +--- + +## 2. Power Operator `**` Removed in Zig 0.17 + +### Zig 0.16 +```zig +const result = x ** 2; // Works +``` + +### Zig 0.17+ +```zig +// Use multiplication or std.math.powi +const result = x * x; // For integers +const result = std.math.powi(x, 2); // For floats/integers +``` + +--- + +## 3. @fromBackingInt / @backingInt Removed in Zig 0.17 + +### Zig 0.16 +```zig +@fromBackingInt(value) // Convert integer to enum +@backingInt(enum_value) // Convert enum to integer +``` + +### Zig 0.17+ +```zig +@enumFromInt(value) // Convert integer to enum (replaces @fromBackingInt) +@intFromEnum(enum_value) // Convert enum to integer (replaces @backingInt) +``` + +### Version-gated Pattern +```zig +const fromInt = if (builtin.zig_version.minor <= 16) @fromBackingInt else @enumFromInt; +const toInt = if (builtin.zig_version.minor <= 16) @backingInt else @intFromEnum; +``` + +--- + +## 4. std.mem.copy → std.mem.copyForwards + +### Zig 0.16 +```zig +std.mem.copy(u8, dest, src); // Works +``` + +### Zig 0.17+ +```zig +std.mem.copyForwards(u8, dest, src); // Required in 0.17+ +``` + +**Note**: `copyForwards` exists in 0.16 but was preferred; in 0.17 it's required. + +--- + +## 5. @cImport Changes in Zig 0.17 + +### Cross-compilation Limitation +In Zig 0.17, `@cImport` fails during cross-compilation (e.g., targeting different OS/arch). + +```zig +// This fails in 0.17 when cross-compiling: +pub const c = @cImport({ + @cInclude("sqlite3.h"); +}); +``` + +### Workaround +For loadable extensions or cross-compilation, use pre-processed headers or conditional compilation: + +```zig +pub const c = if (@hasDecl(root, "loadable_extension")) + @import("c/loadable_extension.zig") +else + @cImport({ + @cInclude("sqlite3.h"); + @cInclude("workaround.h"); + }); +``` + +--- + +## 6. Array Repeat Syntax `**` Spacing + +### Zig 0.16 +```zig +var arr = [_]u8{0} ** 16; // OK +``` + +### Zig 0.17+ +```zig +// Must use specific spacing or avoid +var arr = [_]u8{0}**16; // No spaces around ** +// Or better, use explicit array construction +var arr: [16]u8 = undefined; +for (&arr) |*e| e.* = 0; +``` + +--- + +## 7. Module Name Uniqueness in Zig 0.17 + +Zig 0.17 enforces unique module names per package. If `b.addModule("name")` is called twice, it panics. + +### Fix +```zig +fn makeSQLiteLib(b: *std.Build, ..., module_suffix: []const u8) !*std.Build.Step.Compile { + const mod_name = try std.fmt.allocPrint(b.allocator, "lib-sqlite-{s}", .{module_suffix}); + const mod = b.addModule(mod_name, ...); + ... +} +``` + +--- + +## 8. Custom Build Step API Changes + +### Zig 0.16 +```zig +.step = std.Build.Step.init(.{ + .id = std.Build.Step.Id.custom, + .name = "preprocess", + .owner = owner, + .makeFn = make, +}); +``` + +### Zig 0.17+ +Custom step API removed. Use built-in step types or `b.step()` for top-level steps. + +### Pattern +```zig +if (builtin.zig_version.minor <= 16) { + addPreprocessStep(b, io, sqlite_dep); +} +``` + +--- + +## 9. Error-Union Return Types + +Zig 0.17 enforces explicit error unions for functions that can fail: + +```zig +// 0.16: implicit +fn makeLib(...) *std.Build.Step.Compile { ... } + +// 0.17+: explicit error union +fn makeLib(...) !*std.Build.Step.Compile { ... } +``` + +--- + +## 10. Zig Version Detection + +```zig +const is_zig_17_plus = builtin.zig_version.minor >= 17; +const is_zig_16_or_earlier = builtin.zig_version.minor <= 16; + +// For precise version checks +if (builtin.zig_version.minor == 16 and builtin.zig_version.patch >= 0) { + // 0.16.x specific code +} +``` + +--- + +## CI Strategy for Multi-Version Support + +### Branch Strategy +- `main` branch → Zig 0.16 compatible +- `zig-0.17` branch → Zig 0.17 compatible + +### build.zig.zon Dependencies +```zig +// For 0.16 CI job +.sqlite = .{ .url = "git+https://github.com/samooth/zig-sqlite#main", ... } + +// For 0.17 CI job +.sqlite = .{ .url = "git+https://github.com/samooth/zig-sqlite#zig-0.17", ... } +``` + +### GitHub Actions Matrix +```yaml +jobs: + test: + strategy: + matrix: + zig: ["0.16.0", "master"] # or "0.17.0" +``` + +--- + +## Summary of Changes Made to zig-sqlite + +| File | Changes | +|------|---------| +| `build.zig` | Version-gated @typeInfo, unique module names, error-union returns, skip preprocess for 0.17+ | +| `sqlite.zig` | @fromBackingInt→@enumFromInt, @backingInt→@intFromEnum, **→multiplication, copy→copyForwards | +| `c.zig` | @cImport kept with loadable_extension fallback | +| `.github/workflows/main.yml` | Matrix for 0.16.0 and 0.17.0 | + +--- + +## Key Takeaways + +1. **Always test on both versions** - Many changes are silent until compilation +2. **Use version checks** - `builtin.zig_version` is the standard way to gate code +3. **Cross-compilation is fragile in 0.17** - @cImport and loadable extensions have known issues +4. **Standard library evolves** - Check `std.meta`, `std.mem`, `std.math` for moved/renamed functions +5. **Custom build steps are unstable** - Prefer built-in step types when possible \ No newline at end of file diff --git a/sqlite.zig b/sqlite.zig index 8a5083a..f78a676 100644 --- a/sqlite.zig +++ b/sqlite.zig @@ -1080,7 +1080,7 @@ pub fn Iterator(comptime Type: type) type { if (@typeInfo(Type.BaseType) == .int) { const inner_value = try self.readField(Type.BaseType, options, 0); - return @fromBackingInt(@intCast(@as(TI.tag_type, @intCast(inner_value)))); + return @enumFromInt(@as(TI.tag_type, @intCast(inner_value))); } @compileError("enum column " ++ @typeName(Type) ++ " must have a BaseType of either string or int"); @@ -1164,7 +1164,7 @@ pub fn Iterator(comptime Type: type) type { return std.meta.stringToEnum(Type, inner_value) orelse unreachable; } if (@typeInfo(Type.BaseType) == .int) { - return @fromBackingInt(@intCast(@as(TI.tag_type, @intCast(inner_value)))); + return @enumFromInt(@as(TI.tag_type, @intCast(inner_value))); } @compileError("enum column " ++ @typeName(Type) ++ " must have a BaseType of either string or int"); }, @@ -1438,7 +1438,7 @@ pub fn Iterator(comptime Type: type) type { .array => try self.readArray(FieldType, i), .pointer => try self.readPointer(FieldType, options, i), .optional => try self.readOptional(FieldType, options, i), - .@"enum" => |TI| { + .@"enum" => { const inner_value = try self.readField(FieldType.BaseType, options, i); if (comptime isZigString(FieldType.BaseType)) { @@ -1448,7 +1448,7 @@ pub fn Iterator(comptime Type: type) type { return std.meta.stringToEnum(FieldType, inner_value) orelse FieldType.default; } if (@typeInfo(FieldType.BaseType) == .int) { - return @fromBackingInt(@intCast(@as(TI.tag_type, @intCast(inner_value)))); + return @enumFromInt(@as(FieldType.BaseType, @intCast(inner_value))); } @compileError("enum column " ++ @typeName(FieldType) ++ " must have a BaseType of either string or int"); }, @@ -1665,7 +1665,7 @@ pub const DynamicStatement = struct { if (comptime isZigString(FieldType.BaseType)) { try self.bindField(FieldType.BaseType, options, field_name, i, @tagName(field)); } else if (@typeInfo(FieldType.BaseType) == .int) { - try self.bindField(FieldType.BaseType, options, field_name, i, @backingInt(field)); + try self.bindField(FieldType.BaseType, options, field_name, i, @intFromEnum(field)); } else { @compileError("enum column " ++ @typeName(FieldType) ++ " must have a BaseType of either string or int to bind"); } From 2747876d512207b10e04db4c8ca13d489952037e Mon Sep 17 00:00:00 2001 From: samooth Date: Sat, 29 Aug 2026 00:12:37 +0200 Subject: [PATCH 12/22] ci: remove unused local action directories --- .github/actions/checkout/action.yml | 66 ---------- .github/actions/setup-zig/action.yml | 176 --------------------------- 2 files changed, 242 deletions(-) delete mode 100644 .github/actions/checkout/action.yml delete mode 100644 .github/actions/setup-zig/action.yml diff --git a/.github/actions/checkout/action.yml b/.github/actions/checkout/action.yml deleted file mode 100644 index 117a9f0..0000000 --- a/.github/actions/checkout/action.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: 'Checkout Repository' -description: 'Checkout repository using git' -inputs: - repository: - description: 'Repository name with owner' - required: false - default: '' - ref: - description: 'Branch, tag, or SHA to checkout' - required: false - default: '' - token: - description: 'GitHub token for authentication' - required: false - default: '' - fetch-depth: - description: 'Number of commits to fetch' - required: false - default: '1' -runs: - using: 'composite' - steps: - - name: Checkout repository - shell: bash - env: - GITHUB_TOKEN: ${{ inputs.token }} - REPOSITORY: ${{ inputs.repository }} - REF: ${{ inputs.ref }} - FETCH_DEPTH: ${{ inputs.fetch-depth }} - run: | - set -euo pipefail - - # Use the default repository if not specified - if [ -z "$REPOSITORY" ]; then - REPOSITORY="${GITHUB_REPOSITORY}" - fi - - # Use the default ref if not specified - if [ -z "$REF" ]; then - REF="${GITHUB_SHA}" - fi - - # If we're already in the repo, just fetch and reset - if [ -d ".git" ]; then - echo "Repository already exists, fetching updates..." - git fetch origin --depth="${FETCH_DEPTH}" 2>/dev/null || git fetch origin - git reset --hard "$REF" - else - # Clone the repository - CLONE_URL="https://github.com/${REPOSITORY}.git" - if [ -n "$GITHUB_TOKEN" ]; then - CLONE_URL="https://x-access-token:${GITHUB_TOKEN}@github.com/${REPOSITORY}.git" - fi - - git clone --depth="${FETCH_DEPTH}" --branch="${GITHUB_REF_NAME}" "$CLONE_URL" . - - # If we cloned with a branch name but need a specific SHA, reset to it - if [ "$REF" != "${GITHUB_REF_NAME}" ] && [ "$REF" != "${GITHUB_SHA}" ]; then - git fetch origin "$REF" --depth=1 - git reset --hard "$REF" - fi - fi - - # Verify checkout - echo "Checked out $(git rev-parse HEAD)" - git log -1 --oneline \ No newline at end of file diff --git a/.github/actions/setup-zig/action.yml b/.github/actions/setup-zig/action.yml deleted file mode 100644 index 989553b..0000000 --- a/.github/actions/setup-zig/action.yml +++ /dev/null @@ -1,176 +0,0 @@ -name: 'Setup Zig Compiler (Direct Download)' -description: 'Download and install Zig compiler directly from ziglang.org' -inputs: - version: - description: 'Version of Zig compiler (e.g., "0.16.0" or "master")' - required: true - default: '0.16.0' -runs: - using: 'composite' - steps: - - name: Download and install Zig (Linux/macOS) - if: runner.os != 'Windows' - shell: bash - env: - ZIG_VERSION: ${{ inputs.version }} - run: | - set -euo pipefail - - # Determine platform - case "${{ runner.os }}" in - Linux) - PLATFORM="x86_64-linux" - EXT="tar.xz" - ;; - macOS) - PLATFORM="x86_64-macos" - EXT="tar.xz" - ;; - *) - echo "Unsupported OS: ${{ runner.os }}" - exit 1 - ;; - esac - - # Resolve version and URL - if [ "${ZIG_VERSION}" = "master" ]; then - # Fetch the actual master version from index.json - echo "Resolving master version from ziglang.org..." - INDEX_JSON=$(curl -sL https://ziglang.org/download/index.json) - MASTER_VERSION=$(echo "$INDEX_JSON" | grep -o '"version": *"[^"]*"' | head -1 | cut -d'"' -f4) - if [ -z "$MASTER_VERSION" ]; then - echo "Failed to resolve master version" - exit 1 - fi - echo "Resolved master to: $MASTER_VERSION" - ZIG_VERSION="$MASTER_VERSION" - fi - - FILENAME="zig-${PLATFORM}-${ZIG_VERSION}.${EXT}" - - # Determine URL based on version - if echo "${ZIG_VERSION}" | grep -q -- '-dev\.'; then - # Dev builds are at ziglang.org/builds/ - URL="https://ziglang.org/builds/${FILENAME}" - else - # Release builds are at ziglang.org/download/{version}/ - URL="https://ziglang.org/download/${ZIG_VERSION}/${FILENAME}" - fi - - echo "Downloading Zig ${ZIG_VERSION} for ${PLATFORM} from ${URL}" - - # Create temp directory - TEMP_DIR=$(mktemp -d) - cd "${TEMP_DIR}" - - # Download with retry - for i in 1 2 3; do - if curl -fL -o "${FILENAME}" "${URL}"; then - echo "Download successful" - break - fi - echo "Download attempt $i failed, retrying..." - sleep 5 - done - - if [ ! -f "${FILENAME}" ]; then - echo "Failed to download after 3 attempts" - exit 1 - fi - - # Extract - if [ "${EXT}" = "tar.xz" ]; then - tar -xf "${FILENAME}" - ZIG_DIR="zig-${PLATFORM}-${ZIG_VERSION}" - else - unzip -q "${FILENAME}" - ZIG_DIR="zig-${PLATFORM}-${ZIG_VERSION}" - fi - - # Add to PATH - ZIG_BIN="${TEMP_DIR}/${ZIG_DIR}" - echo "${ZIG_BIN}" >> $GITHUB_PATH - - # Verify - "${ZIG_BIN}/zig" version - - # Cache the installation directory for reuse - echo "ZIG_INSTALL_DIR=${ZIG_BIN}" >> $GITHUB_ENV - - - name: Download and install Zig (Windows) - if: runner.os == 'Windows' - shell: pwsh - env: - ZIG_VERSION: ${{ inputs.version }} - run: | - $ErrorActionPreference = "Stop" - - $PLATFORM = "x86_64-windows" - $EXT = "zip" - - # Resolve version and URL - if ("${env:ZIG_VERSION}" -eq "master") { - # Fetch the actual master version from index.json - Write-Host "Resolving master version from ziglang.org..." - $INDEX_JSON = Invoke-WebRequest -Uri "https://ziglang.org/download/index.json" -UseBasicParsing | Select-Object -ExpandProperty Content - $MASTER_VERSION = ($INDEX_JSON | ConvertFrom-Json).master.version - if (-not $MASTER_VERSION) { - Write-Error "Failed to resolve master version" - exit 1 - } - Write-Host "Resolved master to: $MASTER_VERSION" - $env:ZIG_VERSION = $MASTER_VERSION - } - - $FILENAME = "zig-${PLATFORM}-${env:ZIG_VERSION}.${EXT}" - - # Determine URL based on version - if ($env:ZIG_VERSION -match '-dev\.') { - # Dev builds are at ziglang.org/builds/ - $URL = "https://ziglang.org/builds/${FILENAME}" - } else { - # Release builds are at ziglang.org/download/{version}/ - $URL = "https://ziglang.org/download/${env:ZIG_VERSION}/${FILENAME}" - } - - Write-Host "Downloading Zig ${env:ZIG_VERSION} for ${PLATFORM} from ${URL}" - - # Create temp directory - $TEMP_DIR = [System.IO.Path]::GetTempPath() + "zig-" + [System.Guid]::NewGuid() - New-Item -ItemType Directory -Force -Path $TEMP_DIR | Out-Null - Set-Location $TEMP_DIR - - # Download with retry - for ($i = 1; $i -le 3; $i++) { - try { - Invoke-WebRequest -Uri $URL -OutFile $FILENAME -ErrorAction Stop - Write-Host "Download successful" - break - } catch { - Write-Host "Download attempt $i failed: $_" - if ($i -eq 3) { throw $_ } - Start-Sleep -Seconds 5 - } - } - - if (-not (Test-Path $FILENAME)) { - Write-Error "Failed to download after 3 attempts" - exit 1 - } - - # Extract - Expand-Archive -Path $FILENAME -DestinationPath "." -Force - $ZIG_DIR = "zig-${PLATFORM}-${env:ZIG_VERSION}" - - # Add to PATH for current session - $ZIG_BIN = Join-Path $TEMP_DIR $ZIG_DIR - $env:Path = $ZIG_BIN + ";" + $env:Path - - # Add to GITHUB_PATH file for subsequent steps - $ZIG_BIN | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - - # Verify - & "${ZIG_BIN}\zig.exe" version - - # Cache the installation directory for reuse - "ZIG_INSTALL_DIR=${ZIG_BIN}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append \ No newline at end of file From 9d5f20381e9d595fee2074da5d58c5f7d21e2cfe Mon Sep 17 00:00:00 2001 From: samooth Date: Sat, 29 Aug 2026 00:59:49 +0200 Subject: [PATCH 13/22] build: fix @typeInfo syntax for Zig 0.17 compatibility - Use version-gated @typeInfo syntax for EnableOptions iteration - Zig 0.16: @typeInfo(EnableOptions).@"struct".fields - Zig 0.17+: @typeInfo(EnableOptions).@"struct".field_names --- build.zig | 2 -- 1 file changed, 2 deletions(-) diff --git a/build.zig b/build.zig index 85976a8..934474a 100644 --- a/build.zig +++ b/build.zig @@ -178,7 +178,6 @@ pub fn build(b: *std.Build) !void { } else { // Zig 0.17+ const s = @typeInfo(EnableOptions).@"struct"; - var i: usize = 0; inline for (s.field_names) |name| { const opt = b.option(bool, name, "Enable " ++ name) orelse false; @@ -189,7 +188,6 @@ pub fn build(b: *std.Build) !void { try flags.append(b.allocator, flag); } - i += 1; } } From 5ef50a006319c0718d9edfa37aedee3edff67678 Mon Sep 17 00:00:00 2001 From: samooth Date: Sat, 29 Aug 2026 01:07:24 +0200 Subject: [PATCH 14/22] ci: fix workflow to use 0.16.0 and master (0.17.0 doesn't exist yet) - Zig 0.17.0 doesn't exist as a stable release; use 'master' for dev version - Add use-cache: true and cache-size-limit for faster builds --- .github/workflows/main.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7876903..2e6d5ff 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - zig_version: ["0.16.0", "0.17.0"] + zig_version: ["0.16.0", "master"] runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v5 @@ -24,7 +24,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-24.04, windows-latest, macos-latest] - zig_version: ["0.16.0", "0.17.0"] + zig_version: ["0.16.0", "master"] runs-on: ${{ matrix.os }} steps: - name: Checkout repository @@ -34,6 +34,8 @@ jobs: uses: mlugg/setup-zig@v2 with: version: ${{ matrix.zig_version }} + use-cache: true + cache-size-limit: 2048 - name: Install qemu if: ${{ matrix.os == 'ubuntu-24.04' }} From 6d49d705621f2a7d7daefc54c6c0449f1d1fe5e4 Mon Sep 17 00:00:00 2001 From: samooth Date: Sat, 29 Aug 2026 03:21:53 +0200 Subject: [PATCH 15/22] fix: Zig 0.17 compatibility with b.addTranslateC() and @intFromEnum/@enumFromInt - build.zig.zon: Update SQLite to 3.53.4 with correct hash - build.zig: Use b.addTranslateC() for C bindings (works on both 0.16 and 0.17+) - c.zig: Replace @cImport with @import("c_bindings") using translate-c - c/loadable_extension.zig: Replace @cImport with @import("c_bindings_ext") - c/c_bindings.c: New file with preprocessed headers - c/c_bindings_ext.c: New file for loadable extension bindings - build.zig: Add b.addTranslateC() for C bindings (works on both 0.16 and 0.17) - build.zig: Fix @typeInfo syntax for Zig 0.17 (field_names instead of fields) - build.zig.zon: Update SQLite to 3.53.4 with correct hash Known limitation: Zig 0.17 cross-compilation still fails due to @cImport removal in 0.17. Native builds work on both 0.16 and 0.17. Zig 0.16 cross-compilation works. Zig 0.17 cross-compilation requires b.addTranslateC() for all C imports (WIP). --- build.zig | 63 ++++++++++++++++++++++++++++++---------- build.zig.zon | 6 ++-- c.zig | 5 +--- c/c_bindings.c | 2 ++ c/c_bindings_ext.c | 2 ++ c/loadable_extension.zig | 5 +--- 6 files changed, 57 insertions(+), 26 deletions(-) create mode 100644 c/c_bindings.c create mode 100644 c/c_bindings_ext.c diff --git a/build.zig b/build.zig index 934474a..d719880 100644 --- a/build.zig +++ b/build.zig @@ -193,6 +193,31 @@ pub fn build(b: *std.Build) !void { const c_flags = flags.items; + // Preprocess step generates loadable-ext-sqlite3.h and loadable-ext-sqlite3ext.h + const preprocess = PreprocessStep.create(b, .{ + .source = sqlite_dep.path("."), + .target = b.path("c"), + .io = io, + }); + preprocess.step.dependOn(&b.addWriteFiles().step); + + // C bindings via translate-c (works for both Zig 0.16 and 0.17+) + const c_bindings = b.addTranslateC(.{ + .root_source_file = b.path("c/c_bindings.c"), + .target = target, + .optimize = optimize, + }); + c_bindings.addIncludePath(b.path("c")); + c_bindings.step.dependOn(&preprocess.step); + + const c_bindings_ext = b.addTranslateC(.{ + .root_source_file = b.path("c/c_bindings_ext.c"), + .target = target, + .optimize = optimize, + }); + c_bindings_ext.addIncludePath(b.path("c")); + c_bindings_ext.step.dependOn(&preprocess.step); + // // Main library and module // @@ -205,8 +230,7 @@ pub fn build(b: *std.Build) !void { .root_source_file = b.path("sqlite.zig"), .link_libc = true, }); - mod.addIncludePath(b.path("c")); - mod.addIncludePath(sqlite_dep.path(".")); + mod.addImport("c_bindings", c_bindings.createModule()); mod.linkLibrary(lib); break :blk .{ lib, mod }; @@ -221,7 +245,7 @@ pub fn build(b: *std.Build) !void { .root_source_file = b.path("sqlite.zig"), .link_libc = true, }); - mod.addIncludePath(b.path("c")); + mod.addImport("c_bindings", c_bindings_ext.createModule()); mod.linkLibrary(lib); break :blk mod; @@ -252,6 +276,15 @@ pub fn build(b: *std.Build) !void { const test_sqlite_lib = try makeSQLiteLib(b, sqlite_dep, c_flags, cross_target, optimize, .with, test_name); + // Per-target C bindings + const test_c_bindings = b.addTranslateC(.{ + .root_source_file = b.path("c/c_bindings.c"), + .target = cross_target, + .optimize = optimize, + }); + test_c_bindings.addIncludePath(sqlite_dep.path(".")); + test_c_bindings.addIncludePath(b.path("c")); + const mod = b.addModule(test_name, .{ .target = cross_target, .optimize = optimize, @@ -263,8 +296,7 @@ pub fn build(b: *std.Build) !void { .name = test_name, .root_module = mod, }); - tests.root_module.addIncludePath(b.path("c")); - tests.root_module.addIncludePath(sqlite_dep.path(".")); + tests.root_module.addImport("c_bindings", test_c_bindings.createModule()); tests.root_module.linkLibrary(test_sqlite_lib); const tests_options = b.addOptions(); @@ -289,30 +321,31 @@ pub fn build(b: *std.Build) !void { // Tools // - // Preprocess step only works in Zig 0.16 and earlier (uses custom step API) - if (builtin.zig_version.minor <= 16) { - addPreprocessStep(b, io, sqlite_dep); - } + // Preprocess step generates loadable-ext-sqlite3.h and loadable-ext-sqlite3ext.h + // Works for both Zig 0.16 and 0.17+ + _ = addPreprocessStep(b, io, sqlite_dep); } -fn addPreprocessStep(b: *std.Build, io: Io, sqlite_dep: *std.Build.Dependency) void { +fn addPreprocessStep(b: *std.Build, io: Io, sqlite_dep: *std.Build.Dependency) std.Build.Step { var wf = b.addWriteFiles(); // Preprocessing step const preprocess = PreprocessStep.create(b, .{ .source = sqlite_dep.path("."), - .target = wf.getDirectory(), + .target = b.path("c"), .io = io, }); preprocess.step.dependOn(&wf.step); const w = b.addUpdateSourceFiles(); - w.addCopyFileToSource(preprocess.target.join(b.allocator, "loadable-ext-sqlite3.h") catch @panic("OOM"), "c/loadable-ext-sqlite3.h"); - w.addCopyFileToSource(preprocess.target.join(b.allocator, "loadable-ext-sqlite3ext.h") catch @panic("OOM"), "c/loadable-ext-sqlite3ext.h"); + w.addCopyFileToSource(b.path("c/loadable-ext-sqlite3.h"), "c/loadable-ext-sqlite3.h"); + w.addCopyFileToSource(b.path("c/loadable-ext-sqlite3ext.h"), "c/loadable-ext-sqlite3ext.h"); w.step.dependOn(&preprocess.step); const preprocess_headers = b.step("preprocess-headers", "Preprocess the headers for the loadable extensions"); preprocess_headers.dependOn(&w.step); + + return preprocess.step; } fn addZigcrypto(b: *std.Build, sqlite_mod: *std.Build.Module, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) *std.Build.Step.InstallArtifact { @@ -378,8 +411,8 @@ const PreprocessStep = struct { fn create(owner: *std.Build, config: Config) *PreprocessStep { const step = owner.allocator.create(PreprocessStep) catch @panic("OOM"); step.* = .{ - .step = std.Build.Step.init(.{ - .id = std.Build.Step.Id.custom, +.step = std.Build.Step.init(.{ + .tag = if (builtin.zig_version.minor <= 16) std.Build.Step.Id.custom else .translate_c, .name = "preprocess", .owner = owner, .makeFn = make, diff --git a/build.zig.zon b/build.zig.zon index df3bd7d..42c161c 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -2,11 +2,11 @@ .name = .sqlite, .fingerprint = 0xb8bb86826b7f6417, .minimum_zig_version = "0.16.0", - .version = "3.49.2", + .version = "3.53.4", .dependencies = .{ .sqlite = .{ - .url = "https://www.sqlite.org/2025/sqlite-amalgamation-3490200.zip", - .hash = "N-V-__8AAH-mpwB7g3MnqYU-ooUBF1t99RP27dZ9addtMVXD", + .url = "https://www.sqlite.org/2026/sqlite-amalgamation-3530400.zip", + .hash = "N-V-__8AAE0xkQBMZEkob7f2c_O_Ds3NJOHw4Ve1AvBUIM-I", }, }, .paths = .{"."}, diff --git a/c.zig b/c.zig index 3a43106..0e9cd38 100644 --- a/c.zig +++ b/c.zig @@ -3,10 +3,7 @@ const root = @import("root"); pub const c = if (@hasDecl(root, "loadable_extension")) @import("c/loadable_extension.zig") else - @cImport({ - @cInclude("sqlite3.h"); - @cInclude("workaround.h"); - }); + @import("c_bindings"); // versionGreaterThanOrEqualTo returns true if the SQLite version is >= to the major.minor.patch provided. pub fn versionGreaterThanOrEqualTo(major: u8, minor: u8, patch: u8) bool { diff --git a/c/c_bindings.c b/c/c_bindings.c new file mode 100644 index 0000000..2e36468 --- /dev/null +++ b/c/c_bindings.c @@ -0,0 +1,2 @@ +#include "loadable-ext-sqlite3.h" +#include "workaround.h" \ No newline at end of file diff --git a/c/c_bindings_ext.c b/c/c_bindings_ext.c new file mode 100644 index 0000000..e50e483 --- /dev/null +++ b/c/c_bindings_ext.c @@ -0,0 +1,2 @@ +#include "loadable-ext-sqlite3ext.h" +#include "workaround.h" \ No newline at end of file diff --git a/c/loadable_extension.zig b/c/loadable_extension.zig index dfce56c..2db894a 100644 --- a/c/loadable_extension.zig +++ b/c/loadable_extension.zig @@ -1,7 +1,4 @@ -pub const c = @cImport({ - @cInclude("loadable-ext-sqlite3ext.h"); - @cInclude("workaround.h"); -}); +pub const c = @import("c_bindings_ext"); pub var sqlite3_api: [*c]c.sqlite3_api_routines = null; From f96ab7b0217e5385ef320f64b6630e4cd089ec10 Mon Sep 17 00:00:00 2001 From: samooth Date: Sat, 29 Aug 2026 03:38:40 +0200 Subject: [PATCH 16/22] fix: Zig 0.17 compatibility with b.addTranslateC() and @intFromEnum/@enumFromInt - build.zig.zon: Update SQLite to 3.53.4 with correct hash - build.zig: Use b.addTranslateC() for C bindings (works on both 0.16 and 0.17+) - c.zig: Use @import("c_bindings") for Zig 0.17+ compatibility - c/loadable_extension.zig: Use @import("c_bindings_ext") for Zig 0.17+ - c/c_bindings.c: Include sqlite3.h directly for translate-c - c/c_bindings_ext.c: Include loadable-ext-sqlite3ext.h for loadable extension - build.zig: Add b.addTranslateC() for C bindings (works on both 0.16 and 0.17+) - build.zig: Fix @typeInfo syntax for Zig 0.17 (field_names instead of fields) - build.zig.zon: Update SQLite to 3.53.4 with correct hash - build.zig: Fix @typeInfo syntax for Zig 0.17 (field_names instead of fields) - CI workflow: Use 0.16.0 and master (0.17.0 doesn't exist yet) Known limitation: Zig 0.17 cross-compilation fails due to @cImport removal in 0.17. Native builds work on both 0.16 and 0.17. Zig 0.16 cross-compilation works. Zig 0.17 cross-compilation requires b.addTranslateC() for all C imports (WIP). --- build.zig | 28 +++++++++++++++++++--------- c/c_bindings.c | 2 +- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/build.zig b/build.zig index d719880..86d5eae 100644 --- a/build.zig +++ b/build.zig @@ -207,6 +207,7 @@ pub fn build(b: *std.Build) !void { .target = target, .optimize = optimize, }); + c_bindings.addIncludePath(sqlite_dep.path(".")); c_bindings.addIncludePath(b.path("c")); c_bindings.step.dependOn(&preprocess.step); @@ -321,9 +322,11 @@ pub fn build(b: *std.Build) !void { // Tools // - // Preprocess step generates loadable-ext-sqlite3.h and loadable-ext-sqlite3ext.h - // Works for both Zig 0.16 and 0.17+ - _ = addPreprocessStep(b, io, sqlite_dep); +// Preprocess step generates loadable-ext-sqlite3.h and loadable-ext-sqlite3ext.h + // Works for Zig 0.16 (0.17+ requires different approach) + if (builtin.zig_version.minor <= 16) { + _ = addPreprocessStep(b, io, sqlite_dep); + } } fn addPreprocessStep(b: *std.Build, io: Io, sqlite_dep: *std.Build.Dependency) std.Build.Step { @@ -411,12 +414,19 @@ const PreprocessStep = struct { fn create(owner: *std.Build, config: Config) *PreprocessStep { const step = owner.allocator.create(PreprocessStep) catch @panic("OOM"); step.* = .{ -.step = std.Build.Step.init(.{ - .tag = if (builtin.zig_version.minor <= 16) std.Build.Step.Id.custom else .translate_c, - .name = "preprocess", - .owner = owner, - .makeFn = make, - }), +.step = if (builtin.zig_version.minor <= 16) + std.Build.Step.init(.{ + .id = std.Build.Step.Id.custom, + .name = "preprocess", + .owner = owner, + .makeFn = make, + }) +else + std.Build.Step.init(.{ + .tag = .translate_c, + .name = "preprocess", + .owner = owner, + }), .source = config.source, .target = config.target, .io = config.io, diff --git a/c/c_bindings.c b/c/c_bindings.c index 2e36468..f3a204f 100644 --- a/c/c_bindings.c +++ b/c/c_bindings.c @@ -1,2 +1,2 @@ -#include "loadable-ext-sqlite3.h" +#include "sqlite3.h" #include "workaround.h" \ No newline at end of file From f9729e82cff5f1e691ccee18b90f7edfad6cfb6f Mon Sep 17 00:00:00 2001 From: samooth Date: Sat, 29 Aug 2026 04:00:18 +0200 Subject: [PATCH 17/22] style: format build.zig --- build.zig | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/build.zig b/build.zig index 86d5eae..36ea87e 100644 --- a/build.zig +++ b/build.zig @@ -322,7 +322,7 @@ pub fn build(b: *std.Build) !void { // Tools // -// Preprocess step generates loadable-ext-sqlite3.h and loadable-ext-sqlite3ext.h + // Preprocess step generates loadable-ext-sqlite3.h and loadable-ext-sqlite3ext.h // Works for Zig 0.16 (0.17+ requires different approach) if (builtin.zig_version.minor <= 16) { _ = addPreprocessStep(b, io, sqlite_dep); @@ -414,19 +414,19 @@ const PreprocessStep = struct { fn create(owner: *std.Build, config: Config) *PreprocessStep { const step = owner.allocator.create(PreprocessStep) catch @panic("OOM"); step.* = .{ -.step = if (builtin.zig_version.minor <= 16) - std.Build.Step.init(.{ - .id = std.Build.Step.Id.custom, - .name = "preprocess", - .owner = owner, - .makeFn = make, - }) -else - std.Build.Step.init(.{ - .tag = .translate_c, - .name = "preprocess", - .owner = owner, - }), + .step = if (builtin.zig_version.minor <= 16) + std.Build.Step.init(.{ + .id = std.Build.Step.Id.custom, + .name = "preprocess", + .owner = owner, + .makeFn = make, + }) + else + std.Build.Step.init(.{ + .tag = .translate_c, + .name = "preprocess", + .owner = owner, + }), .source = config.source, .target = config.target, .io = config.io, From 124e645f42f96464aeb66998067f855bebcb951a Mon Sep 17 00:00:00 2001 From: samooth Date: Sat, 29 Aug 2026 04:12:43 +0200 Subject: [PATCH 18/22] ci: fix lint job to only run on 0.16.0 (master has different fmt rules) --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2e6d5ff..057f1e2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,14 +10,14 @@ jobs: strategy: fail-fast: false matrix: - zig_version: ["0.16.0", "master"] + zig_version: ["0.16.0"] runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v5 - uses: mlugg/setup-zig@v2 with: version: ${{ matrix.zig_version }} - - run: zig fmt --check *.zig + - run: zig fmt --check *.zig c/*.zig build.zig test-in-memory: strategy: From 4be7ede74b1a6d6e11a17bb169f54fdcf426360d Mon Sep 17 00:00:00 2001 From: samooth Date: Sat, 29 Aug 2026 04:14:59 +0200 Subject: [PATCH 19/22] ci: restrict test matrix - only test cross-compilation on 0.16.0, test master only on ubuntu --- .github/workflows/main.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 057f1e2..3880e75 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,7 +24,10 @@ jobs: fail-fast: false matrix: os: [ubuntu-24.04, windows-latest, macos-latest] - zig_version: ["0.16.0", "master"] + zig_version: ["0.16.0"] + include: + - os: ubuntu-24.04 + zig_version: "master" runs-on: ${{ matrix.os }} steps: - name: Checkout repository From 1e08fc60eb06312e0e451431d8fea8639f2859bb Mon Sep 17 00:00:00 2001 From: samooth Date: Sat, 29 Aug 2026 04:32:05 +0200 Subject: [PATCH 20/22] fix: update SQLite hash to match actual fetched package The Zig package manager fetches the SQLite amalgamation with a different hash than expected. The correct hash for the 3530400.zip from sqlite.org is N-V-__8AAGVtrgCcOcmjrOJnagmnRyMrcKaOo09KbU-vu8w8 --- build.zig.zon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.zig.zon b/build.zig.zon index 42c161c..1326fbc 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -6,7 +6,7 @@ .dependencies = .{ .sqlite = .{ .url = "https://www.sqlite.org/2026/sqlite-amalgamation-3530400.zip", - .hash = "N-V-__8AAE0xkQBMZEkob7f2c_O_Ds3NJOHw4Ve1AvBUIM-I", + .hash = "N-V-__8AAGVtrgCcOcmjrOJnagmnRyMrcKaOo09KbU-vu8w8", }, }, .paths = .{"."}, From af920fc07d56bce832b76449cdce8ca541eb64fe Mon Sep 17 00:00:00 2001 From: samooth Date: Sun, 30 Aug 2026 00:54:03 +0200 Subject: [PATCH 21/22] fix: Zig 0.17 lang.Type compatibility and Windows CI - Add compat.zig with version-gated helpers for the std.lang.Type reshapes in 0.17 (Fn params/attrs, Struct/Union parallel field arrays), dupeZ (removed from 0.17 std) and pointer attrs - Convert all type-introspection sites in sqlite.zig, vtab.zig and test.zig to the compat helpers so both 0.16 and 0.17 compile - Run the non-Ubuntu CI test step with shell: bash so $ZIG_GLOBAL_CACHE_DIR expands on Windows runners (pwsh created D:\tmp instead, breaking package fetch with FileNotFound) - Restore the full test matrix: master on all three OSes - Keep lint on 0.16.0 only: 0.17 fmt rewrites @enumFromInt/@intFromEnum to builtins that 0.16 cannot parse --- .github/workflows/main.yml | 15 ++-- compat.zig | 144 +++++++++++++++++++++++++++++++++++++ sqlite.zig | 86 ++++++++++++---------- test.zig | 5 +- vtab.zig | 80 +++++++++++---------- 5 files changed, 244 insertions(+), 86 deletions(-) create mode 100644 compat.zig diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3880e75..8b7595f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,6 +10,9 @@ jobs: strategy: fail-fast: false matrix: + # 0.16 fmt cannot parse @fromBackingInt/@backingInt (0.17-renamed builtins) and + # 0.17 fmt rewrites @enumFromInt/@intFromEnum to those new names, so no single + # source can satisfy both formatters; lint with 0.16.0 only. zig_version: ["0.16.0"] runs-on: ubuntu-24.04 steps: @@ -24,10 +27,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-24.04, windows-latest, macos-latest] - zig_version: ["0.16.0"] - include: - - os: ubuntu-24.04 - zig_version: "master" + zig_version: ["0.16.0", "master"] runs-on: ${{ matrix.os }} steps: - name: Checkout repository @@ -48,10 +48,11 @@ jobs: - name: Run Tests in memory if: ${{ matrix.os == 'ubuntu-24.04' }} run: | - mkdir -p $ZIG_GLOBAL_CACHE_DIR/tmp + mkdir -p "$ZIG_GLOBAL_CACHE_DIR/tmp" zig build test -Dci=true -Din_memory=true --summary all -fqemu -fwine - name: Run Tests in memory if: ${{ matrix.os != 'ubuntu-24.04' }} + shell: bash run: | - mkdir -p $ZIG_GLOBAL_CACHE_DIR/tmp - zig build test -Dci=true -Din_memory=true --summary all \ No newline at end of file + mkdir -p "$ZIG_GLOBAL_CACHE_DIR/tmp" + zig build test -Dci=true -Din_memory=true --summary all diff --git a/compat.zig b/compat.zig new file mode 100644 index 0000000..983e11c --- /dev/null +++ b/compat.zig @@ -0,0 +1,144 @@ +//! Compatibility layer for Zig 0.16 (`std.builtin.Type`) and 0.17+ (`std.lang.Type`). +//! +//! Zig 0.17 renamed `std.builtin.Type` to `std.lang.Type` and reshaped its payloads: +//! - `Fn.is_var_args` -> `Fn.attrs.varargs` +//! - `Fn.params` -> `Fn.param_types` +//! - `Struct.fields` -> `Struct.field_names` + `Struct.field_types` +//! - `Union.fields` -> `Union.field_names` + `Union.field_types` +//! +//! The helpers below select the right representation at comptime so that call sites +//! work unmodified on both versions. + +const std = @import("std"); +const builtin = @import("builtin"); + +const is_zig_16 = builtin.zig_version.minor <= 16; + +/// Returns the field names of a struct type. +pub fn structFieldNames(comptime T: type) []const [:0]const u8 { + if (is_zig_16) { + return comptime blk: { + const fields = @typeInfo(T).@"struct".fields; + var names: [fields.len][:0]const u8 = undefined; + for (fields, 0..) |f, i| names[i] = f.name; + const final = names; + break :blk &final; + }; + } else { + return @typeInfo(T).@"struct".field_names; + } +} + +/// Returns the field types of a struct type, parallel to `structFieldNames`. +pub fn structFieldTypes(comptime T: type) []const type { + if (is_zig_16) { + return comptime blk: { + const fields = @typeInfo(T).@"struct".fields; + var types: [fields.len]type = undefined; + for (fields, 0..) |f, i| types[i] = f.type; + const final = types; + break :blk &final; + }; + } else { + return @typeInfo(T).@"struct".field_types; + } +} + +/// Returns the field names of a tagged union type. +pub fn unionFieldNames(comptime T: type) []const [:0]const u8 { + if (is_zig_16) { + return comptime blk: { + const fields = @typeInfo(T).@"union".fields; + var names: [fields.len][:0]const u8 = undefined; + for (fields, 0..) |f, i| names[i] = f.name; + const final = names; + break :blk &final; + }; + } else { + return @typeInfo(T).@"union".field_names; + } +} + +/// Returns the field types of a tagged union type, parallel to `unionFieldNames`. +pub fn unionFieldTypes(comptime T: type) []const type { + if (is_zig_16) { + return comptime blk: { + const fields = @typeInfo(T).@"union".fields; + var types: [fields.len]type = undefined; + for (fields, 0..) |f, i| types[i] = f.type; + const final = types; + break :blk &final; + }; + } else { + return @typeInfo(T).@"union".field_types; + } +} + +/// Returns true if the function type info describes a variadic function. +pub fn fnIsVarArgs(comptime fn_info: anytype) bool { + if (is_zig_16) { + return fn_info.is_var_args; + } else { + return fn_info.attrs.varargs; + } +} + +/// Returns the parameter types of a function type info, as `?type` values +/// (null represents an `anytype` or otherwise generic parameter). +pub fn fnParamTypes(comptime fn_info: anytype) []const ?type { + if (is_zig_16) { + return comptime blk: { + var types: [fn_info.params.len]?type = undefined; + for (fn_info.params, 0..) |p, i| types[i] = p.type; + const final = types; + break :blk &final; + }; + } else { + return fn_info.param_types; + } +} + +/// Returns the parameter type at the given index of a function type info. +pub fn fnParamType(comptime fn_info: anytype, comptime index: usize) ?type { + if (is_zig_16) { + return fn_info.params[index].type; + } else { + return fn_info.param_types[index]; + } +} + +/// Returns the number of parameters of a function type info. +pub fn fnParamCount(comptime fn_info: anytype) usize { + if (is_zig_16) { + return fn_info.params.len; + } else { + return fn_info.param_types.len; + } +} + +/// Duplicates a slice into newly allocated memory, with a zero sentinel. +/// +/// Reimplements `std.mem.Allocator.dupeZ` which was removed in Zig 0.17. +pub fn dupeZ(allocator: std.mem.Allocator, comptime T: type, m: []const T) std.mem.Allocator.Error![:0]T { + const result = try allocator.allocSentinel(T, m.len, 0); + @memcpy(result, m); + return result; +} + +/// Returns true if the pointer type info is volatile. +pub fn ptrIsVolatile(comptime ptr: anytype) bool { + if (is_zig_16) { + return ptr.is_volatile; + } else { + return ptr.attrs.@"volatile"; + } +} + +/// Returns true if the pointer type info allows zero. +pub fn ptrIsAllowzero(comptime ptr: anytype) bool { + if (is_zig_16) { + return ptr.is_allowzero; + } else { + return ptr.attrs.@"allowzero"; + } +} diff --git a/sqlite.zig b/sqlite.zig index f78a676..a3b363d 100644 --- a/sqlite.zig +++ b/sqlite.zig @@ -23,6 +23,7 @@ const getTestDb = @import("test.zig").getTestDb; pub const vtab = @import("vtab.zig"); const helpers = @import("helpers.zig"); +const compat = @import("compat.zig"); test { _ = @import("vtab.zig"); @@ -47,7 +48,7 @@ fn isZigString(comptime T: type) bool { const ptr = &info.pointer; // Check for CV qualifiers that would prevent coerction to []const u8 - if (ptr.is_volatile or ptr.is_allowzero) break :blk false; + if (compat.ptrIsVolatile(ptr) or compat.ptrIsAllowzero(ptr)) break :blk false; // If it's already a slice, simple check. if (ptr.size == .slice) { @@ -656,25 +657,25 @@ pub const Db = struct { else => @compileError("cannot use func, expecting a function"), }; if (step_fn_info.is_generic) @compileError("step function can't be generic"); - if (step_fn_info.is_var_args) @compileError("step function can't be variadic"); + if (comptime compat.fnIsVarArgs(step_fn_info)) @compileError("step function can't be variadic"); const finalize_fn_info = switch (@typeInfo(@TypeOf(finalize_func))) { .@"fn" => |fn_info| fn_info, else => @compileError("cannot use func, expecting a function"), }; - if (finalize_fn_info.params.len != 1) @compileError("finalize function must take exactly one argument"); + if (comptime compat.fnParamCount(finalize_fn_info) != 1) @compileError("finalize function must take exactly one argument"); if (finalize_fn_info.is_generic) @compileError("finalize function can't be generic"); - if (finalize_fn_info.is_var_args) @compileError("finalize function can't be variadic"); + if (comptime compat.fnIsVarArgs(finalize_fn_info)) @compileError("finalize function can't be variadic"); - if (step_fn_info.params[0].type.? != finalize_fn_info.params[0].type.?) { + if (comptime compat.fnParamType(step_fn_info, 0).? != compat.fnParamType(finalize_fn_info, 0).?) { @compileError("both step and finalize functions must have the same first argument and it must be a FunctionContext"); } - if (step_fn_info.params[0].type.? != FunctionContext) { + if (comptime compat.fnParamType(step_fn_info, 0).? != FunctionContext) { @compileError("both step and finalize functions must have a first argument of type FunctionContext"); } // subtract the context argument - const real_args_len = step_fn_info.params.len - 1; + const real_args_len = comptime compat.fnParamCount(step_fn_info) - 1; // @@ -701,10 +702,9 @@ pub const Db = struct { comptime var i: usize = 0; inline while (i < real_args_len) : (i += 1) { // Remember the firt argument is always the function context - const arg = step_fn_info.params[i + 1]; const arg_ptr = &args[i + 1]; - const ArgType = arg.type.?; + const ArgType = compat.fnParamType(step_fn_info, i + 1).?; helpers.setTypeFromValue(ArgType, arg_ptr, sqlite_args[i].?); } @@ -749,7 +749,7 @@ pub const Db = struct { else => @compileError("expecting a function"), }; if (fn_info.is_generic) @compileError("function can't be generic"); - if (fn_info.is_var_args) @compileError("function can't be variadic"); + if (comptime compat.fnIsVarArgs(fn_info)) @compileError("function can't be variadic"); const ArgTuple = std.meta.ArgsTuple(Type); @@ -760,18 +760,18 @@ pub const Db = struct { const result = c.sqlite3_create_function_v2( self.db, func_name, - fn_info.params.len, + @intCast(compat.fnParamCount(fn_info)), flags, null, struct { fn xFunc(ctx: ?*c.sqlite3_context, argc: c_int, argv: [*c]?*c.sqlite3_value) callconv(.c) void { - debug.assert(argc == fn_info.params.len); + debug.assert(argc == compat.fnParamCount(fn_info)); - const sqlite_args = argv[0..fn_info.params.len]; + const sqlite_args = argv[0..compat.fnParamCount(fn_info)]; var fn_args: ArgTuple = undefined; - inline for (fn_info.params, 0..) |arg, i| { - const ArgType = arg.type.?; + inline for (comptime compat.fnParamTypes(fn_info), 0..) |arg_type, i| { + const ArgType = arg_type.?; helpers.setTypeFromValue(ArgType, &fn_args[i], sqlite_args[i].?); } @@ -1086,7 +1086,7 @@ pub fn Iterator(comptime Type: type) type { @compileError("enum column " ++ @typeName(Type) ++ " must have a BaseType of either string or int"); }, .@"struct" => { - std.debug.assert(columns == TypeInfo.@"struct".fields.len); + std.debug.assert(columns == comptime compat.structFieldNames(Type).len); return try self.readStruct(options); }, else => @compileError("cannot read into type " ++ @typeName(Type) ++ " ; if dynamic memory allocation is required use nextAlloc or oneAlloc"), @@ -1169,7 +1169,7 @@ pub fn Iterator(comptime Type: type) type { @compileError("enum column " ++ @typeName(Type) ++ " must have a BaseType of either string or int"); }, .@"struct" => { - std.debug.assert(columns == TypeInfo.@"struct".fields.len); + std.debug.assert(columns == comptime compat.structFieldNames(Type).len); return try self.readStruct(.{ .allocator = allocator, }); @@ -1399,12 +1399,12 @@ pub fn Iterator(comptime Type: type) type { var value: Type = undefined; - inline for (@typeInfo(Type).@"struct".fields, 0..) |field, _i| { + inline for (comptime compat.structFieldNames(Type), comptime compat.structFieldTypes(Type), 0..) |field_name, field_type, _i| { const i = @as(usize, _i); - const ret = try self.readField(field.type, options, i); + const ret = try self.readField(field_type, options, i); - @field(value, field.name) = ret; + @field(value, field_name) = ret; } return value; @@ -1690,16 +1690,19 @@ pub const DynamicStatement = struct { return; } if (info.tag_type) |UnionTagType| { - inline for (info.fields) |u_field| { + inline for ( + comptime compat.unionFieldNames(FieldType), + comptime compat.unionFieldTypes(FieldType), + ) |u_field_name, u_field_type| { // This wasn't entirely obvious when I saw code like this elsewhere, it works because of type coercion. // See https://ziglang.org/documentation/master/#Type-Coercion-unions-and-enums const field_tag: std.meta.Tag(FieldType) = field; - const this_tag: std.meta.Tag(FieldType) = @field(UnionTagType, u_field.name); + const this_tag: std.meta.Tag(FieldType) = @field(UnionTagType, u_field_name); if (field_tag == this_tag) { - const field_value = @field(field, u_field.name); + const field_value = @field(field, u_field_name); - try self.bindField(u_field.type, options, u_field.name, i, field_value); + try self.bindField(u_field_type, options, u_field_name, i, field_value); } } } else { @@ -1746,15 +1749,19 @@ pub const DynamicStatement = struct { const Type = @TypeOf(values); switch (@typeInfo(Type)) { - .@"struct" => |StructTypeInfo| { - inline for (StructTypeInfo.fields, 0..) |struct_field, struct_field_i| { - const field_value = @field(values, struct_field.name); - - const i = sqlite3BindParameterIndex(self.stmt, struct_field.name); + .@"struct" => { + inline for ( + comptime compat.structFieldNames(Type), + comptime compat.structFieldTypes(Type), + 0.., + ) |field_name, field_type, struct_field_i| { + const field_value = @field(values, field_name); + + const i = sqlite3BindParameterIndex(self.stmt, field_name); if (i >= 0) { - try self.bindField(struct_field.type, options, struct_field.name, i, field_value); + try self.bindField(field_type, options, field_name, i, field_value); } else { - try self.bindField(struct_field.type, options, struct_field.name, struct_field_i, field_value); + try self.bindField(field_type, options, field_name, struct_field_i, field_value); } } }, @@ -2042,11 +2049,12 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: anytype) type @compileError("options passed to Statement.bind must be a struct (DynamicStatement supports runtime slices)"); } - const StructTypeInfo = @typeInfo(StructType).@"struct"; + const StructFieldNames = comptime compat.structFieldNames(StructType); + const StructFieldTypes = comptime compat.structFieldTypes(StructType); comptime marker_len_check: { - if (query.bind_markers.len != StructTypeInfo.fields.len) { - if (query.bind_markers.len > StructTypeInfo.fields.len) { + if (query.bind_markers.len != StructFieldNames.len) { + if (query.bind_markers.len > StructFieldNames.len) { var found_markers = 0; for (query.bind_markers) |bind_marker| { if (bind_marker.name) |name| { @@ -2061,21 +2069,21 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: anytype) type } @compileError(std.fmt.comptimePrint("expected {d} bind parameters but got {d}", .{ query.bind_markers.len, - StructTypeInfo.fields.len, + StructFieldNames.len, })); } } - inline for (StructTypeInfo.fields, 0..) |struct_field, _i| { + inline for (StructFieldNames, StructFieldTypes, 0..) |_, struct_field_type, _i| { const bind_marker = query.bind_markers[_i]; if (bind_marker.typed) |typ| { - const FieldTypeInfo = @typeInfo(struct_field.type); + const FieldTypeInfo = @typeInfo(struct_field_type); switch (FieldTypeInfo) { .@"struct", .@"enum", .@"union" => comptime assertMarkerType( - if (@hasDecl(struct_field.type, "BaseType")) struct_field.type.BaseType else struct_field.type, + if (@hasDecl(struct_field_type, "BaseType")) struct_field_type.BaseType else struct_field_type, typ, ), - else => comptime assertMarkerType(struct_field.type, typ), + else => comptime assertMarkerType(struct_field_type, typ), } } } diff --git a/test.zig b/test.zig index fcc8aa7..107afa0 100644 --- a/test.zig +++ b/test.zig @@ -4,6 +4,7 @@ const mem = std.mem; const testing = std.testing; const Db = @import("sqlite.zig").Db; +const compat = @import("compat.zig"); pub fn getTestDb() !Db { var buf: [1024]u8 = undefined; @@ -31,7 +32,7 @@ fn tmpDbPath(allocator: mem.Allocator) ![:0]const u8 { }); defer allocator.free(path); - return allocator.dupeZ(u8, path); + return compat.dupeZ(allocator, u8, path); } fn dbMode(allocator: mem.Allocator) Db.Mode { @@ -39,7 +40,7 @@ fn dbMode(allocator: mem.Allocator) Db.Mode { break :blk .{ .Memory = {} }; } else blk: { if (build_options.dbfile) |dbfile| { - return .{ .File = allocator.dupeZ(u8, dbfile) catch unreachable }; + return .{ .File = compat.dupeZ(allocator, u8, dbfile) catch unreachable }; } const path = tmpDbPath(allocator) catch unreachable; diff --git a/vtab.zig b/vtab.zig index b804dc9..b18b3ca 100644 --- a/vtab.zig +++ b/vtab.zig @@ -13,6 +13,7 @@ const Diagnostics = @import("sqlite.zig").Diagnostics; const Blob = @import("sqlite.zig").Blob; const Text = @import("sqlite.zig").Text; const helpers = @import("helpers.zig"); +const compat = @import("compat.zig"); const logger = std.log.scoped(.vtab); @@ -324,9 +325,9 @@ fn validateCursorType(comptime Table: type) void { const info = @typeInfo(@TypeOf(Cursor.init)).@"fn"; - if (info.params.len != 2) @compileError(error_message); - if (info.params[0].type.? != mem.Allocator) @compileError(error_message); - if (info.params[1].type.? != *Table) @compileError(error_message); + if (compat.fnParamCount(info) != 2) @compileError(error_message); + if (compat.fnParamType(info, 0).? != mem.Allocator) @compileError(error_message); + if (compat.fnParamType(info, 1).? != *Table) @compileError(error_message); if (info.return_type.? != Cursor.InitError!*Cursor) @compileError(error_message); } @@ -342,8 +343,8 @@ fn validateCursorType(comptime Table: type) void { const info = @typeInfo(@TypeOf(Cursor.deinit)).@"fn"; - if (info.params.len != 1) @compileError(error_message); - if (info.params[0].type.? != *Cursor) @compileError(error_message); + if (compat.fnParamCount(info) != 1) @compileError(error_message); + if (compat.fnParamType(info, 0).? != *Cursor) @compileError(error_message); if (info.return_type.? != void) @compileError(error_message); } @@ -363,9 +364,9 @@ fn validateCursorType(comptime Table: type) void { const info = @typeInfo(@TypeOf(Cursor.next)).@"fn"; - if (info.params.len != 2) @compileError(error_message); - if (info.params[0].type.? != *Cursor) @compileError(error_message); - if (info.params[1].type.? != *VTabDiagnostics) @compileError(error_message); + if (compat.fnParamCount(info) != 2) @compileError(error_message); + if (compat.fnParamType(info, 0).? != *Cursor) @compileError(error_message); + if (compat.fnParamType(info, 1).? != *VTabDiagnostics) @compileError(error_message); if (info.return_type.? != Cursor.NextError!void) @compileError(error_message); } @@ -385,9 +386,9 @@ fn validateCursorType(comptime Table: type) void { const info = @typeInfo(@TypeOf(Cursor.hasNext)).@"fn"; - if (info.params.len != 2) @compileError(error_message); - if (info.params[0].type.? != *Cursor) @compileError(error_message); - if (info.params[1].type.? != *VTabDiagnostics) @compileError(error_message); + if (compat.fnParamCount(info) != 2) @compileError(error_message); + if (compat.fnParamType(info, 0).? != *Cursor) @compileError(error_message); + if (compat.fnParamType(info, 1).? != *VTabDiagnostics) @compileError(error_message); if (info.return_type.? != Cursor.HasNextError!bool) @compileError(error_message); } @@ -407,11 +408,11 @@ fn validateCursorType(comptime Table: type) void { const info = @typeInfo(@TypeOf(Cursor.filter)).@"fn"; - if (info.params.len != 4) @compileError(error_message); - if (info.params[0].type.? != *Cursor) @compileError(error_message); - if (info.params[1].type.? != *VTabDiagnostics) @compileError(error_message); - if (info.params[2].type.? != IndexIdentifier) @compileError(error_message); - if (info.params[3].type.? != []FilterArg) @compileError(error_message); + if (compat.fnParamCount(info) != 4) @compileError(error_message); + if (compat.fnParamType(info, 0).? != *Cursor) @compileError(error_message); + if (compat.fnParamType(info, 1).? != *VTabDiagnostics) @compileError(error_message); + if (compat.fnParamType(info, 2).? != IndexIdentifier) @compileError(error_message); + if (compat.fnParamType(info, 3).? != []FilterArg) @compileError(error_message); if (info.return_type.? != Cursor.FilterError!void) @compileError(error_message); } @@ -434,10 +435,10 @@ fn validateCursorType(comptime Table: type) void { const info = @typeInfo(@TypeOf(Cursor.column)).@"fn"; - if (info.params.len != 3) @compileError(error_message); - if (info.params[0].type.? != *Cursor) @compileError(error_message); - if (info.params[1].type.? != *VTabDiagnostics) @compileError(error_message); - if (info.params[2].type.? != i32) @compileError(error_message); + if (compat.fnParamCount(info) != 3) @compileError(error_message); + if (compat.fnParamType(info, 0).? != *Cursor) @compileError(error_message); + if (compat.fnParamType(info, 1).? != *VTabDiagnostics) @compileError(error_message); + if (compat.fnParamType(info, 2).? != i32) @compileError(error_message); if (info.return_type.? != Cursor.ColumnError!Cursor.Column) @compileError(error_message); } @@ -457,9 +458,9 @@ fn validateCursorType(comptime Table: type) void { const info = @typeInfo(@TypeOf(Cursor.rowId)).@"fn"; - if (info.params.len != 2) @compileError(error_message); - if (info.params[0].type.? != *Cursor) @compileError(error_message); - if (info.params[1].type.? != *VTabDiagnostics) @compileError(error_message); + if (compat.fnParamCount(info) != 2) @compileError(error_message); + if (compat.fnParamType(info, 0).? != *Cursor) @compileError(error_message); + if (compat.fnParamType(info, 1).? != *VTabDiagnostics) @compileError(error_message); if (info.return_type.? != Cursor.RowIDError!i64) @compileError(error_message); } } @@ -482,11 +483,11 @@ fn validateTableType(comptime Table: type) void { const info = @typeInfo(@TypeOf(Table.init)).@"fn"; - if (info.params.len != 3) @compileError(error_message); - if (info.params[0].type.? != mem.Allocator) @compileError(error_message); - if (info.params[1].type.? != *VTabDiagnostics) @compileError(error_message); + if (compat.fnParamCount(info) != 3) @compileError(error_message); + if (compat.fnParamType(info, 0).? != mem.Allocator) @compileError(error_message); + if (compat.fnParamType(info, 1).? != *VTabDiagnostics) @compileError(error_message); // TODO(vincent): maybe allow a signature without the params since a table can do withoout them - if (info.params[2].type.? != []const ModuleArgument) @compileError(error_message); + if (compat.fnParamType(info, 2).? != []const ModuleArgument) @compileError(error_message); if (info.return_type.? != Table.InitError!*Table) @compileError(error_message); } @@ -502,9 +503,9 @@ fn validateTableType(comptime Table: type) void { const info = @typeInfo(@TypeOf(Table.deinit)).@"fn"; - if (info.params.len != 2) @compileError(error_message); - if (info.params[0].type.? != *Table) @compileError(error_message); - if (info.params[1].type.? != mem.Allocator) @compileError(error_message); + if (compat.fnParamCount(info) != 2) @compileError(error_message); + if (compat.fnParamType(info, 0).? != *Table) @compileError(error_message); + if (compat.fnParamType(info, 1).? != mem.Allocator) @compileError(error_message); if (info.return_type.? != void) @compileError(error_message); } @@ -524,10 +525,10 @@ fn validateTableType(comptime Table: type) void { const info = @typeInfo(@TypeOf(Table.buildBestIndex)).@"fn"; - if (info.params.len != 3) @compileError(error_message); - if (info.params[0].type.? != *Table) @compileError(error_message); - if (info.params[1].type.? != *VTabDiagnostics) @compileError(error_message); - if (info.params[2].type.? != *BestIndexBuilder) @compileError(error_message); + if (compat.fnParamCount(info) != 3) @compileError(error_message); + if (compat.fnParamType(info, 0).? != *Table) @compileError(error_message); + if (compat.fnParamType(info, 1).? != *VTabDiagnostics) @compileError(error_message); + if (compat.fnParamType(info, 2).? != *BestIndexBuilder) @compileError(error_message); if (info.return_type.? != Table.BuildBestIndexError!void) @compileError(error_message); } @@ -931,15 +932,18 @@ pub fn VirtualTable( switch (@typeInfo(ColumnType)) { .@"union" => |info| { if (info.tag_type) |UnionTagType| { - inline for (info.fields) |u_field| { + inline for ( + comptime compat.unionFieldNames(ColumnType), + comptime compat.unionFieldTypes(ColumnType), + ) |u_field_name, _| { // This wasn't entirely obvious when I saw code like this elsewhere, it works because of type coercion. // See https://ziglang.org/documentation/master/#Type-Coercion-unions-and-enums const column_tag: std.meta.Tag(ColumnType) = column; - const this_tag: std.meta.Tag(ColumnType) = @field(UnionTagType, u_field.name); + const this_tag: std.meta.Tag(ColumnType) = @field(UnionTagType, u_field_name); if (column_tag == this_tag) { - const column_value = @field(column, u_field.name); + const column_value = @field(column, u_field_name); helpers.setResult(ctx, column_value); } @@ -1048,7 +1052,7 @@ const TestVirtualTable = struct { res.rows = rows; // Build the schema - res.schema = try allocator.dupeZ(u8, + res.schema = try compat.dupeZ(allocator, u8, \\CREATE TABLE foobar(foo TEXT, bar TEXT, baz INTEGER) ); From 65a241ffaaea454eaf29fa82d6818732264909b2 Mon Sep 17 00:00:00 2001 From: samooth Date: Sun, 30 Aug 2026 01:02:44 +0200 Subject: [PATCH 22/22] docs: add CI and license badges to README --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 8a7ce37..6767219 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # zig-sqlite +[![CI](https://github.com/samooth/zig-sqlite/actions/workflows/main.yml/badge.svg)](https://github.com/samooth/zig-sqlite/actions/workflows/main.yml) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE) + This package is a thin wrapper around [sqlite](https://sqlite.org/index.html)'s C API. _Maintainer note_: I'm currently on a break working with Zig and don't intend to work on new features for zig-sqlite.