╭ changed ────────────╮ │src/search_thread.zig│ ╰─────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │ 8 // carry them and every host that consumes events builds them. a doc key is the│ │ 9 // thread's creation order key, which never changes. issues and patches list in│ │10 // that order too, so their list sets filter a query by intersection. │ │11 - │ │12 -// one indexed thread: its doc key and the fields the index covers │ │13 -pub const Doc = struct { │ │14 - key: []const u8, │ │15 - title: []const u8, │ │16 - tags: []const u8, │ │17 - description: []const u8, │ │18 -}; │ │11 │ │12 pub fn indexKey(comptime kind: evt.EventKind) []const u8 { │ │13 return switch (kind) { │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │15 .patch => "word->patch-id-set", │ │16 .discuss => "word->discussion-id-set", │ │17 else => @compileError("no thread index for " ++ @tagName(kind)), │ │26 - }; │ │27 -} │ │28 - │ │29 -// `record`'s indexed side under `key`, or null when it is removed and so has│ │30 -// no postings │ │31 -pub fn doc(key: []const u8, record: anytype) ?Doc { │ │32 - if (record.removed) return null; │ │33 - return .{ │ │34 - .key = key, │ │35 - .title = record.event.title, │ │36 - .tags = record.event.tags, │ │37 - .description = record.event.description, │ │18 }; │ │19 } │ │20 │ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │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. │ │21 +// reindex the thread `key` names after `old_maybe` became `new`. an unchanged │ │22 +// doc costs nothing: a status change or an edit that leaves the indexed fields│ │23 +// alone returns here. │ │24 pub fn update( │ │25 comptime DB: type, │ │26 comptime hash_kind: hash.HashKind, │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────╮ │27 comptime kind: evt.EventKind, │ │28 haxy_moment: DB.HashMap(.read_write), │ │29 allocator: std.mem.Allocator, │ │50 - old: ?Doc, │ │51 - new: ?Doc, │ │30 + key: []const u8, │ │31 + old_maybe: anytype, │ │32 + new: anytype, │ │33 ) !void { │ │53 - if (old == null and new == null) return; │ │54 - if (old) |before| if (new) |after| { │ │55 - if (std.mem.eql(u8, before.key, after.key) and │ │56 - std.mem.eql(u8, before.title, after.title) and │ │57 - std.mem.eql(u8, before.tags, after.tags) and │ │58 - std.mem.eql(u8, before.description, after.description)) return;│ │59 - }; │ │60 - │ │34 var arena = std.heap.ArenaAllocator.init(allocator); │ │35 defer arena.deinit(); │ │36 const aa = arena.allocator(); │ ╰───────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │37 + const old_text = if (old_maybe) |old| try text(aa, old) else ""; │ │38 + const new_text = try text(aa, new); │ │39 + if (std.mem.eql(u8, old_text, new_text)) return; │ │40 const index = try DB.SortedMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, indexKey(kind))));│ │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 - } │ │41 + try srch.replace(DB, index, allocator, key, old_text, new_text); │ │42 } │ │43 │ │44 // the doc keys matching `query_text`, in key order, narrowed to `filter` when │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │52 query_text: []const u8, │ │53 filter: ?DB.SortedSet(.read_only), │ │54 ) !srch.Query(DB) { │ │87 - const cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, indexKey(kind))) orelse return .{ .terms = &.{} }; │ │55 + const cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, indexKey(kind))) orelse return .{ .terms = .empty };│ │56 const index = try DB.SortedMap(.read_only).init(cursor); │ │57 var results = try srch.Query(DB).init(index, aa, query_text); │ │58 if (filter) |set| try results.require(aa, set); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │59 return results; │ │60 } │ │61 │ │94 -// the text a thread is indexed under: the title and tags first, so the │ │95 -// tokenizer's caps can only drop the tail of a long description. │ │96 -fn text(aa: std.mem.Allocator, value: Doc) ![]const u8 { │ │97 - return std.mem.concat(aa, u8, &.{ value.title, " ", value.tags, " ", value.description }); │ │62 +// the text a thread is indexed under, none once it is removed: the title and │ │63 +// tags first, so the tokenizer's caps can only drop the tail of a long │ │64 +// description. │ │65 +fn text(aa: std.mem.Allocator, record: anytype) ![]const u8 { │ │66 + if (record.removed) return ""; │ │67 + return std.mem.concat(aa, u8, &.{ record.event.title, " ", record.event.tags, " ", record.event.description });│ │68 } │ │69 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯