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 429edcc..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; @@ -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 @enumFromInt(@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"); }, @@ -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), } } } @@ -3109,7 +3117,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 +3139,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 +3395,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; 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) );