╭ search_thread.zig ────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯
1 const std = @import("std"); 2 const evt = @import("event.zig"); 3 const srch = @import("search.zig"); 4 const xit = @import("xit"); 5 const hash = xit.hash; 6 7 // the thread search indexes: one per kind, inside the haxy moment, so events 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 pub fn indexKey(comptime kind: evt.EventKind) []const u8 { 13 return switch (kind) { 14 .issue => "word->issue-id-set", 15 .patch => "word->patch-id-set", 16 .discuss => "word->discussion-id-set", 17 else => @compileError("no thread index for " ++ @tagName(kind)), 18 }; 19 } 20 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, 30 key: []const u8, 31 old_maybe: anytype, 32 new: anytype, 33 ) !void { 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)))); 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 each non-null 45 // set in `filters`, which must be keyed the same way 46 pub fn query( 47 comptime DB: type, 48 comptime hash_kind: hash.HashKind, 49 comptime kind: evt.EventKind, 50 haxy_moment: DB.HashMap(.read_only), 51 aa: std.mem.Allocator, 52 query_text: []const u8, 53 filters: []const ?DB.SortedSet(.read_only), 54 ) !srch.Query(DB) { 55 // no index means no words match, which one empty term expresses 56 var results: srch.Query(DB) = .{ .terms = .empty }; 57 if (try haxy_moment.getCursor(hash.hashInt(hash_kind, indexKey(kind)))) |cursor| { 58 results = try srch.Query(DB).init(try DB.SortedMap(.read_only).init(cursor), aa, query_text); 59 } else { 60 try results.terms.append(aa, .{ .postings = &.{} }); 61 } 62 for (filters) |filter| if (filter) |set| try results.require(aa, set); 63 return results; 64 } 65 66 // the text a thread is indexed under, none once it is removed: the title and 67 // labels first, so the tokenizer's caps can only drop the tail of a long 68 // description. 69 fn text(aa: std.mem.Allocator, record: anytype) ![]const u8 { 70 if (record.removed) return ""; 71 return std.mem.concat(aa, u8, &.{ record.event.title, " ", record.event.labels, " ", record.event.description }); 72 } 73