mgtzm/src/Db.zig

134 lines
3.3 KiB
Zig

const std = @import("std");
const sqlite = @import("sqlite");
const ObjectError = error{
IncompatibleObjectType, // No ?u32 "id" field
};
pub fn persist(db: *sqlite.Db, obj: anytype) !void {
const T = @TypeOf(obj);
// Check that object is a valid type for the DB system.
// It should have an "id" field and be of type "?u32"
if (!@hasField(T, "id") or @TypeOf(@field(obj, "id")) != ?u32) {
return ObjectError.IncompatibleObjectType;
}
// Tried to persist something that is already persisted
if (@field(obj, "id") != null) {
std.debug.print("Can't persist an object with an id.\n", .{});
return;
}
const fields = std.meta.fields(T);
const query_init = "INSTERT INTO " ++ @typeName(T) ++ " (";
const sep = ", ";
var buf: [1024]u8 = .{0} ** 1024;
var offset: usize = 0;
std.mem.copy(u8, buf[offset..], query_init);
offset += query_init.len;
// Keep track of how many fields to instert
comptime var count: u8 = 0;
inline for (fields) |field_i| {
// Do not input "id" field.
comptime if (std.mem.eql(u8, field_i.name, "id")) {
continue;
};
// Do not input array fields (they got their own table)
comptime switch (@typeInfo(field_i.field_type)) {
.Array => continue,
.Pointer => |info| switch (info.size) {
.One => {},
.Many, .C, .Slice => continue,
},
else => {},
};
count += 1;
std.mem.copy(u8, buf[offset..], field_i.name);
offset += field_i.name.len;
std.mem.copy(u8, buf[offset..], sep);
offset += sep.len;
}
offset -= sep.len;
const query_end = ") VALUES (" ++ ("?, " ** (count - 1)) ++ "?);";
std.mem.copy(u8, buf[offset..], query_end);
offset += query_end.len;
std.debug.print("{s}\n", .{buf[0..offset]});
_ = db;
//const query = "INSERT INTO item (name) VALUES (?);";
//try db.exec(query, .{}, .{ .name = name });
}
pub fn queryTest(db: *sqlite.Db) ![]const u8 {
const row = try db.one(
struct {
version: [128:0]u8,
},
"SELECT sqlite_version();",
.{},
.{},
);
if (row) |row_i| {
return std.mem.sliceTo(&row_i.version, 0);
}
return "N/A";
}
pub fn init() !sqlite.Db {
var db = try sqlite.Db.init(.{
.mode = .{ .File = "/tmp/data.db" },
.open_flags = .{
.write = true,
.create = true,
},
.threading_mode = .MultiThread,
});
try migrate(&db);
return db;
}
// TODO: Make a proper migration system.
fn migrate(db: *sqlite.Db) !void {
try db.exec(
\\ CREATE TABLE IF NOT EXISTS item (
\\ id INTEGER PRIMARY KEY,
\\ name TEXT NOT NULL
\\ );
\\
\\ CREATE TABLE IF NOT EXISTS tag (
\\ id INTEGER PRIMARY KEY,
\\ name TEXT NOT NULL
\\ );
\\
\\ CREATE TABLE IF NOT EXISTS item_tag (
\\ item INTEGER,
\\ tag INTEGER,
\\
\\ PRIMARY KEY (item, tag),
\\
\\ FOREIGN KEY (item) REFERENCES item(id) ON DELETE CASCADE,
\\ FOREIGN KEY (tag) REFERENCES tag(id) ON DELETE CASCADE
\\ );
,
.{},
.{},
);
}