const std = @import("std"); const json = @import("json.zig"); const Db = @import("Db.zig"); const sqlite = @import("sqlite"); const Self = @This(); pub const Tag = struct { // :0 for C compatility name: [:0]const u8, value: ?[:0]const u8, }; id: ?i64, // If null, the object hasn't been persisted tags: ?[]Tag, allocator: ?std.mem.Allocator = null, pub fn deinit(self: *Self) void { // If there's no allocator there has been no allocations const allocator = self.allocator orelse return; const tags = self.tags orelse return; // Free each tag for (tags) |*tag| { allocator.free(tag.name); if (tag.value) |value| { allocator.free(value); } } // Free the tags buffer allocator.free(tags); } pub fn persist(self: *Self, db: *sqlite.Db) !void { // Insert only if there's no ID. if (self.id == null) { const query = "INSERT INTO item VALUES(NULL);"; try db.exec(query, .{}, .{}); // Update the ID field self.id = try Db.getLastId(db); } // If the tags haven't been initialized, don't touch anything if (self.tags) |tags| { // Create new tags if they don't exist // TODO: Do the "if they don't exist" part. for (tags) |*tags_i| { const name_query = "INSERT INTO tag (name) VALUES (?)"; try db.exec(name_query, .{}, .{ .name = tags_i.name }); const rel_query = "INSERT INTO item_tag (item, tag, value) VALUES (?, ?, ?);"; try db.exec(rel_query, .{}, .{ .item = self.id, .tag = tags_i.name, .value = tags_i.value }); } } } /// Convert into a JSON object. A call to deinit() is necessary from the caller's side pub fn toJson(self: Self) json.Obj { // Main object var jobj = json.Obj.newObject(); // Add id (i64|null) jobj.objectAdd("id", if (self.id) |id| &json.Obj.newInt64(id) else null); // Add tags only if they're initialized if (self.tags) |tags| { var jtags = json.Obj.newObject(); jobj.objectAdd("tags", &jtags); for (tags) |tag_i| { const jtag = if (tag_i.value) |value| &json.Obj.newString(value) else null; jtags.objectAdd(tag_i.name, jtag); } } else { // If nothing is found, set it to null // just to make it more explicit jobj.objectAdd("tags", null); } return jobj; } pub fn fromJson(jobj: json.Obj, allocator: std.mem.Allocator) !Self { // Try to assemble a Item object from a JSON string // An item could be just an id, just tags or both. var tags: ?[]Tag = null; if (jobj.objectGet("tags") catch null) |*jtags| { defer jtags.deinit(); // Reserve space for slice of tags const len = @intCast(usize, jtags.objectLen()); tags = try allocator.alloc(Tag, len); var iter = jtags.objectGetIterator(); var i: usize = 0; while (iter.next()) |*tag| { // Whe know it's not null at this point tags.?[i].name = try allocator.dupeZ(u8, tag.key); tags.?[i].value = if (tag.value) |*value| try allocator.dupeZ(u8, value.getString()) else null; i += 1; } } var id: ?i64 = null; if (jobj.objectGet("id") catch null) |*jid| { defer jid.deinit(); id = jid.getInt64(); } // TODO: What should be done when both things are null? // Return a null Obj or leave it as-is? return Self{ .id = id, .tags = tags, .allocator = allocator, }; }