╭ changed ──────────╮ │src/event/Issue.zig│ ╰───────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────╮ │ 6 title: []const u8, │ │ 7 description: []const u8, │ │ 8 tags: []const u8, // comma-separated │ │ 9 +status: Status = .open, │ │10 created_ts: u64 = 0, // the commit timestamp of the event that first created this issue │ │11 + │ │12 +pub const Status = enum { │ │13 + open, │ │14 + closed, │ │15 + │ │16 + const longest_len = blk: { │ │17 + var len: usize = 0; │ │18 + for (@typeInfo(Status).@"enum".fields) |field| len = @max(len, field.name.len); │ │19 + break :blk len; │ │20 + }; │ │21 +}; │ │22 │ │23 pub const tag_max_len = 64; │ │24 + │ │25 +// a "tag,status" key names a tag's per-status issue set │ │26 +pub const TagStatusKey = [tag_max_len + 1 + Status.longest_len]u8; │ │27 │ │28 +pub fn tagStatusKey(buffer: *TagStatusKey, tag: []const u8, status: Status) ![]const u8 {│ │29 + return std.fmt.bufPrint(buffer, "{s},{s}", .{ tag, @tagName(status) }); │ │30 +} │ │31 + │ │32 pub fn tagIterator(tags: []const u8) std.mem.SplitIterator(u8, .scalar) { │ │33 return std.mem.splitScalar(u8, tags, ','); │ │34 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │59 const event_id_to_issue_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "event-id->issue")); │ │60 const event_id_to_issue = try DB.HashMap(.read_write).init(event_id_to_issue_cursor); │ │61 │ │43 - // maps each tag to the set of issues carrying it, so a tag page can seek │ │44 - // its window the same way the unfiltered issue list does. │ │45 - const tag_to_issues_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "tag->issue-id-set")); │ │62 + // the per-status sets the issues views list, ordered by creation time, │ │63 + // keyed by status name │ │64 + const status_to_issues_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "status->issue-id-set")); │ │65 + const status_to_issues = try DB.SortedMap(.read_write).init(status_to_issues_cursor); │ │66 + │ │67 + // the per-tag, per-status sets, keyed "tag,status" │ │68 + const tag_to_issues_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "tag+status->issue-id-set"));│ │69 const tag_to_issues = try DB.SortedMap(.read_write).init(tag_to_issues_cursor); │ │70 │ │71 if (event_maybe) |event| { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │78 const existing_event = try evt.read(@This(), DB, hash_kind, arena, existing_issue); │ │79 event_to_write.created_ts = existing_event.created_ts; │ │80 │ │58 - // drop the old tags' entries; the current tags are re-added below │ │81 + // drop the old status's and tags' entries; the current ones are re-added below │ │82 const order_key = evt.orderKey(existing_event.created_ts, event_id); │ │60 - try removeFromTagSets(DB, tag_to_issues, existing_event.tags, &order_key); │ │83 + const status_set = try statusSet(DB, status_to_issues, existing_event.status); │ │84 + _ = try status_set.remove(&order_key); │ │85 + try removeFromTagSets(DB, tag_to_issues, existing_event.tags, existing_event.status, &order_key);│ │86 } else { │ │87 // first time we've seen this issue: stamp it with the commit timestamp │ │88 event_to_write.created_ts = created_ts; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │ 94 │ │ 95 const order_key = evt.orderKey(event_to_write.created_ts, event_id); │ │ 96 │ │ 97 + const status_set = try statusSet(DB, status_to_issues, event.status);│ │ 98 + try status_set.put(&order_key); │ │ 99 + │ │100 var tag_iter = tagIterator(event.tags); │ │101 while (tag_iter.next()) |tag| { │ │102 if (tag.len == 0) continue; │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │103 if (tag.len > tag_max_len) return error.TagTooLong; │ │ 76 - const tag_set_cursor = try tag_to_issues.putCursor(tag); │ │104 + var key_buffer: TagStatusKey = undefined; │ │105 + const tag_set_cursor = try tag_to_issues.putCursor(try tagStatusKey(&key_buffer, tag, event.status));│ │106 const tag_set = try DB.SortedSet(.read_write).init(tag_set_cursor); │ │107 try tag_set.put(&order_key); │ │ 79 - } │ │ 80 - │ │ 81 - // first time we've seen this issue: add it to the set the issues view │ │ 82 - // lists, ordered by creation time. the key (orderKey) embeds the event │ │ 83 - // id, so the set needs no value. │ │ 84 - if (existing_cursor_maybe == null) { │ │ 85 - const issue_id_set_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "issue-id-set")); │ │ 86 - const issue_id_set = try DB.SortedSet(.read_write).init(issue_id_set_cursor); │ │ 87 - try issue_id_set.put(&order_key); │ │108 } │ │109 } else { │ │ 90 - // drop it from the ordered sets using its recorded creation timestamp │ │110 + // drop it from the ordered sets using its recorded creation timestamp and status │ │111 if (try event_id_to_issue.getCursor(issue_key)) |existing_cursor| { │ │112 const existing_issue = try DB.HashMap(.read_only).init(existing_cursor); │ │113 const existing_event = try evt.read(@This(), DB, hash_kind, arena, existing_issue); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │114 const order_key = evt.orderKey(existing_event.created_ts, event_id); │ │115 │ │ 96 - const issue_id_set_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "issue-id-set")); │ │ 97 - const issue_id_set = try DB.SortedSet(.read_write).init(issue_id_set_cursor); │ │ 98 - _ = try issue_id_set.remove(&order_key); │ │116 + const status_set = try statusSet(DB, status_to_issues, existing_event.status); │ │117 + _ = try status_set.remove(&order_key); │ │118 │ │100 - try removeFromTagSets(DB, tag_to_issues, existing_event.tags, &order_key); │ │119 + try removeFromTagSets(DB, tag_to_issues, existing_event.tags, existing_event.status, &order_key);│ │120 } │ │121 if (!try event_id_to_issue.remove(issue_key)) return error.EventNotFound; │ │122 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │123 } │ │124 │ │106 -// remove an issue's order key from each of its tags' sets, pruning tags whose│ │107 -// set becomes empty. │ │125 +// `status`'s sorted set within `statuses`, keyed by status name │ │126 +fn statusSet( │ │127 + comptime DB: type, │ │128 + statuses: DB.SortedMap(.read_write), │ │129 + status: Status, │ │130 +) !DB.SortedSet(.read_write) { │ │131 + const cursor = try statuses.putCursor(@tagName(status)); │ │132 + return DB.SortedSet(.read_write).init(cursor); │ │133 +} │ │134 + │ │135 +// remove an issue's order key from its tags' `status` sets, pruning entries │ │136 +// whose set becomes empty │ │137 fn removeFromTagSets( │ │138 comptime DB: type, │ │139 tag_to_issues: DB.SortedMap(.read_write), │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────╮ │140 tags: []const u8, │ │141 + status: Status, │ │142 order_key: []const u8, │ │143 ) !void { │ │144 var tag_iter = tagIterator(tags);│ ╰──────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │145 while (tag_iter.next()) |tag| { │ │146 if (tag.len == 0) continue; │ │117 - const tag_set_cursor = try tag_to_issues.putCursor(tag); │ │147 + var key_buffer: TagStatusKey = undefined; │ │148 + const key = try tagStatusKey(&key_buffer, tag, status); │ │149 + const tag_set_cursor = try tag_to_issues.putCursor(key); │ │150 const tag_set = try DB.SortedSet(.read_write).init(tag_set_cursor);│ │151 _ = try tag_set.remove(order_key); │ │120 - if (0 == try tag_set.count()) _ = try tag_to_issues.remove(tag); │ │152 + if (0 == try tag_set.count()) _ = try tag_to_issues.remove(key); │ │153 } │ │154 } │ │155 │ ╰────────────────────────────────────────────────────────────────────────────────╯