Use type introspection in db wrapper

This commit is contained in:
jaina heartles 2022-07-15 21:33:01 -07:00
parent b82600e8cd
commit c9c5958849
1 changed files with 90 additions and 6 deletions

View File

@ -3,6 +3,70 @@ const sql = @import("sql");
const models = @import("./models.zig");
const Uuid = @import("util").Uuid;
const String = []const u8;
fn tableName(comptime T: type) String {
return switch (T) {
models.Note => "note",
models.User => "user",
else => unreachable,
};
}
fn join(comptime vals: anytype, comptime joiner: String) String {
comptime {
if (vals.len == 0) return "";
var result: String = "";
for (vals) |v| {
result = std.fmt.comptimePrint("{s}{s}{s}", .{ result, joiner, v });
}
return result[2..];
}
}
const Query = struct {
select: []const String,
from: String,
where: String = "id = ?",
limit: usize = 1,
pub fn str(comptime self: Query) String {
comptime {
return std.fmt.comptimePrint(
"SELECT {s} FROM {s} WHERE {s} LIMIT {};",
.{ join(self.select, ", "), self.from, self.where, self.limit },
);
}
}
};
fn filterOut(comptime vals: []const String, comptime to_ignore: []const String) []const String {
comptime {
var result: [vals.len]String = undefined;
var count = 0;
for (vals) |v| {
const keep = for (to_ignore) |x| {
if (std.mem.eql(u8, x, v)) break false;
} else true;
if (keep) {
result[count] = v;
count += 1;
}
}
return result[0..count];
}
}
fn fieldsExcept(comptime T: type, comptime to_ignore: []const String) []const String {
comptime {
return filterOut(std.meta.fieldNames(T), to_ignore);
}
}
pub const Database = struct {
db: sql.Sqlite,
@ -30,18 +94,38 @@ pub const Database = struct {
self.db.close();
}
pub fn getNoteById(self: *Database, id: Uuid, alloc: std.mem.Allocator) !?models.Note {
var stmt = try self.db.prepare("SELECT content FROM note WHERE id = ? LIMIT 1;");
pub fn getById(self: *Database, comptime T: type, id: Uuid, alloc: std.mem.Allocator) !?T {
const fields = comptime fieldsExcept(T, &.{"id"});
const q = comptime (Query{
.select = fields,
.from = tableName(T),
.where = "id = ?",
.limit = 1,
}).str();
var stmt = try self.db.prepare(q);
defer stmt.finalize();
const id_str = id.toCharArray();
try stmt.bindText(1, &id_str);
const row = (try stmt.step()) orelse return null;
return models.Note{
.id = id,
.content = try row.getTextAlloc(0, alloc),
};
var val: T = undefined;
val.id = id;
inline for (fields) |f, i| {
@field(val, f) = switch (@TypeOf(@field(val, f))) {
// TODO: Handle allocation failures gracefully
[]const u8 => row.getTextAlloc(i, alloc) catch unreachable,
else => @compileError("unknown type"),
};
}
return val;
}
pub fn getNoteById(self: *Database, id: Uuid, alloc: std.mem.Allocator) !?models.Note {
return self.getById(models.Note, id, alloc);
}
pub fn insertNote(self: *Database, note: models.Note) !void {