Narrowing down error sets
This commit is contained in:
parent
ab96b3b734
commit
91c116a303
3 changed files with 131 additions and 48 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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, "?");
|
||||
|
|
159
src/sql/lib.zig
159
src/sql/lib.zig
|
@ -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)];
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue