2022-10-20 11:23:15 +00:00
|
|
|
const std = @import("std");
|
2022-10-21 18:38:16 +00:00
|
|
|
|
|
|
|
const json = @import("json.zig");
|
2022-10-20 11:23:15 +00:00
|
|
|
const Db = @import("Db.zig");
|
2022-10-21 16:58:44 +00:00
|
|
|
const sqlite = @import("sqlite");
|
2022-10-21 18:38:16 +00:00
|
|
|
|
2022-10-21 16:58:44 +00:00
|
|
|
const Self = @This();
|
2022-10-20 11:23:15 +00:00
|
|
|
|
2022-10-22 03:15:36 +00:00
|
|
|
pub const Tag = struct {
|
2022-10-21 16:58:44 +00:00
|
|
|
// :0 for C compatility
|
|
|
|
name: [:0]const u8,
|
|
|
|
value: ?[:0]const u8,
|
|
|
|
};
|
|
|
|
|
|
|
|
id: ?i64, // If null, the object hasn't been persisted
|
2022-10-20 11:23:15 +00:00
|
|
|
tags: ?[]Tag,
|
2022-10-22 03:15:36 +00:00
|
|
|
allocator: ?std.mem.Allocator = null,
|
2022-10-21 16:58:44 +00:00
|
|
|
|
2022-10-22 03:15:36 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2022-10-21 16:58:44 +00:00
|
|
|
|
2022-10-22 03:15:36 +00:00
|
|
|
// Free the tags buffer
|
|
|
|
allocator.free(tags);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn persist(self: *Self, db: *sqlite.Db) !void {
|
2022-10-21 16:58:44 +00:00
|
|
|
// 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
|
2022-10-22 03:15:36 +00:00
|
|
|
// TODO: Do the "if they don't exist" part.
|
2022-10-21 16:58:44 +00:00
|
|
|
for (tags) |*tags_i| {
|
2022-10-22 03:15:36 +00:00
|
|
|
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 });
|
2022-10-21 16:58:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-10-21 18:38:16 +00:00
|
|
|
|
|
|
|
/// Convert into a JSON object. A call to deinit() is necessary from the caller's side
|
|
|
|
pub fn toJson(self: Self) json.Obj {
|
2022-10-22 03:15:36 +00:00
|
|
|
// Main object
|
2022-10-21 18:38:16 +00:00
|
|
|
var jobj = json.Obj.newObject();
|
|
|
|
|
2022-10-22 03:15:36 +00:00
|
|
|
// Add id (i64|null)
|
|
|
|
jobj.objectAdd("id", if (self.id) |id| &json.Obj.newInt64(id) else null);
|
2022-10-21 18:38:16 +00:00
|
|
|
|
2022-10-22 03:15:36 +00:00
|
|
|
// Add tags only if they're initialized
|
2022-10-21 18:38:16 +00:00
|
|
|
if (self.tags) |tags| {
|
2022-10-23 21:24:28 +00:00
|
|
|
var jtags = json.Obj.newObject();
|
|
|
|
jobj.objectAdd("tags", &jtags);
|
|
|
|
|
2022-10-21 18:38:16 +00:00
|
|
|
for (tags) |tag_i| {
|
|
|
|
const jtag = if (tag_i.value) |value|
|
|
|
|
&json.Obj.newString(value)
|
|
|
|
else
|
|
|
|
null;
|
|
|
|
|
|
|
|
jtags.objectAdd(tag_i.name, jtag);
|
|
|
|
}
|
2022-10-23 21:24:28 +00:00
|
|
|
} else {
|
|
|
|
// If nothing is found, set it to null
|
|
|
|
// just to make it more explicit
|
|
|
|
jobj.objectAdd("tags", null);
|
2022-10-21 18:38:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return jobj;
|
|
|
|
}
|
2022-10-22 03:15:36 +00:00
|
|
|
|
|
|
|
pub fn fromJson(jobj: json.Obj, allocator: std.mem.Allocator) !Self {
|
2022-10-23 21:24:28 +00:00
|
|
|
// Try to assemble a Item object from a JSON string
|
|
|
|
// An item could be just an id, just tags or both.
|
2022-10-22 03:15:36 +00:00
|
|
|
|
2022-10-23 21:24:28 +00:00
|
|
|
var tags: ?[]Tag = null;
|
2022-10-22 03:15:36 +00:00
|
|
|
|
2022-10-23 22:10:48 +00:00
|
|
|
if (jobj.objectGet("tags") catch null) |*jtags| {
|
2022-10-23 21:24:28 +00:00
|
|
|
defer jtags.deinit();
|
2022-10-23 22:10:48 +00:00
|
|
|
|
|
|
|
// Reserve space for slice of tags
|
2022-10-23 21:24:28 +00:00
|
|
|
const len = @intCast(usize, jtags.objectLen());
|
|
|
|
tags = try allocator.alloc(Tag, len);
|
2022-10-22 03:15:36 +00:00
|
|
|
|
2022-10-23 21:24:28 +00:00
|
|
|
var iter = jtags.objectGetIterator();
|
2022-10-22 03:15:36 +00:00
|
|
|
|
2022-10-23 21:24:28 +00:00
|
|
|
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;
|
|
|
|
}
|
2022-10-22 03:15:36 +00:00
|
|
|
}
|
|
|
|
|
2022-10-23 22:10:48 +00:00
|
|
|
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?
|
|
|
|
|
2022-10-22 03:15:36 +00:00
|
|
|
return Self{
|
2022-10-23 22:10:48 +00:00
|
|
|
.id = id,
|
2022-10-22 03:15:36 +00:00
|
|
|
.tags = tags,
|
|
|
|
.allocator = allocator,
|
|
|
|
};
|
|
|
|
}
|