aboutsummaryrefslogtreecommitdiff
path: root/src/db/sqlite.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/db/sqlite.zig')
-rw-r--r--src/db/sqlite.zig69
1 files changed, 45 insertions, 24 deletions
diff --git a/src/db/sqlite.zig b/src/db/sqlite.zig
index 8b2c42c..f2fae69 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,37 +49,48 @@ 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;
- const length: c_int = @truncate(@as(isize, @intCast(statement.len)));
+ const result: Result = .from(sqlite.sqlite3_prepare_v2(self.handle, statement.ptr, @intCast(statement.len), &handle, &unused));
- 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 +124,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 +213,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
- fn from(value: anytype) Result {
- return @enumFromInt(value);
+ /// 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);
+
+ 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));
}