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.zig247
1 files changed, 247 insertions, 0 deletions
diff --git a/src/db/sqlite.zig b/src/db/sqlite.zig
new file mode 100644
index 0000000..8b2c42c
--- /dev/null
+++ b/src/db/sqlite.zig
@@ -0,0 +1,247 @@
+const std = @import("std");
+const sqlite = @import("sqlite");
+
+pub const Database = struct {
+ handle: *sqlite.sqlite3,
+
+ pub const Error = set: {
+ var set: type = error{};
+
+ 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));
+ }
+
+ break :set set;
+ };
+
+ pub const Kind = union(enum) {
+ temp_in_memory,
+ temp_on_disk,
+ 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) {
+ .temp_in_memory => ":memory:",
+ .temp_on_disk => "",
+ .path => |path| path,
+ };
+ var handle: ?*sqlite.sqlite3 = null;
+
+ const result: Result = .from(sqlite.sqlite3_open(name.ptr, &handle));
+
+ 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");
+
+ 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
+const Result = enum(i32) {
+ _,
+
+ // zig fmt: off
+ const Primary = enum(u8) {
+ ok = 0,
+ @"error" = 1,
+ internal = 2,
+ perm = 3,
+ abort = 4,
+ busy = 5,
+ locked = 6,
+ nomem = 7,
+ readonly = 8,
+ interrupt = 9,
+ ioerr = 10,
+ corrupt = 11,
+ notfound = 12,
+ full = 13,
+ cantopen = 14,
+ protocol = 15,
+ empty = 16,
+ schema = 17,
+ toobig = 18,
+ constraint = 19,
+ mismatch = 20,
+ misuse = 21,
+ nolfs = 22,
+ auth = 23,
+ format = 24,
+ range = 25,
+ notadb = 26,
+ notice = 27,
+ warning = 28,
+ row = 100,
+ done = 101,
+
+ fn as(self: Primary, comptime T: type) T {
+ return @as(T, @intFromEnum(self));
+ }
+ };
+
+ // zig fmt: off
+ 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)),
+ };
+
+ fn from(value: anytype) Result {
+ return @enumFromInt(value);
+ }
+
+ 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)),
+ };
+ }
+
+ fn asPrimary(self: Result) Primary {
+ return @enumFromInt(0x000000FF & @intFromEnum(self));
+ }
+};
+
+test {
+ 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 });
+}