authorgravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2026-08-17 21:15:50-07:00
committergravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2026-08-17 21:15:50-07:00
logb9fc4a67c309082dcd7f34d075c9ff25b5156d5b
treeb1036889757a1e099e1b9224c2072b0df8627edb
parent94f9db92114a559639fbe0b1c5deda13962ca8a2
signature Signed by SSH key SHA256:4hHJbtBRU58AYXwjL7fkz2fnQHdiye8x1QpTCQ0sHNw

move off of vrischmann/zig-sqlite


3 files changed, 295 insertions(+), 44 deletions(-)

src/lib.zig+6-6
...@@ -49,14 +49,14 @@ pub const Engine = union(DriverType) {...@@ -49,14 +49,14 @@ pub const Engine = union(DriverType) {
49 pub fn collectDyn(engine: *Engine, alloc: std.mem.Allocator, comptime T: type, query: []const u8, args: anytype) ![]T {49 pub fn collectDyn(engine: *Engine, alloc: std.mem.Allocator, comptime T: type, query: []const u8, args: anytype) ![]T {
50 return switch (engine.*) {50 return switch (engine.*) {
51 .sqlite3 => |*e| {51 .sqlite3 => |*e| {
52 var stmt = try e.prepareDynamic(query);
53 defer stmt.deinit();
54 var iter = try stmt.iteratorAlloc(T, alloc, args);
55 var list = std.array_list.Managed(T).init(alloc);52 var list = std.array_list.Managed(T).init(alloc);
56 errdefer list.deinit();53 errdefer list.deinit();
57 while (try iter.nextAlloc(alloc, .{})) |row| {54 var stmt: Driver(.sqlite3).Statement = try .prepare(e, query);
58 try list.append(row);55 defer stmt.finalize();
59 }56 try stmt.bindArgs(alloc, args);
57 const iter = stmt.iterate();
58 errdefer iter.reset();
59 while (try iter.step(alloc, T)) |row| try list.append(row);
60 return list.toOwnedSlice();60 return list.toOwnedSlice();
61 },61 },
62 .postgresql => {62 .postgresql => {
src/sqlite3.zig+280-37
...@@ -1,51 +1,42 @@...@@ -1,51 +1,42 @@
1const std = @import("std");1const std = @import("std");
2const string = []const u8;2const string = []const u8;
3const sqlite = @import("sqlite");
4const tracer = @import("tracer");3const tracer = @import("tracer");
5const extras = @import("extras");4const extras = @import("extras");
5const builtin = @import("builtin");
66
7const Self = @This();7const Self = @This();
88
9db: sqlite.Db = undefined,9db: *c.sqlite3,
1010
11pub fn connect(allocator: std.mem.Allocator, path: [:0]const u8) !Self {11pub fn connect(allocator: std.mem.Allocator, path: [:0]const u8) !Self {
12 std.log.scoped(.zorm).info("connecting to {s} @ {s}", .{ "sqlite3", path });12 std.log.scoped(.zorm).info("connecting to {s} @ {s}", .{ "sqlite3", path });
13 _ = allocator;13 _ = allocator;
14 return Self{14 var db: ?*c.sqlite3 = null;
15 .db = try sqlite.Db.init(.{15 var flags: c_int = 0;
16 .mode = .{ .File = path },16 flags |= c.SQLITE_OPEN_READWRITE;
17 .open_flags = .{17 flags |= c.SQLITE_OPEN_CREATE;
18 .write = true,18 flags |= c.SQLITE_OPEN_FULLMUTEX;
19 .create = true,19 s.assert(c.sqlite3_open_v2(path, &db, flags, null));
20 },20 std.debug.assert(c.sqlite3_threadsafe() > 0);
21 .threading_mode = .SingleThread,21 return .{ .db = db.? };
22 }),
23 };
24}22}
2523
26pub fn close(self: *Self) void {24pub fn close(self: *Self) void {
27 self.db.deinit();25 s.assert(c.sqlite3_close_v2(self.db));
28}
29
30fn prepare(self: *Self, comptime query: string) !sqlite.StatementType(.{}, query) {
31 return self.db.prepare(query) catch |err| switch (err) {
32 error.SQLiteError => std.debug.panic("`{s}`: {f}", .{ query, self.db.getDetailedError() }),
33 else => return err,
34 };
35}26}
3627
37pub fn collect(self: *Self, alloc: std.mem.Allocator, comptime T: type, comptime query: string, args: anytype) ![]T {28pub fn collect(self: *Self, alloc: std.mem.Allocator, comptime T: type, comptime query: string, args: anytype) ![]T {
38 const t = tracer.trace(@src(), " {s}", .{query});29 const t = tracer.trace(@src(), " {s}", .{query});
39 defer t.end();30 defer t.end();
4031
41 var stmt = try self.prepare(query);
42 defer stmt.deinit();
43 var iter = try stmt.iteratorAlloc(T, alloc, args);
44 var list = std.array_list.Managed(T).init(alloc);32 var list = std.array_list.Managed(T).init(alloc);
45 errdefer list.deinit();33 errdefer list.deinit();
46 while (try iter.nextAlloc(alloc, .{})) |row| {34 var stmt: Statement = try .prepare(self, query);
47 try list.append(row);35 defer stmt.finalize();
48 }36 try stmt.bindArgs(alloc, args);
37 const iter = stmt.iterate();
38 errdefer iter.reset();
39 while (try iter.step(alloc, T)) |row| try list.append(row);
49 return list.toOwnedSlice();40 return list.toOwnedSlice();
50}41}
5142
...@@ -53,29 +44,29 @@ pub fn exec(self: *Self, alloc: std.mem.Allocator, comptime query: string, args:...@@ -53,29 +44,29 @@ pub fn exec(self: *Self, alloc: std.mem.Allocator, comptime query: string, args:
53 const t = tracer.trace(@src(), " {s}", .{query});44 const t = tracer.trace(@src(), " {s}", .{query});
54 defer t.end();45 defer t.end();
5546
56 var stmt = try self.prepare(query);47 var stmt: Statement = try .prepare(self, query);
57 defer stmt.deinit();48 defer stmt.finalize();
58 try stmt.execAlloc(alloc, .{}, args);49 try stmt.bindArgs(alloc, args);
50 try stmt.exec(alloc);
59}51}
6052
61pub fn first(self: *Self, alloc: std.mem.Allocator, comptime T: type, comptime query: string, args: anytype) !?T {53pub fn first(self: *Self, alloc: std.mem.Allocator, comptime T: type, comptime query: string, args: anytype) !?T {
62 const t = tracer.trace(@src(), " {s}", .{query});54 const t = tracer.trace(@src(), " {s}", .{query});
63 defer t.end();55 defer t.end();
6456
65 var stmt = try self.prepare(query);57 var stmt: Statement = try .prepare(self, query);
66 defer stmt.deinit();58 defer stmt.finalize();
67 return try stmt.oneAlloc(T, alloc, .{}, args);59 try stmt.bindArgs(alloc, args);
68}60 const iter = stmt.iterate();
6961 errdefer iter.reset();
70pub fn prepareDynamic(self: *Self, query: string) !sqlite.DynamicStatement {62 return iter.step(alloc, T);
71 return self.db.prepareDynamic(query);
72}63}
7364
74pub fn doesTableExist(self: *Self, alloc: std.mem.Allocator, name: string) !bool {65pub fn doesTableExist(self: *Self, alloc: std.mem.Allocator, name: string) !bool {
75 const t = tracer.trace(@src(), " {s}", .{name});66 const t = tracer.trace(@src(), " {s}", .{name});
76 defer t.end();67 defer t.end();
7768
78 for (try self.collect(alloc, string, "select name from sqlite_master where type=? AND name=?", .{ .type = "table", .name = name })) |item| {69 for (try self.collect(alloc, string, "select name from sqlite_master where type = ? AND name = ?", .{ .type = "table", .name = name })) |item| {
79 if (std.mem.eql(u8, item, name)) {70 if (std.mem.eql(u8, item, name)) {
80 return true;71 return true;
81 }72 }
...@@ -187,3 +178,255 @@ pub const pragma = struct {...@@ -187,3 +178,255 @@ pub const pragma = struct {
187 return try self.collect(alloc, Pragma.TableInfo, "pragma table_info(" ++ name ++ ")", .{});178 return try self.collect(alloc, Pragma.TableInfo, "pragma table_info(" ++ name ++ ")", .{});
188 }179 }
189};180};
181
182pub const c = @cImport({
183 @cInclude("sqlite3.h");
184});
185
186pub const s = struct {
187 const Error = error{
188 SQLITE_ERROR,
189 SQLITE_INTERNAL,
190 SQLITE_PERM,
191 SQLITE_ABORT,
192 SQLITE_BUSY,
193 SQLITE_LOCKED,
194 OutOfMemory,
195 SQLITE_READONLY,
196 SQLITE_INTERRUPT,
197 SQLITE_IOERR,
198 SQLITE_CORRUPT,
199 SQLITE_NOTFOUND,
200 SQLITE_FULL,
201 SQLITE_CANTOPEN,
202 SQLITE_PROTOCOL,
203 SQLITE_EMPTY,
204 SQLITE_SCHEMA,
205 SQLITE_TOOBIG,
206 SQLITE_CONSTRAINT,
207 SQLITE_MISMATCH,
208 SQLITE_MISUSE,
209 SQLITE_NOLFS,
210 SQLITE_AUTH,
211 SQLITE_FORMAT,
212 SQLITE_RANGE,
213 SQLITE_NOTADB,
214 SQLITE_NOTICE,
215 SQLITE_WARNING,
216 };
217 pub fn rc2e(code: c_int) Error {
218 if (code == c.SQLITE_ERROR) return error.SQLITE_ERROR;
219 if (code == c.SQLITE_INTERNAL) return error.SQLITE_INTERNAL;
220 if (code == c.SQLITE_PERM) return error.SQLITE_PERM;
221 if (code == c.SQLITE_ABORT) return error.SQLITE_ABORT;
222 if (code == c.SQLITE_BUSY) return error.SQLITE_BUSY;
223 if (code == c.SQLITE_LOCKED) return error.SQLITE_LOCKED;
224 if (code == c.SQLITE_NOMEM) return error.OutOfMemory;
225 if (code == c.SQLITE_READONLY) return error.SQLITE_READONLY;
226 if (code == c.SQLITE_INTERRUPT) return error.SQLITE_INTERRUPT;
227 if (code == c.SQLITE_IOERR) return error.SQLITE_IOERR;
228 if (code == c.SQLITE_CORRUPT) return error.SQLITE_CORRUPT;
229 if (code == c.SQLITE_NOTFOUND) return error.SQLITE_NOTFOUND;
230 if (code == c.SQLITE_FULL) return error.SQLITE_FULL;
231 if (code == c.SQLITE_CANTOPEN) return error.SQLITE_CANTOPEN;
232 if (code == c.SQLITE_PROTOCOL) return error.SQLITE_PROTOCOL;
233 if (code == c.SQLITE_EMPTY) return error.SQLITE_EMPTY;
234 if (code == c.SQLITE_SCHEMA) return error.SQLITE_SCHEMA;
235 if (code == c.SQLITE_TOOBIG) return error.SQLITE_TOOBIG;
236 if (code == c.SQLITE_CONSTRAINT) return error.SQLITE_CONSTRAINT;
237 if (code == c.SQLITE_MISMATCH) return error.SQLITE_MISMATCH;
238 if (code == c.SQLITE_MISUSE) return error.SQLITE_MISUSE;
239 if (code == c.SQLITE_NOLFS) return error.SQLITE_NOLFS;
240 if (code == c.SQLITE_AUTH) return error.SQLITE_AUTH;
241 if (code == c.SQLITE_FORMAT) return error.SQLITE_FORMAT;
242 if (code == c.SQLITE_RANGE) return error.SQLITE_RANGE;
243 if (code == c.SQLITE_NOTADB) return error.SQLITE_NOTADB;
244 if (code == c.SQLITE_NOTICE) return error.SQLITE_NOTICE;
245 if (code == c.SQLITE_WARNING) return error.SQLITE_WARNING;
246 unreachable;
247 }
248 pub fn rc2p(code: c_int) Error {
249 if (builtin.mode == .Debug) @panic(std.mem.sliceTo(c.sqlite3_errstr(code), 0));
250 return rc2e(code);
251 }
252 pub fn assert(code: c_int) void {
253 if (code == c.SQLITE_OK) return;
254 @panic(std.mem.sliceTo(c.sqlite3_errstr(code), 0));
255 }
256 pub fn please(code: c_int) !void {
257 if (code == c.SQLITE_OK) return;
258 return rc2p(code);
259 }
260};
261
262pub const Statement = struct {
263 stmt: *c.sqlite3_stmt,
264
265 pub fn prepare(driver: *Self, query: []const u8) !Statement {
266 var stmt: ?*c.sqlite3_stmt = null;
267 var flags: c_uint = 0;
268 _ = &flags;
269 try s.please(c.sqlite3_prepare_v3(driver.db, query.ptr, @intCast(query.len), flags, &stmt, null));
270 return .{ .stmt = stmt.? };
271 }
272
273 pub fn finalize(stmt: Statement) void {
274 s.assert(c.sqlite3_finalize(stmt.stmt));
275 }
276
277 pub fn bindArgs(stmt: Statement, allocator: std.mem.Allocator, args: anytype) !void {
278 if (comptime extras.isSlice(@TypeOf(args))) {
279 for (args, 0..) |a, i| {
280 const A = @TypeOf(a);
281 try bindType(stmt, allocator, i + 1, A, a);
282 }
283 return;
284 }
285 inline for (@typeInfo(@TypeOf(args)).@"struct".fields, 0..) |f, i| {
286 try bindType(stmt, allocator, i + 1, f.type, @field(args, f.name));
287 }
288 }
289
290 fn bindType(stmt: Statement, allocator: std.mem.Allocator, idx: usize, T: type, value: T) !void {
291 if (comptime extras.isZigString(T)) {
292 return s.please(c.sqlite3_bind_text64(stmt.stmt, @intCast(idx), value.ptr, value.len, c.SQLITE_STATIC, c.SQLITE_UTF8));
293 }
294 if (comptime extras.isArrayOf(u8)(T)) {
295 return s.please(c.sqlite3_bind_blob64(stmt.stmt, @intCast(idx), &value, value.len, c.SQLITE_TRANSIENT));
296 }
297 switch (@typeInfo(T)) {
298 .@"struct" => |info| {
299 if (@hasDecl(T, "BaseType")) return bindBaseType(stmt, allocator, idx, T, value);
300 if (info.layout == .@"packed") return bindType(stmt, allocator, idx, info.backing_integer.?, @bitCast(value));
301 return bindBaseType(stmt, allocator, idx, T, value);
302 },
303 .int => |info| {
304 comptime std.debug.assert(info.bits <= 64);
305 if (value > std.math.maxInt(c.sqlite_int64)) return error.Overflow;
306 if (value < std.math.minInt(c.sqlite_int64)) return error.Overflow;
307 return s.please(c.sqlite3_bind_int64(stmt.stmt, @intCast(idx), @intCast(value)));
308 },
309 .optional => |info| {
310 if (value == null) return s.please(c.sqlite3_bind_null(stmt.stmt, @intCast(idx)));
311 return bindType(stmt, allocator, idx, info.child, value.?);
312 },
313 .bool => {
314 return bindType(stmt, allocator, idx, u1, @intFromBool(value));
315 },
316 .@"enum" => {
317 if (T.BaseType == []const u8 and !@hasDecl(T, "bindField")) {
318 return bindType(stmt, allocator, idx, []const u8, @tagName(value));
319 }
320 return bindBaseType(stmt, allocator, idx, T, value);
321 },
322 .@"union" => {
323 switch (value) {
324 inline else => |val| return bindType(stmt, allocator, idx, @TypeOf(val), val),
325 }
326 },
327 else => @compileError(@typeName(T)),
328 }
329 }
330
331 fn bindBaseType(stmt: Statement, allocator: std.mem.Allocator, idx: usize, T: type, value: T) !void {
332 const bind_fn = T.bindField;
333 const info = @typeInfo(@TypeOf(bind_fn)).@"fn";
334 switch (info.params.len) {
335 1 => {
336 const base = try value.bindField();
337 return bindType(stmt, allocator, idx, @TypeOf(base), base);
338 },
339 2 => {
340 const base = try value.bindField(allocator);
341 return bindType(stmt, allocator, idx, @TypeOf(base), base);
342 },
343 else => comptime unreachable,
344 }
345 }
346
347 pub fn exec(stmt: Statement, allocator: std.mem.Allocator) !void {
348 const iter = stmt.iterate();
349 errdefer iter.reset();
350 const row = try iter.step(allocator, void);
351 if (builtin.mode == .Debug) std.debug.assert(row == null);
352 }
353
354 pub fn iterate(stmt: Statement) Iterator {
355 return .{ .stmt = stmt.stmt };
356 }
357
358 pub const Iterator = struct {
359 stmt: *c.sqlite3_stmt,
360
361 pub fn reset(iter: Iterator) void {
362 return s.please(c.sqlite3_reset(iter.stmt)) catch {};
363 }
364
365 pub fn step(iter: Iterator, allocator: std.mem.Allocator, T: type) !?T {
366 const code = c.sqlite3_step(iter.stmt);
367 if (code == c.SQLITE_DONE) return null;
368 if (code != c.SQLITE_ROW) return s.rc2p(code);
369 if (T == void) return;
370 if (T == string) return try readType(iter, allocator, 0, string);
371 if (@typeInfo(T) == .int) return try readType(iter, allocator, 0, T);
372 var result: T = undefined;
373 inline for (@typeInfo(T).@"struct".fields, 0..) |f, i| {
374 @field(result, f.name) = try readType(iter, allocator, i, f.type);
375 }
376 return result;
377 }
378
379 fn readType(iter: Iterator, allocator: std.mem.Allocator, idx: usize, T: type) !T {
380 if (comptime extras.isZigString(T)) {
381 const res = c.sqlite3_column_text(iter.stmt, @intCast(idx));
382 if (res == null) return "";
383 const ptr: [*:0]const u8 = @ptrCast(res);
384 const len = c.sqlite3_column_bytes(iter.stmt, @intCast(idx));
385 return try allocator.dupe(u8, ptr[0..@intCast(len) :0]);
386 }
387 if (comptime extras.isArrayOf(u8)(T)) {
388 const info = @typeInfo(T).array;
389 const ptr: [*:0]const u8 = @ptrCast(c.sqlite3_column_blob(iter.stmt, @intCast(idx)));
390 const len = c.sqlite3_column_bytes(iter.stmt, @intCast(idx));
391 std.debug.assert(len == info.len);
392 return ptr[0..info.len].*;
393 }
394 switch (@typeInfo(T)) {
395 .int => |info| {
396 comptime std.debug.assert(info.bits <= 64);
397 const res = c.sqlite3_column_int64(iter.stmt, @intCast(idx));
398 if (res > std.math.maxInt(T)) return error.Overflow;
399 if (res < std.math.minInt(T)) return error.Overflow;
400 return @intCast(res);
401 },
402 .bool => {
403 return @bitCast(try readType(iter, allocator, idx, u1));
404 },
405 .@"struct" => |info| {
406 if (@hasDecl(T, "BaseType")) return readBaseType(iter, allocator, idx, T);
407 if (info.layout == .@"packed") return @bitCast(try readType(iter, allocator, idx, info.backing_integer.?));
408 return readBaseType(iter, allocator, idx, T);
409 },
410 .optional => |info| {
411 if (c.sqlite3_column_type(iter.stmt, @intCast(idx)) == c.SQLITE_NULL) return null;
412 return try readType(iter, allocator, idx, info.child);
413 },
414 .@"enum" => {
415 if (T.BaseType == string) {
416 const ptr: [*:0]const u8 = @ptrCast(c.sqlite3_column_text(iter.stmt, @intCast(idx)));
417 const len = c.sqlite3_column_bytes(iter.stmt, @intCast(idx));
418 const str = ptr[0..@intCast(len) :0];
419 const enm = std.meta.stringToEnum(T, str);
420 return enm orelse T.default;
421 }
422 },
423 else => {}, // else => @compileError(T), // https://codeberg.org/ziglang/zig/issues/32119
424 }
425 }
426
427 fn readBaseType(iter: Iterator, allocator: std.mem.Allocator, idx: usize, T: type) !T {
428 const base = try readType(iter, allocator, idx, T.BaseType);
429 return T.readField(allocator, base);
430 }
431 };
432};
zig.mod+9-1
...@@ -4,7 +4,15 @@ main: src/lib.zig...@@ -4,7 +4,15 @@ main: src/lib.zig
4license: MPL-2.04license: MPL-2.0
5description: The ORM library for Zig.5description: The ORM library for Zig.
6dependencies:6dependencies:
7 - src: git https://github.com/vrischmann/zig-sqlite7 - src: http https://sqlite.org/2025/sqlite-amalgamation-3480000.zip sha256-d9a15a42db7c78f88fe3d3c5945acce2f4bfe9e4da9f685cd19f6ea1d40aa884
8 id: 5wea8xz8pv9w3gv4ve959e6wnxp372g6t4dpzf8j
9 license: blessing
10 description: SQLite is a C-language library that implements a small, fast, self-contained, high-reliability, full-featured, SQL database engine.
11 c_include_dirs:
12 - sqlite-amalgamation-3480000
13 c_source_files:
14 - sqlite-amalgamation-3480000/sqlite3.c
15
8 - src: git https://github.com/nektro/zig-tracer16 - src: git https://github.com/nektro/zig-tracer
9 - src: git https://github.com/nektro/zig-whatwg-url17 - src: git https://github.com/nektro/zig-whatwg-url
10 - src: git https://github.com/nektro/zig-extras18 - src: git https://github.com/nektro/zig-extras