diff options
| -rw-r--r-- | src/db/sqlite.zig | 65 |
1 files changed, 44 insertions, 21 deletions
diff --git a/src/db/sqlite.zig b/src/db/sqlite.zig index 8b2c42c..87282ac 100644 --- a/src/db/sqlite.zig +++ b/src/db/sqlite.zig @@ -1,5 +1,6 @@ const std = @import("std"); const sqlite = @import("sqlite"); +const assert = std.debug.assert; pub const Database = struct { handle: *sqlite.sqlite3, @@ -26,12 +27,12 @@ pub const Database = struct { pub const Query = struct { handle: *sqlite.sqlite3_stmt, - // https://sqlite.org/c3ref/finalize.html + // Source: 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())); + if (!result.isOk()) @panic("Last error was " ++ @errorName(result.toError())); self.* = undefined; } @@ -48,20 +49,26 @@ pub const Database = struct { const result: Result = .from(sqlite.sqlite3_open(name.ptr, &handle)); - return if (result.isOk()) .{ .handle = handle.? } else result.asError(handle); + return if (result.isOk()) .{ .handle = handle.? } else result.toError(handle); } - // https://sqlite.org/c3ref/close.html + /// Closes the database and releases all of its resources. + /// + /// # Panics + /// + /// Panics if there exist unfinalised prepared statements, BLOB handlers, + /// and/or unfinished backup objects. + /// + /// See 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"); + if (!result.isOk()) @panic("There are still unfinalised and/or unfinished objects"); self.* = undefined; } - // https://sqlite.org/c3ref/prepare.html + // Source: 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; @@ -70,15 +77,22 @@ pub const Database = struct { 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); + return if (result.isOk()) .{ .handle = handle.? } else result.toError(self.handle); } }; -// Source: https://sqlite.org/rescode.html +/// SQLite result codes. +/// +/// Primary result codes define a broad category of results, whereas extended +/// result codes provide more detail. The primary result code is always part of +/// an extended result code. +/// +/// See https://sqlite.org/rescode.html const Result = enum(i32) { _, // zig fmt: off + /// Primary result codes. const Primary = enum(u8) { ok = 0, @"error" = 1, @@ -112,12 +126,13 @@ const Result = enum(i32) { row = 100, done = 101, + /// Returns the primary result code as type `T`. fn as(self: Primary, comptime T: type) T { return @as(T, @intFromEnum(self)); } }; - // zig fmt: off + /// Extended result codes. const Extended = enum(i32) { error_missing_collseq = (Primary.@"error".as(i32) | ( 1 << 8)), error_retry = (Primary.@"error".as(i32) | ( 2 << 8)), @@ -200,30 +215,38 @@ const Result = enum(i32) { auth_user = (Primary.auth.as(i32) | ( 1 << 8)), ok_load_permanently = (Primary.ok.as(i32) | ( 1 << 8)), }; + // zig fmt: on + + /// Returns the result code corresponding to the given integer value. + fn from(integer: anytype) Result { + assert(std.enums.fromInt(Primary, integer) != null or std.enums.fromInt(Extended, integer) != null); - fn from(value: anytype) Result { - return @enumFromInt(value); + return @enumFromInt(integer); } + /// Returns `true` if the primary result code is `.ok`. 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) { + /// Returns the error name corresponding to the result code. The `handle` is + /// needed to fetch the extended result code, if it exists, only if `self` + /// is not already an extended result code. + fn toError(self: Result, handle: ?*sqlite.sqlite3) Database.Error { + if (std.enums.fromInt(Extended, @intFromEnum(self))) |extended| switch (extended) { inline else => |code| return @field(Database.Error, @tagName(code)), }; - const extended: Extended = @enumFromInt(@intFromEnum(result)); + const value: Result = .from(sqlite.sqlite3_extended_errcode(handle)); - return switch (extended) { - inline else => |code| @field(Database.Error, @tagName(code)), - }; + if (std.enums.fromInt(Extended, @intFromEnum(value))) |extended| switch (extended) { + inline else => |code| return @field(Database.Error, @tagName(code)), + } else switch (self.asPrimary()) { + inline else => |code| return @field(Database.Error, @tagName(code)), + } } + /// Returns the primary result code. fn asPrimary(self: Result) Primary { return @enumFromInt(0x000000FF & @intFromEnum(self)); } |