const std = @import("std"); const Allocator = std.mem.Allocator; const builtin = @import("builtin"); const optimize_mode = builtin.mode; const sqlite = @import("sqlite"); pub const schema = \\CREATE TABLE IF NOT EXISTS tag( \\ id INTEGER PRIMARY KEY AUTOINCREMENT, \\ key TEXT UNIQUE NOT NULL, \\ parent_key TEXT TEXT, \\ FOREIGN KEY (parent_key) REFERENCES tag(key) ON DELETE RESTRICT \\); ; id: usize, key: []const u8, parent: ?*@This() = null, children: ?[]@This() = null, const internal = struct { id: usize, key: []const u8, parent_key: ?[]const u8, }; const ArrList = std.ArrayList(@This()); pub fn getTopLevelTags(conn: *sqlite.Db, arena: Allocator) ![]@This() { var stmt = try conn.prepareDynamic("SELECT id, key, parent_key FROM tag WHERE parent_key IS NULL;"); defer stmt.deinit(); var list = ArrList.empty; defer list.deinit(arena); var iter = try stmt.iterator(internal, .{}); while (try iter.nextAlloc(arena, .{})) |row| { var tag: @This() = .{ .id = row.id, .key = row.key, }; try tag.getChildren(conn, arena); try list.append(arena, tag); } return list.toOwnedSlice(arena); } pub fn getChildren(self: *@This(), conn: *sqlite.Db, arena: Allocator) !void { var stmt = try conn.prepareDynamic("SELECT id, key, parent_key FROM tag WHERE parent_key LIKE ?;"); defer stmt.deinit(); var list = ArrList.empty; defer list.deinit(arena); var iter = try stmt.iterator(internal, .{self.key}); while (try iter.nextAlloc(arena, .{})) |row| { var tag: @This() = .{ .id = row.id, .key = row.key, }; tag.parent = self; try tag.getChildren(conn, arena); try list.append(arena, tag); } self.children = try list.toOwnedSlice(arena); } pub fn jsonStringify(v: @This(), jws: anytype) !void { try jws.beginObject(); try jws.objectField("key"); try jws.write(v.key); try jws.objectField("children"); try jws.write(v.children); try jws.endObject(); }