╭ changed ─────╮ │src/search.zig│ ╰──────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────╮ │30 var arena = std.heap.ArenaAllocator.init(allocator); │ │31 defer arena.deinit(); │ │32 for (try tokenize(arena.allocator(), text)) |word| { │ │33 - const set = try DB.SortedSet(.read_write).init(try index.putCursor(word));│ │34 - try set.put(doc_key); │ │33 + try addWord(DB, index, doc_key, word); │ │34 } │ │35 } │ │36 │ ╰──────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 46 var arena = std.heap.ArenaAllocator.init(allocator); │ │ 47 defer arena.deinit(); │ │ 48 for (try tokenize(arena.allocator(), text)) |word| { │ │ 50 - if (null == try index.getCursor(word)) continue; │ │ 51 - const set = try DB.SortedSet(.read_write).init(try index.putCursor(word)); │ │ 52 - _ = try set.remove(doc_key); │ │ 53 - if (0 == try set.count()) _ = try index.remove(word); │ │ 49 + try removeWord(DB, index, doc_key, word); │ │ 50 + } │ │ 51 +} │ │ 52 + │ │ 53 +// move `doc_key` from `old_text`'s words to `new_text`'s. the words they share │ │ 54 +// are left alone, so editing a long description costs what the edit changed │ │ 55 +// rather than the whole text. │ │ 56 +pub fn replace( │ │ 57 + comptime DB: type, │ │ 58 + index: DB.SortedMap(.read_write), │ │ 59 + allocator: std.mem.Allocator, │ │ 60 + doc_key: []const u8, │ │ 61 + old_text: []const u8, │ │ 62 + new_text: []const u8, │ │ 63 +) !void { │ │ 64 + var arena = std.heap.ArenaAllocator.init(allocator); │ │ 65 + defer arena.deinit(); │ │ 66 + const aa = arena.allocator(); │ │ 67 + const old_words = try tokenize(aa, old_text); │ │ 68 + const new_words = try tokenize(aa, new_text); │ │ 69 + │ │ 70 + for (old_words) |word| { │ │ 71 + if (!contains(new_words, word)) try removeWord(DB, index, doc_key, word); │ │ 72 } │ │ 73 + for (new_words) |word| { │ │ 74 + if (!contains(old_words, word)) try addWord(DB, index, doc_key, word); │ │ 75 + } │ │ 76 } │ │ 77 │ │ 78 +// a document contributes at most max_words_per_doc words, so a scan costs less │ │ 79 +// than building a second map │ │ 80 +fn contains(words: []const []const u8, word: []const u8) bool { │ │ 81 + for (words) |candidate| { │ │ 82 + if (std.mem.eql(u8, candidate, word)) return true; │ │ 83 + } │ │ 84 + return false; │ │ 85 +} │ │ 86 + │ │ 87 +fn addWord(comptime DB: type, index: DB.SortedMap(.read_write), doc_key: []const u8, word: []const u8) !void { │ │ 88 + const set = try DB.SortedSet(.read_write).init(try index.putCursor(word)); │ │ 89 + try set.put(doc_key); │ │ 90 +} │ │ 91 + │ │ 92 +fn removeWord(comptime DB: type, index: DB.SortedMap(.read_write), doc_key: []const u8, word: []const u8) !void {│ │ 93 + if (null == try index.getCursor(word)) return; │ │ 94 + const set = try DB.SortedSet(.read_write).init(try index.putCursor(word)); │ │ 95 + _ = try set.remove(doc_key); │ │ 96 + if (0 == try set.count()) _ = try index.remove(word); │ │ 97 +} │ │ 98 + │ │ 99 // the doc keys holding every typed word, in key order. a stream, so callers │ │100 // stop at their page size. │ │101 pub fn Query(comptime DB: type) type { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────────────╮ │src/search_thread.zig│ ╰─────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │38 }; │ │39 } │ │40 │ │41 -// replace `old`'s postings with `new`'s. an unchanged doc costs nothing: a │ │42 -// status change or an edit that leaves the indexed fields alone returns here.│ │41 +// move `old`'s postings to `new`'s. an unchanged doc costs nothing: a status │ │42 +// change or an edit that leaves the indexed fields alone returns here. both │ │43 +// sides share a doc key, since a thread keeps the one it was created with. │ │44 pub fn update( │ │45 comptime DB: type, │ │46 comptime hash_kind: hash.HashKind, │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │60 │ │61 var arena = std.heap.ArenaAllocator.init(allocator); │ │62 defer arena.deinit(); │ │63 + const aa = arena.allocator(); │ │64 const index = try DB.SortedMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, indexKey(kind))));│ │63 - if (old) |before| try srch.remove(DB, index, allocator, before.key, try text(arena.allocator(), before)); │ │64 - if (new) |after| try srch.add(DB, index, allocator, after.key, try text(arena.allocator(), after)); │ │65 + if (old) |before| { │ │66 + if (new) |after| { │ │67 + try srch.replace(DB, index, allocator, after.key, try text(aa, before), try text(aa, after)); │ │68 + } else { │ │69 + try srch.remove(DB, index, allocator, before.key, try text(aa, before)); │ │70 + } │ │71 + } else if (new) |after| { │ │72 + try srch.add(DB, index, allocator, after.key, try text(aa, after)); │ │73 + } │ │74 } │ │75 │ │76 // the doc keys matching `query_text`, in key order, narrowed to `filter` when │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯