From 81df6d846144bd1e8d699e4d1f3642a9ea7a10e9 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Thu, 6 Jul 2023 01:29:13 -0700 Subject: [PATCH] Add open/close methods --- src/sql/engines/sqlite.zig | 99 +++++++++++++++++++++++++++++++++++--- src/sql/lib.zig | 8 ++- 2 files changed, 95 insertions(+), 12 deletions(-) diff --git a/src/sql/engines/sqlite.zig b/src/sql/engines/sqlite.zig index a01bdb2..c79e3e7 100644 --- a/src/sql/engines/sqlite.zig +++ b/src/sql/engines/sqlite.zig @@ -1,7 +1,7 @@ const common = @import("../lib.zig"); const std = @import("std"); const c = @cImport({ - //@cInclude("sqlite3.h"); + @cInclude("sqlite3.h"); }); const QueryOptions = common.QueryOptions; @@ -12,22 +12,71 @@ const Results = common.Results; const Allocator = std.mem.Allocator; pub const Engine = struct { - //db: *c.sqlite3, - dummy: usize = 0, + conn: *c.sqlite3, + + pub fn open(path: [:0]const u8) !Engine { + return openInternal(path, false); + } + + pub fn openUri(path: [:0]const u8) !Engine { + return openInternal(path, true); + } + + fn openInternal(path: [:0]const u8, is_uri: bool) !Engine { + const flags = c.SQLITE_OPEN_READWRITE | c.SQLITE_OPEN_CREATE | c.SQLITE_OPEN_EXRESCODE | if (is_uri) c.SQLITE_OPEN_URI else 0; + + var conn: ?*c.sqlite3 = null; + switch (c.sqlite3_open_v2(@ptrCast([*c]const u8, path), &conn, flags, null)) { + c.SQLITE_OK => {}, + else => |code| { + if (conn == null) { + // this path should only be hit if out of memory, but log it anyways + std.log.err( + "Unable to open SQLite DB \"{s}\". Error: {?s} ({})", + .{ path, c.sqlite3_errstr(code), code }, + ); + return error.BadConnection; + } + + const ext_code = c.sqlite3_extended_errcode(conn); + std.log.err( + \\Unable to open SQLite DB "{s}". Error: {?s} ({}) + \\Details: {?s} + , + .{ path, c.sqlite3_errstr(ext_code), ext_code, c.sqlite3_errmsg(conn) }, + ); + + return error.Unexpected; + }, + } + + return Engine{ + .conn = conn.?, + }; + } pub fn db(self: *Engine) common.Db { return .{ .ptr = self, .vtable = &.{ - .close = Engine.close, .exec = Engine.exec, }, }; } - fn close(ctx: *anyopaque) void { - const self: *Engine = @ptrCast(*Engine, @alignCast(@alignOf(Engine), ctx)); - _ = self; + pub fn close(self: *Engine) void { + switch (c.sqlite3_close(self.conn)) { + c.SQLITE_OK => {}, + + c.SQLITE_BUSY => { + std.log.err("SQLite DB could not be closed as it is busy.\n{s}", .{c.sqlite3_errmsg(self.conn)}); + }, + + else => |err| { + std.log.err("Could not close SQLite DB", .{}); + handleUnexpectedError(self.conn, err, null) catch {}; + }, + } } fn exec( @@ -42,3 +91,39 @@ pub const Engine = struct { @panic("unimplemented"); } }; + +fn getCharPos(text: []const u8, offset: c_int) struct { row: usize, col: usize } { + var row: usize = 0; + var col: usize = 0; + var i: usize = 0; + + if (offset > text.len) return .{ .row = 0, .col = 0 }; + + while (i != offset) : (i += 1) { + if (text[i] == '\n') { + row += 1; + col = 0; + } else { + col += 1; + } + } + + return .{ .row = row, .col = col }; +} + +fn handleUnexpectedError(db: *c.sqlite3, code: c_int, sql_text: ?[]const u8) error{Unexpected} { + std.log.err("Unexpected error in SQLite engine: {s} ({})", .{ c.sqlite3_errstr(code), code }); + + std.log.debug("Additional details:", .{}); + std.log.debug("{?s}", .{c.sqlite3_errmsg(db)}); + if (sql_text) |sql| { + const byte_offset = c.sqlite3_error_offset(db); + if (byte_offset >= 0) { + const pos = getCharPos(sql, byte_offset); + std.log.debug("Failed at char ({}:{}) of SQL:\n{s}", .{ pos.row, pos.col, sql }); + } + } + std.log.debug("{?}", .{@errorReturnTrace()}); + + return error.Unexpected; +} diff --git a/src/sql/lib.zig b/src/sql/lib.zig index 6a3bccf..fef8a69 100644 --- a/src/sql/lib.zig +++ b/src/sql/lib.zig @@ -48,9 +48,6 @@ pub const ExecError = error{ pub const Db = struct { pub const VTable = struct { - /// Closes the database connection - close: *const fn (ctx: *anyopaque) void, - /// Executes a SQL query. /// All memory allocated with this allocator must be freed before this function returns. exec: *const fn (ctx: *anyopaque, sql: []const u8, args: []const SqlValue, opt: QueryOptions, allocator: Allocator) ExecError!Results, @@ -104,8 +101,9 @@ pub const Row = struct { }; test "test" { - const backend = @import("./engines/sqlite2.zig"); - var engine: backend.Engine = undefined; + const backend = @import("./engines/sqlite.zig"); + var engine = try backend.Engine.open(":memory:"); + defer engine.close(); const db = engine.db();