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 util = @import("util");
const models = @import("../db/models.zig"); const models = @import("../db/models.zig");
const DbError = @import("../db.zig").ExecError;
const getRandom = @import("../api.zig").getRandom; const getRandom = @import("../api.zig").getRandom;
const Uuid = util.Uuid; const Uuid = util.Uuid;
@ -11,8 +13,7 @@ const CreateError = error{
InvalidOrigin, InvalidOrigin,
UnsupportedScheme, UnsupportedScheme,
CommunityExists, CommunityExists,
DbError, } || DbError;
};
pub const Scheme = enum { pub const Scheme = enum {
https, https,
@ -65,11 +66,11 @@ pub fn create(db: anytype, origin: []const u8, name: ?[]const u8) CreateError!Co
.scheme = scheme, .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; return error.CommunityExists;
} }
db.insert2("community", community) catch return error.DbError; try db.insert2("community", community);
return 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 { pub fn ResultSet(comptime result_types: []const type) type {
return struct { return struct {
pub const QueryError = anyerror;
pub const Row = std.meta.Tuple(result_types); pub const Row = std.meta.Tuple(result_types);
_stmt: sql.PreparedStmt, _stmt: sql.PreparedStmt,
err: ?QueryError = null, err: ?ExecError = null,
pub fn finish(self: *@This()) void { pub fn finish(self: *@This()) void {
self._stmt.finalize(); self._stmt.finalize();
@ -217,6 +216,8 @@ fn getEnum(row: sql.Row, comptime T: type, idx: u15, alloc: std.mem.Allocator) !
return error.UnknownTag; return error.UnknownTag;
} }
pub const ExecError = sql.PrepareError || sql.RowGetError || sql.BindError || std.mem.Allocator.Error || error{AllocatorRequired};
pub const Database = struct { pub const Database = struct {
db: sql.Sqlite, db: sql.Sqlite,
@ -238,7 +239,7 @@ pub const Database = struct {
comptime result_types: []const type, comptime result_types: []const type,
comptime q: []const u8, comptime q: []const u8,
args: anytype, args: anytype,
) !ResultSet(result_types) { ) ExecError!ResultSet(result_types) {
std.log.debug("executing sql:\n===\n{s}\n===", .{q}); std.log.debug("executing sql:\n===\n{s}\n===", .{q});
const stmt = try self.db.prepare(q); const stmt = try self.db.prepare(q);
@ -259,7 +260,7 @@ pub const Database = struct {
comptime q: []const u8, comptime q: []const u8,
args: anytype, args: anytype,
allocator: ?std.mem.Allocator, allocator: ?std.mem.Allocator,
) !?ResultSet(result_types).Row { ) ExecError!?ResultSet(result_types).Row {
var results = try self.exec2(result_types, q, args); var results = try self.exec2(result_types, q, args);
defer results.finish(); defer results.finish();
@ -290,7 +291,7 @@ pub const Database = struct {
self: *Database, self: *Database,
comptime table: []const u8, comptime table: []const u8,
value: anytype, value: anytype,
) !void { ) ExecError!void {
const ValueType = comptime @TypeOf(value); const ValueType = comptime @TypeOf(value);
const table_spec = comptime table ++ build_field_list(ValueType, null); const table_spec = comptime table ++ build_field_list(ValueType, null);
const value_spec = comptime build_field_list(ValueType, "?"); const value_spec = comptime build_field_list(ValueType, "?");

View file

@ -7,31 +7,104 @@ const c = @cImport({
const Uuid = util.Uuid; const Uuid = util.Uuid;
const DateTime = util.DateTime; 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 { pub const Sqlite = struct {
db: *c.sqlite3, 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; 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); switch (c.sqlite3_open_v2(@ptrCast([*c]const u8, path), &db, flags, null)) {
if (err != c.SQLITE_OK) return error.UnknownError; 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{ return Sqlite{
.db = db.?, .db = db.?,
}; };
} }
pub fn close(self: *Sqlite) void { pub fn close(self: Sqlite) void {
_ = c.sqlite3_close_v2(self.db); 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; var stmt: ?*c.sqlite3_stmt = undefined;
const err = c.sqlite3_prepare_v2(self.db, sql.ptr, @intCast(c_int, sql.len), &stmt, null); const err = c.sqlite3_prepare_v2(self.db, sql.ptr, @intCast(c_int, sql.len), &stmt, null);
if (err != c.SQLITE_OK) { if (err != c.SQLITE_OK) return handleUnexpectedError(self.db, err, sql);
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;
}
return PreparedStmt{ .stmt = stmt.?, .db = self.db }; return PreparedStmt{ .stmt = stmt.?, .db = self.db };
} }
@ -41,15 +114,15 @@ pub const Row = struct {
stmt: *c.sqlite3_stmt, stmt: *c.sqlite3_stmt,
db: *c.sqlite3, 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; 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)); 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 ptr = c.sqlite3_column_text(self.stmt, idx);
const size = @intCast(usize, c.sqlite3_column_bytes(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]; 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); const size = c.sqlite3_column_bytes(self.stmt, idx);
var buf = try alloc.alloc(u8, @intCast(usize, size)); var buf = try alloc.alloc(u8, @intCast(usize, size));
errdefer alloc.free(buf); errdefer alloc.free(buf);
@ -67,7 +140,7 @@ pub const Row = struct {
return self.getText(idx, buf); 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 ptr = @ptrCast([*]const u8, c.sqlite3_column_blob(self.stmt, idx));
const size = @intCast(usize, c.sqlite3_column_bytes(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]; 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); const size = c.sqlite3_column_bytes(self.stmt, idx);
var buf = try alloc.alloc(u8, @intCast(usize, size)); var buf = try alloc.alloc(u8, @intCast(usize, size));
errdefer alloc.free(buf); errdefer alloc.free(buf);
@ -85,13 +158,13 @@ pub const Row = struct {
return self.getBlob(idx, buf); 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; var buf: [Uuid.string_len + 1]u8 = undefined;
_ = try self.getText(idx, &buf); _ = 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) }; return DateTime{ .seconds_since_epoch = try self.getI64(idx) };
} }
}; };
@ -100,67 +173,75 @@ pub const PreparedStmt = struct {
stmt: *c.sqlite3_stmt, stmt: *c.sqlite3_stmt,
db: *c.sqlite3, 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)) { return switch (c.sqlite3_bind_null(self.stmt, idx)) {
c.SQLITE_OK => {}, 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(); const str = id.toCharArray();
return self.bindText(idx, &str); 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 // Work around potential null pointer in empty string
const eff_str = if (str.len == 0) (" ")[0..0] else str; 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)) { return switch (c.sqlite3_bind_text(self.stmt, idx, eff_str.ptr, @intCast(c_int, eff_str.len), c.SQLITE_TRANSIENT)) {
c.SQLITE_OK => {}, 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)) { return switch (c.sqlite3_bind_blob64(self.stmt, idx, blob.ptr, blob.len, c.SQLITE_TRANSIENT)) {
c.SQLITE_OK => {}, 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)) { return switch (c.sqlite3_bind_int64(self.stmt, idx, val)) {
c.SQLITE_OK => {}, 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); return self.bindI64(idx, val.seconds_since_epoch);
} }
pub const StepError = UnexpectedError;
pub fn step(self: PreparedStmt) !?Row { pub fn step(self: PreparedStmt) !?Row {
return switch (c.sqlite3_step(self.stmt)) { return switch (c.sqlite3_step(self.stmt)) {
c.SQLITE_ROW => Row{ .stmt = self.stmt, .db = self.db }, c.SQLITE_ROW => Row{ .stmt = self.stmt, .db = self.db },
c.SQLITE_DONE => null, c.SQLITE_DONE => null,
else => |err| blk: { else => |err| handleUnexpectedError(self.db, err, self.getGeneratingSql()),
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;
},
}; };
} }
pub fn finalize(self: PreparedStmt) void { 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 { 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 { fn getGeneratingSql(self: PreparedStmt) ?[]const u8 {
return c.sqlite3_sql(self.stmt); const ptr = c.sqlite3_sql(self.stmt) orelse return null;
return ptr[0..std.mem.len(ptr)];
} }
}; };