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); var jtags = json.Obj.newObject(); jobj.objectAdd("tags", &jtags); // Add tags only if they're initialized if (self.tags) |tags| { for (tags) |tag_i| { const jtag = if (tag_i.value) |value| &json.Obj.newString(value) else null; jtags.objectAdd(tag_i.name, jtag); } } return jobj; } pub fn fromJson(jobj: json.Obj, allocator: std.mem.Allocator) !Self { var jtags = jobj.objectGet("tags"); defer jtags.deinit(); const len = @intCast(usize, jtags.objectLen()); var tags = try allocator.alloc(Tag, len); var iter = jtags.objectGetIterator(); var i: usize = 0; while (iter.next()) |*tag| { 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; } return Self{ .id = null, .tags = tags, .allocator = allocator, }; }