╭ changed ───────────────╮ │src/event/Discussion.zig│ ╰────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 94 // the search index is keyed by creation, not activity: ordering is the │ │ 95 // list's business, so a comment moving the discussion up leaves it alone │ │ 96 const search_key = evt.orderKeyDesc(record_to_write.created_order, event_id); │ │ 97 - try srch_thrd.update(DB, hash_kind, .discuss, haxy_moment, arena.child_allocator, if (existing_record_maybe) |existing| srch_thrd.doc(&search_key, existing) else null, srch_thrd.doc(&search_key, record_to_write));│ │ 97 + try srch_thrd.update(DB, hash_kind, .discuss, haxy_moment, arena.child_allocator, &search_key, existing_record_maybe, record_to_write); │ │ 98 │ │ 99 if (!record_to_write.removed) { │ │100 try active.put(&order_key); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────────╮ │src/event/Issue.zig│ ╰───────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │141 } │ │142 │ │143 // the search index is keyed by the same order key as the sets above │ │144 - try srch_thrd.update(DB, hash_kind, .issue, haxy_moment, arena.child_allocator, if (existing_record_maybe) |existing| srch_thrd.doc(&order_key, existing) else null, srch_thrd.doc(&order_key, record_to_write));│ │144 + try srch_thrd.update(DB, hash_kind, .issue, haxy_moment, arena.child_allocator, &order_key, existing_record_maybe, record_to_write); │ │145 │ │146 if (!record_to_write.removed) { │ │147 const status_set = try statusSet(DB, status_to_issues, record_to_write.event.status); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────────╮ │src/event/Patch.zig│ ╰───────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │254 } │ │255 │ │256 // the search index is keyed by the same order key as the sets above │ │257 - try srch_thrd.update(DB, hash_kind, .patch, haxy_moment, arena.child_allocator, if (existing_maybe) |existing| srch_thrd.doc(&order_key, existing) else null, srch_thrd.doc(&order_key, record));│ │257 + try srch_thrd.update(DB, hash_kind, .patch, haxy_moment, arena.child_allocator, &order_key, existing_maybe, record); │ │258 │ │259 if (!record.removed) { │ │260 const status = try statusSet(DB, statuses, status_kind); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───╮ │src/find.zig│ ╰────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │107 } │ │108 } │ │109 } │ │110 - │ │111 -// the action a file index refresh records when it runs on its own │ │112 -pub const undo_action = "haxy/find-index"; │ │113 - │ │114 -// reconcile every branch tip's index, in a transaction of its own │ │115 -pub fn refresh( │ │116 - comptime repo_opts: rp.RepoOpts(.xit), │ │117 - io: std.Io, │ │118 - allocator: std.mem.Allocator, │ │119 - repo: *rp.Repo(.xit, repo_opts), │ │120 -) !void { │ │121 - const Repo = rp.Repo(.xit, repo_opts); │ │122 - const DB = Repo.DB; │ │123 - const Save = struct { │ │124 - core: *Repo.Core, │ │125 - io: std.Io, │ │126 - allocator: std.mem.Allocator, │ │110 │ │128 - pub fn run(ctx: @This(), cursor: *DB.Cursor(.read_write)) !void { │ │129 - var moment = try DB.HashMap(.read_write).init(cursor.*); │ │130 - const state = Repo.State(.read_write){ .core = ctx.core, .extra = .{ .moment = &moment } }; │ │131 - if (!try refreshInTransaction(repo_opts, state, &moment, ctx.io, ctx.allocator, null)) return error.CancelTransaction; │ │132 - │ │133 - // record the user action for undo │ │134 - try xit.undo.write(repo_opts, state, std.Io.Timestamp.now(ctx.io, .real).toSeconds(), .{ .custom = .{ .action_kind = undo_action } }); │ │135 - } │ │136 - }; │ │137 - │ │138 - // held across the reads and the writes, so a concurrent refresh can't │ │139 - // change the entries these decisions were made from. │ │140 - try repo.core.db_file.lock(io, .exclusive); │ │141 - defer repo.core.db_file.unlock(io); │ │142 - │ │143 - const history = try DB.ArrayList(.read_write).init(repo.core.db.rootCursor()); │ │144 - history.appendContext(.{ .slot = try history.getSlot(-1) }, Save{ .core = &repo.core, .io = io, .allocator = allocator }) catch |err| switch (err) {│ │145 - error.CancelTransaction => {}, │ │146 - else => return err, │ │147 - }; │ │148 -} │ │149 - │ │111 // reconcile them inside the caller's transaction, so the index lands with the │ │112 // change that invalidated it. every branch is reconciled, since an up-to-date │ │113 // one costs a ref read and an oid compare. reports whether anything was │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────╮ │src/search.zig│ ╰──────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────╮ │18 const max_expansions = 50; │ │19 // the largest doc key, so results are read into a buffer rather than allocated.│ │20 pub const max_doc_key_len = 64; │ │21 - │ │22 -// index `text` under `doc_key`. │ │23 -pub fn add( │ │24 - comptime DB: type, │ │25 - index: DB.SortedMap(.read_write), │ │26 - allocator: std.mem.Allocator, │ │27 - doc_key: []const u8, │ │28 - text: []const u8, │ │29 -) !void { │ │30 - var arena = std.heap.ArenaAllocator.init(allocator); │ │31 - defer arena.deinit(); │ │32 - for (try tokenize(arena.allocator(), text)) |word| { │ │33 - try addWord(DB, index, doc_key, word); │ │34 - } │ │35 -} │ │36 - │ │37 -// drop `doc_key`'s postings. the caller supplies the text again, so no forward │ │38 -// index is stored. │ │39 -pub fn remove( │ │40 - comptime DB: type, │ │41 - index: DB.SortedMap(.read_write), │ │42 - allocator: std.mem.Allocator, │ │43 - doc_key: []const u8, │ │44 - text: []const u8, │ │45 -) !void { │ │46 - var arena = std.heap.ArenaAllocator.init(allocator); │ │47 - defer arena.deinit(); │ │48 - for (try tokenize(arena.allocator(), text)) |word| { │ │49 - try removeWord(DB, index, doc_key, word); │ │50 - } │ │51 -} │ │21 │ │22 // move `doc_key` from `old_text`'s words to `new_text`'s. the words they share │ │23 // are left alone, so editing a long description costs what the edit changed │ ╰────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────╮ │55 -// rather than the whole text. │ │24 +// rather than the whole text. an empty side adds or drops the document.│ │25 pub fn replace( │ │26 comptime DB: type, │ │27 index: DB.SortedMap(.read_write), │ ╰────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────╮ │ 70 pub fn Query(comptime DB: type) type { │ │ 71 return struct { │ │ 72 // one per typed word; empty when nothing can match │ │104 - terms: []Term, │ │ 73 + terms: std.ArrayList(Term), │ │ 74 // the key the next result starts from, plus room for the one byte that│ │ 75 // steps past a returned key │ │ 76 pos: [max_doc_key_len + 1]u8 = undefined, │ ╰────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │148 try postings.append(aa, .{ .set = try DB.SortedSet(.read_only).init(pair.value_cursor) });│ │149 } │ │150 // a word nothing starts with rules out every document │ │182 - if (postings.items.len == 0) return .{ .terms = &.{} }; │ │151 + if (postings.items.len == 0) return .{ .terms = .empty }; │ │152 try terms.append(aa, .{ .postings = postings.items }); │ │153 } │ │185 - return .{ .terms = terms.items }; │ │154 + return .{ .terms = terms }; │ │155 } │ │156 │ │157 // narrow the results to the doc keys in `set`. it is one more term, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────╮ │158 // with that set as its only posting. a query nothing can match stays │ │159 // empty, since requiring a set would turn it into every doc key in it. │ │160 pub fn require(self: *Self, aa: std.mem.Allocator, set: DB.SortedSet(.read_only)) !void {│ │192 - if (self.terms.len == 0) return; │ │193 - var terms: std.ArrayList(Term) = .empty; │ │194 - try terms.appendSlice(aa, self.terms); │ │161 + if (self.terms.items.len == 0) return; │ │162 const postings = try aa.alloc(Posting, 1); │ │163 postings[0] = .{ .set = set }; │ │197 - try terms.append(aa, .{ .postings = postings }); │ │198 - self.terms = terms.items; │ │164 + try self.terms.append(aa, .{ .postings = postings }); │ │165 } │ │166 │ │167 // start the next result at `key` rather than the first one. │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │172 } │ │173 │ │174 pub fn next(self: *Self) !?[]const u8 { │ │209 - if (self.terms.len == 0) return null; │ │175 + if (self.terms.items.len == 0) return null; │ │176 if (self.returned) { │ │177 // doc keys are at most max_doc_key_len, so appending a byte│ │178 // lands strictly after the one just returned │ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯