From 37493d9907f1d7f224ca0b08245f5f3d5323c53c Mon Sep 17 00:00:00 2001 From: Alexander Rolley Date: Wed, 20 May 2026 10:26:18 +0200 Subject: Add missing fingerprint. --- build.zig.zon | 1 + 1 file changed, 1 insertion(+) diff --git a/build.zig.zon b/build.zig.zon index 5ac92c9..705debf 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,6 +1,7 @@ .{ .name = .memora, .version = "0.0.0", + .fingerprint = 0xf901ac636a172b52, .minimum_zig_version = "0.16.0", .dependencies = .{ }, -- cgit v1.2.3-70-g09d2 From 004b9941cbb703ceebb4804a6f0a2328889d38a8 Mon Sep 17 00:00:00 2001 From: Alexander Rolley Date: Wed, 20 May 2026 15:27:00 +0200 Subject: Start on Sqlite3. --- build.zig | 9 ++- src/db/root.zig | 7 +- src/db/sqlite.h | 1 + src/db/sqlite.zig | 192 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 205 insertions(+), 4 deletions(-) create mode 100644 src/db/sqlite.h create mode 100644 src/db/sqlite.zig diff --git a/build.zig b/build.zig index c84375a..d18745c 100644 --- a/build.zig +++ b/build.zig @@ -4,10 +4,17 @@ pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); - const db_mod = b.addModule("db", .{ + const sqlite_c = b.addTranslateC(.{ + .root_source_file = b.path("src/db/sqlite.h"), + .target = target, + .optimize = optimize, + }); + + var db_mod = b.addModule("db", .{ .root_source_file = b.path("src/db/root.zig"), .target = target, }); + db_mod.addImport("sqlite", sqlite_c.createModule()); const api_mod = b.addModule("api", .{ .root_source_file = b.path("src/api/root.zig"), diff --git a/src/db/root.zig b/src/db/root.zig index 049a63b..4ca9847 100644 --- a/src/db/root.zig +++ b/src/db/root.zig @@ -1,9 +1,10 @@ -//! Implements the layer between the sqlite -//! database and the interface for the api endpoints or -//! the cli. +//! Implements the layer between the sqlite database and the interface for the +//! API endpoints or the CLI. const std = @import("std"); +pub const sqlite = @import("sqlite.zig"); + test { _ = std.testing.refAllDecls(@This()); } diff --git a/src/db/sqlite.h b/src/db/sqlite.h new file mode 100644 index 0000000..f52e1f0 --- /dev/null +++ b/src/db/sqlite.h @@ -0,0 +1 @@ +#include diff --git a/src/db/sqlite.zig b/src/db/sqlite.zig new file mode 100644 index 0000000..ad76992 --- /dev/null +++ b/src/db/sqlite.zig @@ -0,0 +1,192 @@ +const std = @import("std"); +const mem = std.mem; +const sqlite = @import("sqlite"); + +pub const Sqlite3Database = struct { + handle: *sqlite.sqlite3, + + // FIXME: this is shit, generate these enums at comptime where + // primary and extended codes are given as tuples + // zig fmt: off + pub const PrimaryResultCode = enum(u8) { + /// Successful result. + ok = 0, + /// Generic error. + @"error" = 1, + /// Internal logic error in SQLite. + internal = 2, + /// Access permission denied. + perm = 3, + /// Callback routine requested an abort. + abort = 4, + /// The database file is locked. + busy = 5, + /// A table in the database is locked. + locked = 6, + /// A malloc() failed. + nomem = 7, + /// Attempt to write a readonly database. + readonly = 8, + /// Operation terminated by sqlite3_interrupt(). + interrupt = 9, + /// Some kind of disk I/O error occurred. + ioerr = 10, + /// The database disk image is malformed. + corrupt = 11, + /// Unknown opcode in sqlite3_file_control(). + notfound = 12, + /// Insertion failed because database is full. + full = 13, + /// Unable to open the database file. + cantopen = 14, + /// Database lock protocol error. + protocol = 15, + /// Internal use only. + empty = 16, + /// The database schema changed. + schema = 17, + /// String or BLOB exceeds size limit. + toobig = 18, + /// Abort due to constraint violation. + constraint = 19, + /// Data type mismatch. + mismatch = 20, + /// Library used incorrectly. + misuse = 21, + /// Uses OS features not supported on host. + nolfs = 22, + /// Authorization denied. + auth = 23, + /// Not used. + format = 24, + /// 2nd parameter to sqlite3_bind out of range. + range = 25, + /// File opened that is not a database file. + notadb = 26, + /// Notifications from sqlite3_log(). + notice = 27, + /// Warnings from sqlite3_log(). + warning = 28, + /// sqlite3_step() has another row ready. + row = 100, + /// sqlite3_step() has finished executing. + done = 101, + }; + + // zig fmt: off + pub const ExtendedResultCode = enum(i32) { + err_missing_collseq = (@as(i32, @intFromEnum(PrimaryResultCode.@"error" )) | (1 << 8)), + err_retry = (@as(i32, @intFromEnum(PrimaryResultCode.@"error" )) | (2 << 8)), + err_snapshot = (@as(i32, @intFromEnum(PrimaryResultCode.@"error" )) | (3 << 8)), + err_reservesize = (@as(i32, @intFromEnum(PrimaryResultCode.@"error" )) | (4 << 8)), + err_key = (@as(i32, @intFromEnum(PrimaryResultCode.@"error" )) | (5 << 8)), + err_unable = (@as(i32, @intFromEnum(PrimaryResultCode.@"error" )) | (6 << 8)), + ioerr_read = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (1 << 8)), + ioerr_short_read = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (2 << 8)), + ioerr_write = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (3 << 8)), + ioerr_fsync = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (4 << 8)), + ioerr_dir_fsync = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (5 << 8)), + ioerr_truncate = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (6 << 8)), + ioerr_fstat = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (7 << 8)), + ioerr_unlock = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (8 << 8)), + ioerr_rdlock = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (9 << 8)), + ioerr_delete = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (10 << 8)), + ioerr_blocked = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (11 << 8)), + ioerr_nomem = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (12 << 8)), + ioerr_access = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (13 << 8)), + ioerr_checkreservedlock = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (14 << 8)), + ioerr_lock = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (15 << 8)), + ioerr_close = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (16 << 8)), + ioerr_dir_close = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (17 << 8)), + ioerr_shmopen = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (18 << 8)), + ioerr_shmsize = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (19 << 8)), + ioerr_shmlock = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (20 << 8)), + ioerr_shmmap = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (21 << 8)), + ioerr_seek = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (22 << 8)), + ioerr_delete_noent = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (23 << 8)), + ioerr_mmap = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (24 << 8)), + ioerr_gettemppath = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (25 << 8)), + ioerr_convpath = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (26 << 8)), + ioerr_vnode = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (27 << 8)), + ioerr_auth = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (28 << 8)), + ioerr_begin_atomic = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (29 << 8)), + ioerr_commit_atomic = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (30 << 8)), + ioerr_rollback_atomic = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (31 << 8)), + ioerr_data = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (32 << 8)), + ioerr_corruptfs = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (33 << 8)), + ioerr_in_page = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (34 << 8)), + ioerr_badkey = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (35 << 8)), + ioerr_codec = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (36 << 8)), + locked_sharedcache = (@as(i32, @intFromEnum(PrimaryResultCode.locked )) | (1 << 8)), + locked_vtab = (@as(i32, @intFromEnum(PrimaryResultCode.locked )) | (2 << 8)), + busy_recovery = (@as(i32, @intFromEnum(PrimaryResultCode.busy )) | (1 << 8)), + busy_snapshot = (@as(i32, @intFromEnum(PrimaryResultCode.busy )) | (2 << 8)), + busy_timeout = (@as(i32, @intFromEnum(PrimaryResultCode.busy )) | (3 << 8)), + cantopen_notempdir = (@as(i32, @intFromEnum(PrimaryResultCode.cantopen )) | (1 << 8)), + cantopen_isdir = (@as(i32, @intFromEnum(PrimaryResultCode.cantopen )) | (2 << 8)), + cantopen_fullpath = (@as(i32, @intFromEnum(PrimaryResultCode.cantopen )) | (3 << 8)), + cantopen_convpath = (@as(i32, @intFromEnum(PrimaryResultCode.cantopen )) | (4 << 8)), + /// Not used. + cantopen_dirtywal = (@as(i32, @intFromEnum(PrimaryResultCode.cantopen )) | (5 << 8)), + cantopen_symlink = (@as(i32, @intFromEnum(PrimaryResultCode.cantopen )) | (6 << 8)), + corrupt_vtab = (@as(i32, @intFromEnum(PrimaryResultCode.corrupt )) | (1 << 8)), + corrupt_sequence = (@as(i32, @intFromEnum(PrimaryResultCode.corrupt )) | (2 << 8)), + corrupt_index = (@as(i32, @intFromEnum(PrimaryResultCode.corrupt )) | (3 << 8)), + readonly_recovery = (@as(i32, @intFromEnum(PrimaryResultCode.readonly )) | (1 << 8)), + readonly_cantlock = (@as(i32, @intFromEnum(PrimaryResultCode.readonly )) | (2 << 8)), + readonly_rollback = (@as(i32, @intFromEnum(PrimaryResultCode.readonly )) | (3 << 8)), + readonly_dbmoved = (@as(i32, @intFromEnum(PrimaryResultCode.readonly )) | (4 << 8)), + readonly_cantinit = (@as(i32, @intFromEnum(PrimaryResultCode.readonly )) | (5 << 8)), + readonly_directory = (@as(i32, @intFromEnum(PrimaryResultCode.readonly )) | (6 << 8)), + abort_rollback = (@as(i32, @intFromEnum(PrimaryResultCode.abort )) | (2 << 8)), + constraint_check = (@as(i32, @intFromEnum(PrimaryResultCode.constraint )) | (1 << 8)), + constraint_commithook = (@as(i32, @intFromEnum(PrimaryResultCode.constraint )) | (2 << 8)), + constraint_foreignkey = (@as(i32, @intFromEnum(PrimaryResultCode.constraint )) | (3 << 8)), + constraint_function = (@as(i32, @intFromEnum(PrimaryResultCode.constraint )) | (4 << 8)), + constraint_notnull = (@as(i32, @intFromEnum(PrimaryResultCode.constraint )) | (5 << 8)), + constraint_primarykey = (@as(i32, @intFromEnum(PrimaryResultCode.constraint )) | (6 << 8)), + constraint_trigger = (@as(i32, @intFromEnum(PrimaryResultCode.constraint )) | (7 << 8)), + constraint_unique = (@as(i32, @intFromEnum(PrimaryResultCode.constraint )) | (8 << 8)), + constraint_vtab = (@as(i32, @intFromEnum(PrimaryResultCode.constraint )) | (9 << 8)), + constraint_rowid = (@as(i32, @intFromEnum(PrimaryResultCode.constraint )) | (10 << 8)), + constraint_pinned = (@as(i32, @intFromEnum(PrimaryResultCode.constraint )) | (11 << 8)), + constraint_datatype = (@as(i32, @intFromEnum(PrimaryResultCode.constraint )) | (12 << 8)), + notice_recover_wal = (@as(i32, @intFromEnum(PrimaryResultCode.notice )) | (1 << 8)), + notice_recover_rollback = (@as(i32, @intFromEnum(PrimaryResultCode.notice )) | (2 << 8)), + notice_rbu = (@as(i32, @intFromEnum(PrimaryResultCode.notice )) | (3 << 8)), + warning_autoindex = (@as(i32, @intFromEnum(PrimaryResultCode.warning )) | (1 << 8)), + auth_user = (@as(i32, @intFromEnum(PrimaryResultCode.auth )) | (1 << 8)), + ok_load_permanently = (@as(i32, @intFromEnum(PrimaryResultCode.ok )) | (1 << 8)), + /// Internal only. + ok_symlink = (@as(i32, @intFromEnum(PrimaryResultCode.ok )) | (2 << 8)), + }; + + pub const Error = error{ExtendedResultCode}; + + pub const Self = @This(); + + pub fn init(path: []const u8) Error!Self { + var handle: ?*sqlite.sqlite3 = null; + + const result = sqlite.sqlite3_open(path.ptr, &handle); + const primary_code = @as(PrimaryResultCode, @enumFromInt(result)); + + switch (primary_code) { + .ok => .{}, + else => { + const code = sqlite.sqlite3_extended_errcode(handle); + return error.@as(ExtendedResultCode, @enumFromInt(code)); + }, + } + + return .{ .handle = handle }; + } +}; + +test { + const db: Sqlite3Database = try .init(":memory:"); + + _ = db; + + std.debug.print("asdf", .{}); +} -- cgit v1.2.3-70-g09d2 From c50de586c0b121615802dea2a84632901e8dc25a Mon Sep 17 00:00:00 2001 From: Alexander Rolley Date: Wed, 20 May 2026 20:50:08 +0200 Subject: Actually link against Sqlite3 library. --- build.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.zig b/build.zig index d18745c..81eadfb 100644 --- a/build.zig +++ b/build.zig @@ -9,8 +9,9 @@ pub fn build(b: *std.Build) void { .target = target, .optimize = optimize, }); + sqlite_c.linkSystemLibrary("sqlite3", .{}); - var db_mod = b.addModule("db", .{ + const db_mod = b.addModule("db", .{ .root_source_file = b.path("src/db/root.zig"), .target = target, }); -- cgit v1.2.3-70-g09d2 From d4b3a64ba6ad2862eb31e8c3081b7247c6b7b7b3 Mon Sep 17 00:00:00 2001 From: Alexander Rolley Date: Wed, 20 May 2026 20:50:33 +0200 Subject: Implement Sqlite3 result codes and start on database abstraction. --- src/db/sqlite.zig | 371 +++++++++++++++++++++++++++++------------------------- 1 file changed, 200 insertions(+), 171 deletions(-) diff --git a/src/db/sqlite.zig b/src/db/sqlite.zig index ad76992..1e45537 100644 --- a/src/db/sqlite.zig +++ b/src/db/sqlite.zig @@ -1,192 +1,221 @@ const std = @import("std"); -const mem = std.mem; const sqlite = @import("sqlite"); -pub const Sqlite3Database = struct { +pub const Database = struct { handle: *sqlite.sqlite3, - // FIXME: this is shit, generate these enums at comptime where - // primary and extended codes are given as tuples - // zig fmt: off - pub const PrimaryResultCode = enum(u8) { - /// Successful result. - ok = 0, - /// Generic error. - @"error" = 1, - /// Internal logic error in SQLite. - internal = 2, - /// Access permission denied. - perm = 3, - /// Callback routine requested an abort. - abort = 4, - /// The database file is locked. - busy = 5, - /// A table in the database is locked. - locked = 6, - /// A malloc() failed. - nomem = 7, - /// Attempt to write a readonly database. - readonly = 8, - /// Operation terminated by sqlite3_interrupt(). - interrupt = 9, - /// Some kind of disk I/O error occurred. - ioerr = 10, - /// The database disk image is malformed. - corrupt = 11, - /// Unknown opcode in sqlite3_file_control(). - notfound = 12, - /// Insertion failed because database is full. - full = 13, - /// Unable to open the database file. - cantopen = 14, - /// Database lock protocol error. - protocol = 15, - /// Internal use only. - empty = 16, - /// The database schema changed. - schema = 17, - /// String or BLOB exceeds size limit. - toobig = 18, - /// Abort due to constraint violation. - constraint = 19, - /// Data type mismatch. - mismatch = 20, - /// Library used incorrectly. - misuse = 21, - /// Uses OS features not supported on host. - nolfs = 22, - /// Authorization denied. - auth = 23, - /// Not used. - format = 24, - /// 2nd parameter to sqlite3_bind out of range. - range = 25, - /// File opened that is not a database file. - notadb = 26, - /// Notifications from sqlite3_log(). - notice = 27, - /// Warnings from sqlite3_log(). - warning = 28, - /// sqlite3_step() has another row ready. - row = 100, - /// sqlite3_step() has finished executing. - done = 101, - }; + pub const Error = set: { + var set: type = error{}; - // zig fmt: off - pub const ExtendedResultCode = enum(i32) { - err_missing_collseq = (@as(i32, @intFromEnum(PrimaryResultCode.@"error" )) | (1 << 8)), - err_retry = (@as(i32, @intFromEnum(PrimaryResultCode.@"error" )) | (2 << 8)), - err_snapshot = (@as(i32, @intFromEnum(PrimaryResultCode.@"error" )) | (3 << 8)), - err_reservesize = (@as(i32, @intFromEnum(PrimaryResultCode.@"error" )) | (4 << 8)), - err_key = (@as(i32, @intFromEnum(PrimaryResultCode.@"error" )) | (5 << 8)), - err_unable = (@as(i32, @intFromEnum(PrimaryResultCode.@"error" )) | (6 << 8)), - ioerr_read = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (1 << 8)), - ioerr_short_read = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (2 << 8)), - ioerr_write = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (3 << 8)), - ioerr_fsync = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (4 << 8)), - ioerr_dir_fsync = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (5 << 8)), - ioerr_truncate = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (6 << 8)), - ioerr_fstat = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (7 << 8)), - ioerr_unlock = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (8 << 8)), - ioerr_rdlock = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (9 << 8)), - ioerr_delete = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (10 << 8)), - ioerr_blocked = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (11 << 8)), - ioerr_nomem = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (12 << 8)), - ioerr_access = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (13 << 8)), - ioerr_checkreservedlock = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (14 << 8)), - ioerr_lock = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (15 << 8)), - ioerr_close = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (16 << 8)), - ioerr_dir_close = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (17 << 8)), - ioerr_shmopen = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (18 << 8)), - ioerr_shmsize = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (19 << 8)), - ioerr_shmlock = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (20 << 8)), - ioerr_shmmap = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (21 << 8)), - ioerr_seek = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (22 << 8)), - ioerr_delete_noent = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (23 << 8)), - ioerr_mmap = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (24 << 8)), - ioerr_gettemppath = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (25 << 8)), - ioerr_convpath = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (26 << 8)), - ioerr_vnode = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (27 << 8)), - ioerr_auth = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (28 << 8)), - ioerr_begin_atomic = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (29 << 8)), - ioerr_commit_atomic = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (30 << 8)), - ioerr_rollback_atomic = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (31 << 8)), - ioerr_data = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (32 << 8)), - ioerr_corruptfs = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (33 << 8)), - ioerr_in_page = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (34 << 8)), - ioerr_badkey = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (35 << 8)), - ioerr_codec = (@as(i32, @intFromEnum(PrimaryResultCode.ioerr )) | (36 << 8)), - locked_sharedcache = (@as(i32, @intFromEnum(PrimaryResultCode.locked )) | (1 << 8)), - locked_vtab = (@as(i32, @intFromEnum(PrimaryResultCode.locked )) | (2 << 8)), - busy_recovery = (@as(i32, @intFromEnum(PrimaryResultCode.busy )) | (1 << 8)), - busy_snapshot = (@as(i32, @intFromEnum(PrimaryResultCode.busy )) | (2 << 8)), - busy_timeout = (@as(i32, @intFromEnum(PrimaryResultCode.busy )) | (3 << 8)), - cantopen_notempdir = (@as(i32, @intFromEnum(PrimaryResultCode.cantopen )) | (1 << 8)), - cantopen_isdir = (@as(i32, @intFromEnum(PrimaryResultCode.cantopen )) | (2 << 8)), - cantopen_fullpath = (@as(i32, @intFromEnum(PrimaryResultCode.cantopen )) | (3 << 8)), - cantopen_convpath = (@as(i32, @intFromEnum(PrimaryResultCode.cantopen )) | (4 << 8)), - /// Not used. - cantopen_dirtywal = (@as(i32, @intFromEnum(PrimaryResultCode.cantopen )) | (5 << 8)), - cantopen_symlink = (@as(i32, @intFromEnum(PrimaryResultCode.cantopen )) | (6 << 8)), - corrupt_vtab = (@as(i32, @intFromEnum(PrimaryResultCode.corrupt )) | (1 << 8)), - corrupt_sequence = (@as(i32, @intFromEnum(PrimaryResultCode.corrupt )) | (2 << 8)), - corrupt_index = (@as(i32, @intFromEnum(PrimaryResultCode.corrupt )) | (3 << 8)), - readonly_recovery = (@as(i32, @intFromEnum(PrimaryResultCode.readonly )) | (1 << 8)), - readonly_cantlock = (@as(i32, @intFromEnum(PrimaryResultCode.readonly )) | (2 << 8)), - readonly_rollback = (@as(i32, @intFromEnum(PrimaryResultCode.readonly )) | (3 << 8)), - readonly_dbmoved = (@as(i32, @intFromEnum(PrimaryResultCode.readonly )) | (4 << 8)), - readonly_cantinit = (@as(i32, @intFromEnum(PrimaryResultCode.readonly )) | (5 << 8)), - readonly_directory = (@as(i32, @intFromEnum(PrimaryResultCode.readonly )) | (6 << 8)), - abort_rollback = (@as(i32, @intFromEnum(PrimaryResultCode.abort )) | (2 << 8)), - constraint_check = (@as(i32, @intFromEnum(PrimaryResultCode.constraint )) | (1 << 8)), - constraint_commithook = (@as(i32, @intFromEnum(PrimaryResultCode.constraint )) | (2 << 8)), - constraint_foreignkey = (@as(i32, @intFromEnum(PrimaryResultCode.constraint )) | (3 << 8)), - constraint_function = (@as(i32, @intFromEnum(PrimaryResultCode.constraint )) | (4 << 8)), - constraint_notnull = (@as(i32, @intFromEnum(PrimaryResultCode.constraint )) | (5 << 8)), - constraint_primarykey = (@as(i32, @intFromEnum(PrimaryResultCode.constraint )) | (6 << 8)), - constraint_trigger = (@as(i32, @intFromEnum(PrimaryResultCode.constraint )) | (7 << 8)), - constraint_unique = (@as(i32, @intFromEnum(PrimaryResultCode.constraint )) | (8 << 8)), - constraint_vtab = (@as(i32, @intFromEnum(PrimaryResultCode.constraint )) | (9 << 8)), - constraint_rowid = (@as(i32, @intFromEnum(PrimaryResultCode.constraint )) | (10 << 8)), - constraint_pinned = (@as(i32, @intFromEnum(PrimaryResultCode.constraint )) | (11 << 8)), - constraint_datatype = (@as(i32, @intFromEnum(PrimaryResultCode.constraint )) | (12 << 8)), - notice_recover_wal = (@as(i32, @intFromEnum(PrimaryResultCode.notice )) | (1 << 8)), - notice_recover_rollback = (@as(i32, @intFromEnum(PrimaryResultCode.notice )) | (2 << 8)), - notice_rbu = (@as(i32, @intFromEnum(PrimaryResultCode.notice )) | (3 << 8)), - warning_autoindex = (@as(i32, @intFromEnum(PrimaryResultCode.warning )) | (1 << 8)), - auth_user = (@as(i32, @intFromEnum(PrimaryResultCode.auth )) | (1 << 8)), - ok_load_permanently = (@as(i32, @intFromEnum(PrimaryResultCode.ok )) | (1 << 8)), - /// Internal only. - ok_symlink = (@as(i32, @intFromEnum(PrimaryResultCode.ok )) | (2 << 8)), - }; + for (std.meta.fieldNames(Result.Primary)) |name| { + set = set || @TypeOf(@field(anyerror, name)); + } + for (std.meta.fieldNames(Result.Extended)) |name| { + set = set || @TypeOf(@field(anyerror, name)); + } - pub const Error = error{ExtendedResultCode}; + break :set set; + }; - pub const Self = @This(); + pub const Kind = union(enum) { + temp_in_memory, + temp_on_disk, + path: []const u8, + }; - pub fn init(path: []const u8) Error!Self { + // Source: https://sqlite.org/c3ref/open.html + pub fn init(kind: Kind) Error!Database { + const name = switch (kind) { + .temp_in_memory => ":memory:", + .temp_on_disk => "", + .path => |path| path, + }; var handle: ?*sqlite.sqlite3 = null; - const result = sqlite.sqlite3_open(path.ptr, &handle); - const primary_code = @as(PrimaryResultCode, @enumFromInt(result)); + const result: Result = .from(sqlite.sqlite3_open(name.ptr, &handle)); + + switch (result.primary()) { + .ok => {}, + inline else => |code| return @field(anyerror, @tagName(code)), + } + + return .{ .handle = handle.? }; + } +}; + +// Source: https://sqlite.org/rescode.html +pub const Result = enum(i32) { + _, + + // zig fmt: off + pub const Primary = enum(u8) { + /// Successful result. + ok = 0, + /// Generic error. + @"error" = 1, + /// Internal logic error in SQLite. + internal = 2, + /// Access permission denied. + perm = 3, + /// Callback routine requested an abort. + abort = 4, + /// The database file is locked. + busy = 5, + /// A table in the database is locked. + locked = 6, + /// A malloc() failed. + nomem = 7, + /// Attempt to write a readonly database. + readonly = 8, + /// Operation terminated by sqlite3_interrupt(). + interrupt = 9, + /// Some kind of disk I/O error occurred. + ioerr = 10, + /// The database disk image is malformed. + corrupt = 11, + /// Unknown opcode in sqlite3_file_control(). + notfound = 12, + /// Insertion failed because database is full. + full = 13, + /// Unable to open the database file. + cantopen = 14, + /// Database lock protocol error. + protocol = 15, + /// Internal use only. + empty = 16, + /// The database schema changed. + schema = 17, + /// String or BLOB exceeds size limit. + toobig = 18, + /// Abort due to constraint violation. + constraint = 19, + /// Data type mismatch. + mismatch = 20, + /// Library used incorrectly. + misuse = 21, + /// Uses OS features not supported on host. + nolfs = 22, + /// Authorization denied. + auth = 23, + /// Not used. + format = 24, + /// 2nd parameter to sqlite3_bind out of range. + range = 25, + /// File opened that is not a database file. + notadb = 26, + /// Notifications from sqlite3_log(). + notice = 27, + /// Warnings from sqlite3_log(). + warning = 28, + /// sqlite3_step() has another row ready. + row = 100, + /// sqlite3_step() has finished executing. + done = 101, - switch (primary_code) { - .ok => .{}, - else => { - const code = sqlite.sqlite3_extended_errcode(handle); - return error.@as(ExtendedResultCode, @enumFromInt(code)); - }, + fn as(self: Primary, comptime T: type) T { + return @as(T, @intFromEnum(self)); } + }; - return .{ .handle = handle }; + // zig fmt: off + pub const Extended = enum(i32) { + error_missing_collseq = (Primary.@"error".as(i32) | ( 1 << 8)), + error_retry = (Primary.@"error".as(i32) | ( 2 << 8)), + error_snapshot = (Primary.@"error".as(i32) | ( 3 << 8)), + error_reservesize = (Primary.@"error".as(i32) | ( 4 << 8)), + error_key = (Primary.@"error".as(i32) | ( 5 << 8)), + error_unable = (Primary.@"error".as(i32) | ( 6 << 8)), + ioerr_read = (Primary.ioerr.as(i32) | ( 1 << 8)), + ioerr_short_read = (Primary.ioerr.as(i32) | ( 2 << 8)), + ioerr_write = (Primary.ioerr.as(i32) | ( 3 << 8)), + ioerr_fsync = (Primary.ioerr.as(i32) | ( 4 << 8)), + ioerr_dir_fsync = (Primary.ioerr.as(i32) | ( 5 << 8)), + ioerr_truncate = (Primary.ioerr.as(i32) | ( 6 << 8)), + ioerr_fstat = (Primary.ioerr.as(i32) | ( 7 << 8)), + ioerr_unlock = (Primary.ioerr.as(i32) | ( 8 << 8)), + ioerr_rdlock = (Primary.ioerr.as(i32) | ( 9 << 8)), + ioerr_delete = (Primary.ioerr.as(i32) | (10 << 8)), + ioerr_blocked = (Primary.ioerr.as(i32) | (11 << 8)), + ioerr_nomem = (Primary.ioerr.as(i32) | (12 << 8)), + ioerr_access = (Primary.ioerr.as(i32) | (13 << 8)), + ioerr_checkreservedlock = (Primary.ioerr.as(i32) | (14 << 8)), + ioerr_lock = (Primary.ioerr.as(i32) | (15 << 8)), + ioerr_close = (Primary.ioerr.as(i32) | (16 << 8)), + ioerr_dir_close = (Primary.ioerr.as(i32) | (17 << 8)), + ioerr_shmopen = (Primary.ioerr.as(i32) | (18 << 8)), + ioerr_shmsize = (Primary.ioerr.as(i32) | (19 << 8)), + ioerr_shmlock = (Primary.ioerr.as(i32) | (20 << 8)), + ioerr_shmmap = (Primary.ioerr.as(i32) | (21 << 8)), + ioerr_seek = (Primary.ioerr.as(i32) | (22 << 8)), + ioerr_delete_noent = (Primary.ioerr.as(i32) | (23 << 8)), + ioerr_mmap = (Primary.ioerr.as(i32) | (24 << 8)), + ioerr_gettemppath = (Primary.ioerr.as(i32) | (25 << 8)), + ioerr_convpath = (Primary.ioerr.as(i32) | (26 << 8)), + ioerr_vnode = (Primary.ioerr.as(i32) | (27 << 8)), + ioerr_auth = (Primary.ioerr.as(i32) | (28 << 8)), + ioerr_begin_atomic = (Primary.ioerr.as(i32) | (29 << 8)), + ioerr_commit_atomic = (Primary.ioerr.as(i32) | (30 << 8)), + ioerr_rollback_atomic = (Primary.ioerr.as(i32) | (31 << 8)), + ioerr_data = (Primary.ioerr.as(i32) | (32 << 8)), + ioerr_corruptfs = (Primary.ioerr.as(i32) | (33 << 8)), + ioerr_in_page = (Primary.ioerr.as(i32) | (34 << 8)), + ioerr_badkey = (Primary.ioerr.as(i32) | (35 << 8)), + ioerr_codec = (Primary.ioerr.as(i32) | (36 << 8)), + locked_sharedcache = (Primary.locked.as(i32) | ( 1 << 8)), + locked_vtab = (Primary.locked.as(i32) | ( 2 << 8)), + busy_recovery = (Primary.busy.as(i32) | ( 1 << 8)), + busy_snapshot = (Primary.busy.as(i32) | ( 2 << 8)), + busy_timeout = (Primary.busy.as(i32) | ( 3 << 8)), + cantopen_notempdir = (Primary.cantopen.as(i32) | ( 1 << 8)), + cantopen_isdir = (Primary.cantopen.as(i32) | ( 2 << 8)), + cantopen_fullpath = (Primary.cantopen.as(i32) | ( 3 << 8)), + cantopen_convpath = (Primary.cantopen.as(i32) | ( 4 << 8)), + cantopen_symlink = (Primary.cantopen.as(i32) | ( 6 << 8)), + corrupt_vtab = (Primary.corrupt.as(i32) | ( 1 << 8)), + corrupt_sequence = (Primary.corrupt.as(i32) | ( 2 << 8)), + corrupt_index = (Primary.corrupt.as(i32) | ( 3 << 8)), + readonly_recovery = (Primary.readonly.as(i32) | ( 1 << 8)), + readonly_cantlock = (Primary.readonly.as(i32) | ( 2 << 8)), + readonly_rollback = (Primary.readonly.as(i32) | ( 3 << 8)), + readonly_dbmoved = (Primary.readonly.as(i32) | ( 4 << 8)), + readonly_cantinit = (Primary.readonly.as(i32) | ( 5 << 8)), + readonly_directory = (Primary.readonly.as(i32) | ( 6 << 8)), + abort_rollback = (Primary.abort.as(i32) | ( 2 << 8)), + constraint_check = (Primary.constraint.as(i32) | ( 1 << 8)), + constraint_commithook = (Primary.constraint.as(i32) | ( 2 << 8)), + constraint_foreignkey = (Primary.constraint.as(i32) | ( 3 << 8)), + constraint_function = (Primary.constraint.as(i32) | ( 4 << 8)), + constraint_notnull = (Primary.constraint.as(i32) | ( 5 << 8)), + constraint_primarykey = (Primary.constraint.as(i32) | ( 6 << 8)), + constraint_trigger = (Primary.constraint.as(i32) | ( 7 << 8)), + constraint_unique = (Primary.constraint.as(i32) | ( 8 << 8)), + constraint_vtab = (Primary.constraint.as(i32) | ( 9 << 8)), + constraint_rowid = (Primary.constraint.as(i32) | (10 << 8)), + constraint_pinned = (Primary.constraint.as(i32) | (11 << 8)), + constraint_datatype = (Primary.constraint.as(i32) | (12 << 8)), + notice_recover_wal = (Primary.notice.as(i32) | ( 1 << 8)), + notice_recover_rollback = (Primary.notice.as(i32) | ( 2 << 8)), + notice_rbu = (Primary.notice.as(i32) | ( 3 << 8)), + warning_autoindex = (Primary.warning.as(i32) | ( 1 << 8)), + auth_user = (Primary.auth.as(i32) | ( 1 << 8)), + ok_load_permanently = (Primary.ok.as(i32) | ( 1 << 8)), + }; + + pub fn from(value: anytype) Result { + return @enumFromInt(value); + } + + pub fn primary(self: Result) Primary { + return @enumFromInt(0x000000FF & @intFromEnum(self)); + } + + pub fn extended(self: Result) Extended { + return @enumFromInt(@intFromEnum(self)); } }; test { - const db: Sqlite3Database = try .init(":memory:"); + const db: Database = try .init(.temp_in_memory); _ = db; - - std.debug.print("asdf", .{}); } -- cgit v1.2.3-70-g09d2 From a9b6b2495de6c97fd17b37885dd99d8557a9ea0e Mon Sep 17 00:00:00 2001 From: Alexander Rolley Date: Thu, 21 May 2026 15:30:07 +0200 Subject: - Add associated function on `Result` type that returns the error type, as not every primary error code has an extended error code. - Implement `Database.deinit` and `Database.query` functions. --- src/db/sqlite.zig | 120 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 73 insertions(+), 47 deletions(-) diff --git a/src/db/sqlite.zig b/src/db/sqlite.zig index 1e45537..8b2c42c 100644 --- a/src/db/sqlite.zig +++ b/src/db/sqlite.zig @@ -23,6 +23,20 @@ pub const Database = struct { path: []const u8, }; + pub const Query = struct { + handle: *sqlite.sqlite3_stmt, + + // https://sqlite.org/c3ref/finalize.html + pub fn deinit(self: *Query) void { + const result: Result = .from(sqlite.sqlite3_finalize(self.handle)); + + // TODO: maybe don't panic? + if (!result.isOk()) @panic("Last error was " ++ @errorName(result.asError())); + + self.* = undefined; + } + }; + // Source: https://sqlite.org/c3ref/open.html pub fn init(kind: Kind) Error!Database { const name = switch (kind) { @@ -34,82 +48,68 @@ pub const Database = struct { const result: Result = .from(sqlite.sqlite3_open(name.ptr, &handle)); - switch (result.primary()) { - .ok => {}, - inline else => |code| return @field(anyerror, @tagName(code)), - } + return if (result.isOk()) .{ .handle = handle.? } else result.asError(handle); + } + + // https://sqlite.org/c3ref/close.html + pub fn deinit(self: *Database) void { + const result: Result = .from(sqlite.sqlite3_close(self.handle)); + + // TODO: check if we can loop over the unfinalised objects and commit them + if (!result.isOk()) @panic("There are still unfinalised objects"); - return .{ .handle = handle.? }; + self.* = undefined; + } + + // https://sqlite.org/c3ref/prepare.html + pub fn query(self: *Database, statement: []const u8) Error!Query { + var handle: ?*sqlite.sqlite3_stmt = null; + var unused: [*c]const u8 = null; + + const length: c_int = @truncate(@as(isize, @intCast(statement.len))); + + const result: Result = .from(sqlite.sqlite3_prepare_v2(self.handle, statement.ptr, length, &handle, &unused)); + + return if (result.isOk()) .{ .handle = handle.? } else result.asError(self.handle); } }; // Source: https://sqlite.org/rescode.html -pub const Result = enum(i32) { +const Result = enum(i32) { _, // zig fmt: off - pub const Primary = enum(u8) { - /// Successful result. + const Primary = enum(u8) { ok = 0, - /// Generic error. @"error" = 1, - /// Internal logic error in SQLite. internal = 2, - /// Access permission denied. perm = 3, - /// Callback routine requested an abort. abort = 4, - /// The database file is locked. busy = 5, - /// A table in the database is locked. locked = 6, - /// A malloc() failed. nomem = 7, - /// Attempt to write a readonly database. readonly = 8, - /// Operation terminated by sqlite3_interrupt(). interrupt = 9, - /// Some kind of disk I/O error occurred. ioerr = 10, - /// The database disk image is malformed. corrupt = 11, - /// Unknown opcode in sqlite3_file_control(). notfound = 12, - /// Insertion failed because database is full. full = 13, - /// Unable to open the database file. cantopen = 14, - /// Database lock protocol error. protocol = 15, - /// Internal use only. empty = 16, - /// The database schema changed. schema = 17, - /// String or BLOB exceeds size limit. toobig = 18, - /// Abort due to constraint violation. constraint = 19, - /// Data type mismatch. mismatch = 20, - /// Library used incorrectly. misuse = 21, - /// Uses OS features not supported on host. nolfs = 22, - /// Authorization denied. auth = 23, - /// Not used. format = 24, - /// 2nd parameter to sqlite3_bind out of range. range = 25, - /// File opened that is not a database file. notadb = 26, - /// Notifications from sqlite3_log(). notice = 27, - /// Warnings from sqlite3_log(). warning = 28, - /// sqlite3_step() has another row ready. row = 100, - /// sqlite3_step() has finished executing. done = 101, fn as(self: Primary, comptime T: type) T { @@ -118,7 +118,7 @@ pub const Result = enum(i32) { }; // zig fmt: off - pub const Extended = enum(i32) { + const Extended = enum(i32) { error_missing_collseq = (Primary.@"error".as(i32) | ( 1 << 8)), error_retry = (Primary.@"error".as(i32) | ( 2 << 8)), error_snapshot = (Primary.@"error".as(i32) | ( 3 << 8)), @@ -201,21 +201,47 @@ pub const Result = enum(i32) { ok_load_permanently = (Primary.ok.as(i32) | ( 1 << 8)), }; - pub fn from(value: anytype) Result { + fn from(value: anytype) Result { return @enumFromInt(value); } - pub fn primary(self: Result) Primary { - return @enumFromInt(0x000000FF & @intFromEnum(self)); + fn isOk(self: Result) bool { + return if (self.asPrimary() == .ok) true else false; + } + + fn asError(self: Result, handle: ?*sqlite.sqlite3) Database.Error { + const result: Result = .from(sqlite.sqlite3_extended_errcode(handle)); + const primary = self.asPrimary(); + + if (self == result) switch (primary) { + inline else => |code| return @field(Database.Error, @tagName(code)), + }; + + const extended: Extended = @enumFromInt(@intFromEnum(result)); + + return switch (extended) { + inline else => |code| @field(Database.Error, @tagName(code)), + }; } - pub fn extended(self: Result) Extended { - return @enumFromInt(@intFromEnum(self)); + fn asPrimary(self: Result) Primary { + return @enumFromInt(0x000000FF & @intFromEnum(self)); } }; test { - const db: Database = try .init(.temp_in_memory); - - _ = db; + var db: Database = try .init(.temp_in_memory); + // defer db.deinit(); + + const query = db.query( + \\ CREATE TABLE contacts ( + \\ contact_id INTEGER PRIMARY KEY, + \\ first_name TEXT NOT NULL, + \\ last_name TEXT NOT NULL, + \\ email TEXT NOT NULL UNIQUE, + \\ phone TEXT NOT NULL UNIQUE + \\ ); + ); + + std.debug.print("{any}\n", .{ query }); } -- cgit v1.2.3-70-g09d2