╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ Patch.zig ────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭ find file ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ ╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ │ │ │http│ ssh │ │ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯
48 created_order: u64 = 0, 49 updated_order: u64 = 0, 50 }; 51 52 const Self = @This(); 53 54 pub const StatusKind = enum { 55 open, 56 closed, 57 merged, 58 59 const longest_len = blk: { 60 var len: usize = 0; 61 for (@typeInfo(StatusKind).@"enum".field_names) |name| len = @max(len, name.len); 62 break :blk len; 63 }; 64 }; 65 66 pub const Status = union(StatusKind) { 67 open, 68 closed, 69 merged: Merged, 70 71 pub const Merged = struct { 72 revision: MergeRevision, 73 patchrev_id: [evt.event_id_size * 2]u8, 74 }; 75 76 pub fn kind(self: Status) StatusKind { 77 return std.meta.activeTag(self); 78 } 79 }; 80 81 pub const Resolve = struct { 82 title: ?[]const u8 = null, 83 labels: ?[]const u8 = null, 84 hunks: []const []const u8 = &.{}, 85 theirs: []const u8 = "", 86 }; 87 88 pub const Update = union(enum) { 89 status: StatusKind, 90 fields: struct { title: []const u8, labels: []const u8, description: []const u8, target_branch: []const u8 }, 91 resolve: Resolve, 92 }; 93 94 pub const label_max_len = 64; 95 pub const merge_policy: evt.MergePolicy = .field_conflicts; 96 pub const record_map_key = "event-id->patch"; 97 pub const all_id_set_key = "patch-id-set"; 98 pub const conflicts_key = "conflicted-patch-id->conflict"; 99 pub const id_to_field_to_oid_key = "patch-id->field->oid"; 100 pub const target_patch_id_to_patch_id_set_key = "target-patch-id->patch-id-set"; 101 pub const status_to_id_set_key = "status->patch-id-set"; 102 pub const label_status_to_id_set_key = "label+status->patch-id-set"; 103 pub const revision_to_id_set_key = "target-branch+oid->patch-id-set"; 104 pub const source_to_id_set_key = "source-branch->patch-id-set"; 105 pub const patch_id_to_mergeability_key = "patch-id->mergeability"; 106 107 pub const LabelStatusKey = [label_max_len + 1 + StatusKind.longest_len]u8; 108 109 pub fn labelStatusKey(buffer: *LabelStatusKey, label: []const u8, status: StatusKind) ![]const u8 { 110 return std.fmt.bufPrint(buffer, "{s} {s}", .{ label, @tagName(status) }); 111 } 112 113 pub fn labelIterator(labels: []const u8) std.mem.SplitIterator(u8, .scalar) { 114 return std.mem.splitScalar(u8, labels, ' '); 115 } 116 117 pub fn fieldsValid(title: []const u8, labels: []const u8) bool { 118 if (!evt.titleValid(title)) return false; 119 var label_iter = labelIterator(labels); 120 while (label_iter.next()) |label| { 121 if (label.len > label_max_len) return false; 122 } 123 return true; 124 } 125 126 pub fn branchValid(branch: []const u8) bool { 127 return rf.validateName(branch) and !std.mem.eql(u8, branch, evt.events_ref.name); 128 } 129 130 pub fn consume( 131 comptime DB: type, 132 comptime hash_kind: hash.HashKind, 133 haxy_moment: DB.HashMap(.read_write), 134 event_id: *const [evt.event_id_size]u8, 135 record_maybe: ?Record, 136 arena: *std.heap.ArenaAllocator, 137 event_oid: ?[]const u8, 138 ) !void { 139 const record_key = hash.hashInt(hash_kind, event_id); 140 const records = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, record_map_key))); 141 const statuses = try DB.SortedMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, status_to_id_set_key))); 142 const label_statuses = try DB.SortedMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, label_status_to_id_set_key))); 143 const conflicts = try DB.SortedMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, conflicts_key))); 144 const field_oid_map = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, id_to_field_to_oid_key))); 145 146 var existing_maybe: ?Record = null; 147 const existing_cursor_maybe = try records.getCursor(record_key); 148 if (existing_cursor_maybe) |cursor| { 149 existing_maybe = try evt.read(Record, DB, hash_kind, arena, try DB.HashMap(.read_only).init(cursor)); 150 } 151 152 var record = record_maybe orelse try evt.removedRecord(Record, DB, hash_kind, haxy_moment.readOnly(), existing_maybe); 153 154 if (!fieldsValid(record.event.title, record.event.labels)) return error.InvalidPatch; 155 if (!branchValid(record.event.target_branch)) return error.InvalidTargetBranch; 156 if (record.event.source_branch) |branch| { 157 if (!branchValid(branch)) return error.InvalidSourceBranch; 158 if (std.mem.eql(u8, branch, record.event.target_branch)) return error.SameBranch; 159 } 160 const revision_id = if (record.event.revision) |*revision| blk: { 161 try evt.PatchRev.validateOid(hash_kind, revision.squash_oid); 162 try evt.PatchRev.validateOid(hash_kind, revision.source_oid); 163 break :blk try evt.parseEventId(&revision.id); 164 } else null; 165 const target_patch_id = if (record.event.target_patch_id) |*id| try evt.parseEventId(id) else null; 166 if (target_patch_id) |*id| { 167 if (std.mem.eql(u8, id, event_id)) return error.InvalidPatch; 168 } 169 const status_kind = record.event.status.kind(); 170 171 if (existing_maybe) |existing| { 172 if (!evt.fieldEqual(?[]const u8, existing.event.source_branch, record.event.source_branch)) return error.PatchSourceChanged; 173 record.created_order = existing.created_order; 174 record.author_email = existing.author_email; 175 record.created_timestamp = existing.created_timestamp; 176 const existing_status_kind = existing.event.status.kind(); 177 if (existing_status_kind == .merged and !evt.fieldEqual(Status, existing.event.status, record.event.status)) return error.PatchAlreadyMerged; 178 if (existing_status_kind == .merged and !std.mem.eql(u8, existing.event.target_branch, record.event.target_branch)) return error.PatchAlreadyMerged; 179 if (event_oid != null) { 180 if (!std.meta.eql(existing.event.target_patch_id, record.event.target_patch_id)) { 181 return error.TargetPatchChanged; 182 } 183 } 184 } 185 186 switch (record.event.status) { 187 .open, .closed => {}, 188 .merged => |merged| { 189 const merged_id = try evt.parseEventId(&merged.patchrev_id); 190 const merged_revision = (try evt.PatchRev.readById(DB, hash_kind, haxy_moment.readOnly(), arena, &merged_id)) orelse return error.InvalidPatch; 191 if (merged_revision.removed) return error.InvalidPatch; 192 const id = revision_id orelse return error.InvalidPatch; 193 const selected = record.event.revision orelse return error.InvalidPatch; 194 const revision = (try evt.PatchRev.readById(DB, hash_kind, haxy_moment.readOnly(), arena, &id)) orelse return error.InvalidPatch; 195 if (!selected.matches(revision)) return error.InvalidPatch; 196 }, 197 } 198 199 if (record.removed or status_kind != .open) { 200 const key = hash.hashInt(hash_kind, patch_id_to_mergeability_key); 201 if (try haxy_moment.getCursor(key) != null) { 202 const checks = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(key)); 203 _ = try checks.remove(record_key); 204 } 205 } 206 207 const order_key = evt.orderKeyDesc(record.created_order, event_id); 208 if (existing_maybe) |existing| { 209 if (event_oid != null or record.removed) _ = try conflicts.remove(&order_key); 210 if (!existing.removed) { 211 const existing_status_kind = existing.event.status.kind(); 212 const old_status = try statusSet(DB, statuses, existing_status_kind); 213 _ = try old_status.remove(&order_key); 214 try removeFromLabelSets(DB, label_statuses, existing.event.labels, existing_status_kind, &order_key); 215 if (existing_status_kind != .merged) { 216 if (existing.event.revision) |revision| { 217 const revisions = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, revision_to_id_set_key))); 218 for ([_][]const u8{ revision.squash_oid, revision.source_oid }, 0..) |oid, i| { 219 if (i != 0 and std.mem.eql(u8, revision.squash_oid, revision.source_oid)) continue; 220 const key_hash = hash.hashInt(hash_kind, try revisionKey(arena.allocator(), existing.event.target_branch, oid)); 221 const ids = try DB.CountedHashSet(.read_write).init(try revisions.putCursor(key_hash)); 222 _ = try ids.remove(record_key); 223 if (try ids.count() == 0) _ = try revisions.remove(key_hash); 224 } 225 } 226 } 227 } 228 } 229 230 if (record.event.source_branch) |branch| { 231 const sources = try DB.SortedMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, source_to_id_set_key))); 232 if (!record.removed and status_kind != .merged) { 233 const ids = try DB.CountedHashSet(.read_write).init(try sources.putCursor(branch)); 234 try ids.put(record_key, .{ .bytes = event_id }); 235 } else if (try sources.getCursor(branch) != null) { 236 const ids = try DB.CountedHashSet(.read_write).init(try sources.putCursor(branch)); 237 _ = try ids.remove(record_key); 238 if (try ids.count() == 0) _ = try sources.remove(branch); 239 } 240 } 241 242 try evt.writeOid(Self, DB, hash_kind, field_oid_map, record_key, if (existing_maybe) |existing| existing.event else null, record.event, event_oid); 243 244 const cursor = try records.putCursor(record_key); 245 try evt.upsert(Record, DB, hash_kind, try DB.HashMap(.read_write).init(cursor), record); 246 try evt.indexEvent(DB, hash_kind, haxy_moment, event_id, .patch, existing_maybe, record); 247 248 if (existing_cursor_maybe == null) { 249 const ids = try DB.SortedSet(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, all_id_set_key))); 250 try ids.put(&order_key); 251 if (target_patch_id) |*target_id| { 252 const targets = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, target_patch_id_to_patch_id_set_key))); 253 const children = try DB.SortedSet(.read_write).init(try targets.putCursor(hash.hashInt(hash_kind, target_id))); 254 try children.put(&order_key); 255 } 256 } 257 258 // the search index is keyed by the same order key as the sets above 259 try srch_thrd.update(DB, hash_kind, .patch, haxy_moment, arena.child_allocator, &order_key, existing_maybe, record); 260 261 if (!record.removed) { 262 const status = try statusSet(DB, statuses, status_kind); 263 try status.put(&order_key); 264 265 var label_iter = labelIterator(record.event.labels); 266 while (label_iter.next()) |label| { 267 if (label.len == 0 or label.len > label_max_len) continue; 268 var key_buffer: LabelStatusKey = undefined; 269 const set = try DB.SortedSet(.read_write).init(try label_statuses.putCursor(try labelStatusKey(&key_buffer, label, status_kind))); 270 try set.put(&order_key); 271 } 272 273 if (status_kind != .merged) { 274 if (record.event.revision) |revision| { 275 const revisions = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, revision_to_id_set_key))); 276 for ([_][]const u8{ revision.squash_oid, revision.source_oid }, 0..) |oid, i| { 277 if (i != 0 and std.mem.eql(u8, revision.squash_oid, revision.source_oid)) continue; 278 const key = try revisionKey(arena.allocator(), record.event.target_branch, oid); 279 const ids = try DB.CountedHashSet(.read_write).init(try revisions.putCursor(hash.hashInt(hash_kind, key))); 280 try ids.put(record_key, .{ .bytes = event_id }); 281 } 282 } 283 } 284 } 285 } 286 287 pub fn resolveMerge( 288 target: Self, 289 parent: Self, 290 merged: *Self, 291 outcome: *[@typeInfo(Self).@"struct".field_names.len]evt.FieldMerge, 292 ) void { 293 const status_index = std.meta.fieldIndex(Self, "status") orelse @compileError("Patch.status not found"); 294 const revision_index = std.meta.fieldIndex(Self, "revision") orelse @compileError("Patch.revision not found"); 295 if (outcome[status_index] != .conflicted or outcome[revision_index] == .conflicted) return; 296 297 if (target.status.kind() == .merged and evt.fieldEqual(?Revision, target.revision, merged.revision)) { 298 merged.status = target.status; 299 outcome[status_index] = .kept; 300 } else if (parent.status.kind() == .merged and evt.fieldEqual(?Revision, parent.revision, merged.revision)) { 301 merged.status = parent.status; 302 outcome[status_index] = .parent; 303 } 304 } 305 306 pub fn update( 307 host: evt.Host, 308 comptime repo_kind: rp.RepoKind, 309 comptime repo_opts: rp.RepoOpts(repo_kind), 310 io: std.Io, 311 allocator: std.mem.Allocator, 312 repo: *rp.Repo(repo_kind, repo_opts), 313 id: *const [evt.event_id_size]u8, 314 change: Update, 315 author: evt.CommitAuthor, 316 ) !void { 317 var arena = std.heap.ArenaAllocator.init(allocator); 318 defer arena.deinit(); 319 const record = (try evt.readFromRepo(Self, repo_kind, repo_opts, io, allocator, &arena, repo, id)) orelse return error.NotFound; 320 if (record.removed) return error.NotFound; 321 322 var updated = record.event; 323 switch (change) { 324 .status => |status| { 325 if (updated.status.kind() == status) return; 326 updated.status = switch (status) { 327 .open => .open, 328 .closed => .closed, 329 .merged => return error.InvalidPatchStatus, 330 }; 331 // a closed patch may have missed source branch updates 332 if (status == .open and updated.source_branch != null) updated.revision = null; 333 }, 334 .fields => |fields| { 335 if (!branchValid(fields.target_branch)) return error.InvalidTargetBranch; 336 if ((try repo.readRef(io, .{ .kind = .head, .name = fields.target_branch })) == null) return error.InvalidTargetBranch; 337 updated.title = fields.title; 338 updated.labels = fields.labels; 339 updated.description = fields.description; 340 if (!std.mem.eql(u8, updated.target_branch, fields.target_branch)) updated.revision = null; 341 updated.target_branch = fields.target_branch; 342 }, 343 .resolve => |resolve| { 344 updated = try resolveFields(repo_kind, repo_opts, io, allocator, &arena, repo, id, record, resolve); 345 }, 346 } 347 if (!fieldsValid(updated.title, updated.labels)) return error.InvalidFields; 348 349 if (updated.source_branch != null and updated.revision == null) return @import("../patch.zig").writeBranchPatch(host, repo_kind, repo_opts, io, allocator, repo, std.fmt.bytesToHex(id.*, .lower), updated, record.event, author); 350 try evt.consume(host, .repo, repo_kind, repo_opts, io, allocator, repo, evt.events_ref, &.{.{ 351 .id = std.fmt.bytesToHex(id.*, .lower), 352 .timestamp = @intCast(std.Io.Timestamp.now(io, .real).toSeconds()), 353 .author = author, 354 .event = .{ .patch = updated }, 355 }}); 356 } 357 358 fn resolveFields( 359 comptime repo_kind: rp.RepoKind, 360 comptime repo_opts: rp.RepoOpts(repo_kind), 361 io: std.Io, 362 allocator: std.mem.Allocator, 363 arena: *std.heap.ArenaAllocator, 364 repo: *rp.Repo(repo_kind, repo_opts), 365 id: *const [evt.event_id_size]u8, 366 live: Record, 367 resolve: Resolve, 368 ) !Self { 369 const DB = evt.EventDB(repo_opts.hash); 370 var updated = live.event; 371 if (resolve.title) |title| updated.title = title; 372 if (resolve.labels) |labels| updated.labels = labels; 373 374 var event_db_maybe: ?evt.LocalEventDB(repo_opts.hash) = if (repo_kind == .git) try evt.LocalEventDB(repo_opts.hash).openReadOnly(io, allocator, repo.core.repo_dir) else null; 375 defer if (event_db_maybe) |*event_db| event_db.deinit(io, allocator); 376 const moment = (if (event_db_maybe) |*event_db| 377 evt.currentMomentFromDb(repo_opts.hash, event_db.db) 378 else if (repo_kind == .git) 379 return updated 380 else 381 evt.currentMoment(repo_opts, repo)) catch return updated; 382 const conflicts_cursor = (try moment.getCursor(hash.hashInt(repo_opts.hash, conflicts_key))) orelse return updated; 383 const conflicts = try DB.SortedMap(.read_only).init(conflicts_cursor); 384 const conflict_cursor = (try conflicts.getCursor(&evt.orderKeyDesc(live.created_order, id))) orelse return updated; 385 const entry = try DB.HashMap(.read_only).init(conflict_cursor); 386 const fields_cursor = (try entry.getCursor(hash.hashInt(repo_opts.hash, evt.conflicted_fields_key))) orelse return updated; 387 const fields = try fields_cursor.readBytesAlloc(arena.allocator(), null); 388 const their_cursor = (try entry.getCursor(hash.hashInt(repo_opts.hash, evt.their_record_key))) orelse return updated; 389 const theirs = try evt.read(Record, DB, repo_opts.hash, arena, try DB.HashMap(.read_only).init(their_cursor)); 390 391 const original_target_branch = updated.target_branch; 392 var field_iter = std.mem.splitScalar(u8, fields, ' '); 393 while (field_iter.next()) |field| { 394 if (std.mem.eql(u8, field, "status") and fieldListed(resolve.theirs, ',', field)) updated.status = theirs.event.status; 395 if (std.mem.eql(u8, field, "revision") and fieldListed(resolve.theirs, ',', field)) updated.revision = theirs.event.revision; 396 if (std.mem.eql(u8, field, "target_branch") and fieldListed(resolve.theirs, ',', field)) updated.target_branch = theirs.event.target_branch; 397 } 398 if (!std.mem.eql(u8, original_target_branch, updated.target_branch)) updated.revision = null; 399 400 if (!fieldListed(fields, ' ', "description")) return updated; 401 var base_description: []const u8 = ""; 402 if (try entry.getCursor(hash.hashInt(repo_opts.hash, evt.base_record_key))) |base_cursor| { 403 const base = try evt.read(Record, DB, repo_opts.hash, arena, try DB.HashMap(.read_only).init(base_cursor)); 404 base_description = base.event.description; 405 } 406 const chunks = try diff3.chunks(allocator, arena, base_description, live.event.description, theirs.event.description); 407 var resolutions: std.ArrayList([]const u8) = .empty; 408 var hunk_index: usize = 0; 409 for (chunks) |chunk| { 410 if (chunk != .conflict) continue; 411 try resolutions.append(arena.allocator(), if (hunk_index < resolve.hunks.len) resolve.hunks[hunk_index] else (chunk.conflict.ours orelse "")); 412 hunk_index += 1; 413 } 414 updated.description = try diff3.assemble(arena.allocator(), chunks, resolutions.items); 415 return updated; 416 } 417 418 fn fieldListed(fields: []const u8, delimiter: u8, name: []const u8) bool { 419 var iter = std.mem.splitScalar(u8, fields, delimiter); 420 while (iter.next()) |field| { 421 if (std.mem.eql(u8, field, name)) return true; 422 } 423 return false; 424 } 425 426 fn revisionKey(allocator: std.mem.Allocator, target_branch: []const u8, oid: []const u8) ![]u8 { 427 return std.mem.concat(allocator, u8, &.{ target_branch, "\x00", oid }); 428 } 429 430 fn statusSet( 431 comptime DB: type, 432 statuses: DB.SortedMap(.read_write), 433 status: StatusKind, 434 ) !DB.SortedSet(.read_write) { 435 return DB.SortedSet(.read_write).init(try statuses.putCursor(@tagName(status))); 436 } 437 438 fn removeFromLabelSets( 439 comptime DB: type, 440 label_statuses: DB.SortedMap(.read_write), 441 labels: []const u8, 442 status: StatusKind, 443 order_key: []const u8, 444 ) !void { 445 var label_iter = labelIterator(labels); 446 while (label_iter.next()) |label| { 447 if (label.len == 0 or label.len > label_max_len) continue; 448 var key_buffer: LabelStatusKey = undefined; 449 const key = try labelStatusKey(&key_buffer, label, status); 450 const set = try DB.SortedSet(.read_write).init(try label_statuses.putCursor(key)); 451 _ = try set.remove(order_key); 452 if (0 == try set.count()) _ = try label_statuses.remove(key); 453 } 454 } 455 456 pub fn readById( 457 comptime DB: type, 458 comptime hash_kind: hash.HashKind, 459 haxy_moment: DB.HashMap(.read_only), 460 arena: *std.heap.ArenaAllocator, 461 id: *const [evt.event_id_size]u8, 462 ) !?Record { 463 const records_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, record_map_key)) orelse return null; 464 const records = try DB.HashMap(.read_only).init(records_cursor); 465 const record_cursor = try records.getCursor(hash.hashInt(hash_kind, id)) orelse return null; 466 return try evt.read(Record, DB, hash_kind, arena, try DB.HashMap(.read_only).init(record_cursor)); 467 } 468