Narrowing down error sets

This commit is contained in:
jaina heartles 2022-09-07 18:55:55 -07:00
parent ab96b3b734
commit 91c116a303
3 changed files with 131 additions and 48 deletions

View file

@ -3,6 +3,8 @@ const builtin = @import("builtin");
const util = @import("util");
const models = @import("../db/models.zig");
const DbError = @import("../db.zig").ExecError;
const getRandom = @import("../api.zig").getRandom;
const Uuid = util.Uuid;
@ -11,8 +13,7 @@ const CreateError = error{
InvalidOrigin,
UnsupportedScheme,
CommunityExists,
DbError,
};
} || DbError;
pub const Scheme = enum {
https,
@ -65,11 +66,11 @@ pub fn create(db: anytype, origin: []const u8, name: ?[]const u8) CreateError!Co
.scheme = scheme,
};
if ((db.execRow2(&.{Uuid}, "SELECT id FROM community WHERE host = ?", .{host}, null) catch return error.DbError) != null) {
if ((try db.execRow2(&.{Uuid}, "SELECT id FROM community WHERE host = ?", .{host}, null)) != null) {
return error.CommunityExists;
}
db.insert2("community", community) catch return error.DbError;
try db.insert2("community", community);
return community;
}

View file

@ -35,11 +35,10 @@ fn readRow(comptime RowTuple: type, row: sql.Row, allocator: ?std.mem.Allocator)
pub fn ResultSet(comptime result_types: []const type) type {
return struct {
pub const QueryError = anyerror;
pub const Row = std.meta.Tuple(result_types);
_stmt: sql.PreparedStmt,
err: ?QueryError = null,
err: ?ExecError = null,
pub fn finish(self: *@This()) void {
self._stmt.finalize();
@ -217,6 +216,8 @@ fn getEnum(row: sql.Row, comptime T: type, idx: u15, alloc: std.mem.Allocator) !
return error.UnknownTag;
}
pub const ExecError = sql.PrepareError || sql.RowGetError || sql.BindError || std.mem.Allocator.Error || error{AllocatorRequired};
pub const Database = struct {
db: sql.Sqlite,
@ -238,7 +239,7 @@ pub const Database = struct {
comptime result_types: []const type,
comptime q: []const u8,
args: anytype,
) !ResultSet(result_types) {
) ExecError!ResultSet(result_types) {
std.log.debug("executing sql:\n===\n{s}\n===", .{q});
const stmt = try self.db.prepare(q);
@ -259,7 +260,7 @@ pub const Database = struct {
comptime q: []const u8,
args: anytype,
allocator: ?std.mem.Allocator,
) !?ResultSet(result_types).Row {
) ExecError!?ResultSet(result_types).Row {
var results = try self.exec2(result_types, q, args);
defer results.finish();
@ -290,7 +291,7 @@ pub const Database = struct {
self: *Database,
comptime table: []const u8,
value: anytype,
) !void {
) ExecError!void {
const ValueType = comptime @TypeOf(value);
const table_spec = comptime table ++ build_field_list(ValueType, null);
const value_spec = comptime build_field_list(ValueType, "?");

View file

@ -7,31 +7,104 @@ const c = @cImport({
const Uuid = util.Uuid;
const DateTime = util.DateTime;
const UnexpectedError = error{Unexpected};
pub const OpenError = error{
DbCorrupt,
BadPathName,
IsDir,
InputOutput,
NoSpaceLeft,
} || UnexpectedError;
pub const PrepareError = UnexpectedError;
pub const RowGetError = error{ InvalidData, StreamTooLong, OutOfMemory } || UnexpectedError;
pub const BindError = UnexpectedError;
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) UnexpectedError {
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("{?s}", .{@errorReturnTrace()});
return error.Unexpected;
}
pub const Sqlite = struct {
db: *c.sqlite3,
pub fn open(path: [:0]const u8) !Sqlite {
pub fn open(path: [:0]const u8) OpenError!Sqlite {
const flags = c.SQLITE_OPEN_READWRITE | c.SQLITE_OPEN_CREATE | c.SQLITE_OPEN_EXRESCODE;
var db: ?*c.sqlite3 = undefined;
const err = c.sqlite3_open_v2(@ptrCast([*c]const u8, path), &db, c.SQLITE_OPEN_READWRITE | c.SQLITE_OPEN_CREATE, null);
if (err != c.SQLITE_OK) return error.UnknownError;
switch (c.sqlite3_open_v2(@ptrCast([*c]const u8, path), &db, flags, null)) {
c.SQLITE_OK => {},
c.SQLITE_NOTADB, c.SQLITE_CORRUPT => return error.DbCorrupt,
c.SQLITE_IOERR_WRITE, c.SQLITE_IOERR_READ => return error.InputOutput,
c.SQLITE_CANTOPEN_ISDIR => return error.IsDir,
c.SQLITE_CANTOPEN_FULLPATH => return error.BadPathName,
c.SQLITE_FULL => return error.NoSpaceLeft,
else => |err| {
std.log.err(
\\Unable to open SQLite DB "{s}"
\\Error: {s} ({})
, .{ path, c.sqlite3_errstr(err), err });
return error.Unexpected;
},
}
return Sqlite{
.db = db.?,
};
}
pub fn close(self: *Sqlite) void {
_ = c.sqlite3_close_v2(self.db);
pub fn close(self: Sqlite) void {
switch (c.sqlite3_close(self.db)) {
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.db)});
},
else => |err| {
std.log.err("Could not close SQLite DB", .{});
handleUnexpectedError(self.db, err, null) catch {};
},
}
}
pub fn prepare(self: *Sqlite, sql: []const u8) !PreparedStmt {
pub fn prepare(self: *Sqlite, sql: []const u8) PrepareError!PreparedStmt {
var stmt: ?*c.sqlite3_stmt = undefined;
const err = c.sqlite3_prepare_v2(self.db, sql.ptr, @intCast(c_int, sql.len), &stmt, null);
if (err != c.SQLITE_OK) {
std.log.debug("sql error {}: {s}", .{ err, c.sqlite3_errmsg(self.db) });
std.log.debug("Failed on SQL:\n==========\n{s}\n==========", .{sql});
return error.UnknownError;
}
if (err != c.SQLITE_OK) return handleUnexpectedError(self.db, err, sql);
return PreparedStmt{ .stmt = stmt.?, .db = self.db };
}
@ -41,15 +114,15 @@ pub const Row = struct {
stmt: *c.sqlite3_stmt,
db: *c.sqlite3,
pub fn isNull(self: Row, idx: u15) bool {
pub fn isNull(self: Row, idx: u15) RowGetError!bool {
return c.sqlite3_column_type(self.stmt, idx) == c.SQLITE_NULL;
}
pub fn getI64(self: Row, idx: u15) !i64 {
pub fn getI64(self: Row, idx: u15) RowGetError!i64 {
return @intCast(i64, c.sqlite3_column_int64(self.stmt, idx));
}
pub fn getText(self: Row, idx: u15, buf: []u8) ![]u8 {
pub fn getText(self: Row, idx: u15, buf: []u8) RowGetError![]u8 {
const ptr = c.sqlite3_column_text(self.stmt, idx);
const size = @intCast(usize, c.sqlite3_column_bytes(self.stmt, idx));
@ -59,7 +132,7 @@ pub const Row = struct {
return buf[0..size];
}
pub fn getTextAlloc(self: Row, idx: u15, alloc: std.mem.Allocator) ![]u8 {
pub fn getTextAlloc(self: Row, idx: u15, alloc: std.mem.Allocator) RowGetError![]u8 {
const size = c.sqlite3_column_bytes(self.stmt, idx);
var buf = try alloc.alloc(u8, @intCast(usize, size));
errdefer alloc.free(buf);
@ -67,7 +140,7 @@ pub const Row = struct {
return self.getText(idx, buf);
}
pub fn getBlob(self: Row, idx: u15, buf: []u8) ![]u8 {
pub fn getBlob(self: Row, idx: u15, buf: []u8) RowGetError![]u8 {
const ptr = @ptrCast([*]const u8, c.sqlite3_column_blob(self.stmt, idx));
const size = @intCast(usize, c.sqlite3_column_bytes(self.stmt, idx));
@ -77,7 +150,7 @@ pub const Row = struct {
return buf[0..size];
}
pub fn getBlobAlloc(self: Row, idx: u15, alloc: std.mem.Allocator) ![]u8 {
pub fn getBlobAlloc(self: Row, idx: u15, alloc: std.mem.Allocator) RowGetError![]u8 {
const size = c.sqlite3_column_bytes(self.stmt, idx);
var buf = try alloc.alloc(u8, @intCast(usize, size));
errdefer alloc.free(buf);
@ -85,13 +158,13 @@ pub const Row = struct {
return self.getBlob(idx, buf);
}
pub fn getUuid(self: Row, idx: u15) !Uuid {
pub fn getUuid(self: Row, idx: u15) RowGetError!Uuid {
var buf: [Uuid.string_len + 1]u8 = undefined;
_ = try self.getText(idx, &buf);
return try Uuid.parse(buf[0..Uuid.string_len]);
return Uuid.parse(buf[0..Uuid.string_len]) catch return error.InvalidData;
}
pub fn getDateTime(self: Row, idx: u15) !DateTime {
pub fn getDateTime(self: Row, idx: u15) RowGetError!DateTime {
return DateTime{ .seconds_since_epoch = try self.getI64(idx) };
}
};
@ -100,67 +173,75 @@ pub const PreparedStmt = struct {
stmt: *c.sqlite3_stmt,
db: *c.sqlite3,
pub fn bindNull(self: PreparedStmt, idx: u15) !void {
pub fn bindNull(self: PreparedStmt, idx: u15) BindError!void {
return switch (c.sqlite3_bind_null(self.stmt, idx)) {
c.SQLITE_OK => {},
else => error.UnknownError,
else => |err| handleUnexpectedError(self.db, err, null),
};
}
pub fn bindUuid(self: PreparedStmt, idx: u15, id: Uuid) !void {
pub fn bindUuid(self: PreparedStmt, idx: u15, id: Uuid) BindError!void {
const str = id.toCharArray();
return self.bindText(idx, &str);
}
pub fn bindText(self: PreparedStmt, idx: u15, str: []const u8) !void {
pub fn bindText(self: PreparedStmt, idx: u15, str: []const u8) BindError!void {
// Work around potential null pointer in empty string
const eff_str = if (str.len == 0) (" ")[0..0] else str;
return switch (c.sqlite3_bind_text(self.stmt, idx, eff_str.ptr, @intCast(c_int, eff_str.len), c.SQLITE_TRANSIENT)) {
c.SQLITE_OK => {},
else => error.UnknownError,
else => |err| handleUnexpectedError(self.db, err, null),
};
}
pub fn bindBlob(self: PreparedStmt, idx: u15, blob: []const u8) !void {
pub fn bindBlob(self: PreparedStmt, idx: u15, blob: []const u8) BindError!void {
return switch (c.sqlite3_bind_blob64(self.stmt, idx, blob.ptr, blob.len, c.SQLITE_TRANSIENT)) {
c.SQLITE_OK => {},
else => error.UnknownError,
else => |err| handleUnexpectedError(self.db, err, null),
};
}
pub fn bindI64(self: PreparedStmt, idx: u15, val: i64) !void {
pub fn bindI64(self: PreparedStmt, idx: u15, val: i64) BindError!void {
return switch (c.sqlite3_bind_int64(self.stmt, idx, val)) {
c.SQLITE_OK => {},
else => error.UnknownError,
else => |err| handleUnexpectedError(self.db, err, null),
};
}
pub fn bindDateTime(self: PreparedStmt, idx: u15, val: DateTime) !void {
pub fn bindDateTime(self: PreparedStmt, idx: u15, val: DateTime) BindError!void {
return self.bindI64(idx, val.seconds_since_epoch);
}
pub const StepError = UnexpectedError;
pub fn step(self: PreparedStmt) !?Row {
return switch (c.sqlite3_step(self.stmt)) {
c.SQLITE_ROW => Row{ .stmt = self.stmt, .db = self.db },
c.SQLITE_DONE => null,
else => |err| blk: {
std.log.debug("sql error {}: {s}", .{ err, c.sqlite3_errmsg(self.db) });
std.log.debug("Failed on SQL:\n==========\n{?s}\n==========", .{self.getGeneratingSql()});
break :blk error.UnknownError;
},
else => |err| handleUnexpectedError(self.db, err, self.getGeneratingSql()),
};
}
pub fn finalize(self: PreparedStmt) void {
_ = c.sqlite3_finalize(self.stmt);
switch (c.sqlite3_finalize(self.stmt)) {
c.SQLITE_OK => {},
else => |err| {
handleUnexpectedError(self.db, err, self.getGeneratingSql()) catch {};
},
}
}
pub fn reset(self: PreparedStmt) void {
_ = c.sqlite3_reset(self.stmt);
switch (c.sqlite3_reset(self.stmt)) {
c.SQLITE_OK => {},
else => |err| {
handleUnexpectedError(self.db, err, self.getGeneratingSql()) catch {};
},
}
}
fn getGeneratingSql(self: PreparedStmt) ?[*:0]const u8 {
return c.sqlite3_sql(self.stmt);
fn getGeneratingSql(self: PreparedStmt) ?[]const u8 {
const ptr = c.sqlite3_sql(self.stmt) orelse return null;
return ptr[0..std.mem.len(ptr)];
}
};