╭ changed ─────╮ │src/search.zig│ ╰──────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────╮ │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 { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯