comptime sql bindings for zig
ziglang sql

add bind() for named parameter support

converts struct args to positional tuple matching sql param order.
struct field names match :name params - order in struct doesn't matter.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+38
+38
src/Query.zig
··· 65 65 return result; 66 66 } 67 67 68 + /// bind args struct to positional tuple in param order 69 + pub fn bind(args: anytype) BindTuple(@TypeOf(args)) { 70 + comptime validateArgs(@TypeOf(args)); 71 + var result: BindTuple(@TypeOf(args)) = undefined; 72 + inline for (params, 0..) |p, i| { 73 + result[i] = @field(args, p); 74 + } 75 + return result; 76 + } 77 + 78 + fn BindTuple(comptime Args: type) type { 79 + const fields = @typeInfo(Args).@"struct".fields; 80 + var types: [param_count]type = undefined; 81 + inline for (params, 0..) |p, i| { 82 + for (fields) |f| { 83 + if (std.mem.eql(u8, f.name, p)) { 84 + types[i] = f.type; 85 + break; 86 + } 87 + } 88 + } 89 + return std.meta.Tuple(&types); 90 + } 91 + 68 92 fn hasField(fields: anytype, name: []const u8) bool { 69 93 inline for (fields) |f| { 70 94 if (std.mem.eql(u8, f.name, name)) return true; ··· 139 163 140 164 const Partial = struct { id: i64, name: []const u8 }; 141 165 comptime Q.validateStruct(Partial); 166 + } 167 + 168 + test "bind" { 169 + const Q = Query("INSERT INTO users (name, age) VALUES (:name, :age)"); 170 + try std.testing.expectEqualStrings("INSERT INTO users (name, age) VALUES (?, ?)", Q.positional); 171 + 172 + const args = Q.bind(.{ .name = "alice", .age = @as(i64, 25) }); 173 + try std.testing.expectEqualStrings("alice", args[0]); 174 + try std.testing.expectEqual(25, args[1]); 175 + 176 + // order doesn't matter in input struct 177 + const args2 = Q.bind(.{ .age = @as(i64, 30), .name = "bob" }); 178 + try std.testing.expectEqualStrings("bob", args2[0]); 179 + try std.testing.expectEqual(30, args2[1]); 142 180 } 143 181 144 182 test "fromRow" {