╭ 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 // 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 }; 19 20 pub fn indexKey(comptime kind: evt.EventKind) []const u8 { 21 return switch (kind) { 22 .issue => "word->issue-id-set", 23 .patch => "word->patch-id-set", 24 .discuss => "word->discussion-id-set", 25 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, 38 }; 39 } 40 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, 47 comptime kind: evt.EventKind, 48 haxy_moment: DB.HashMap(.read_write), 49 allocator: std.mem.Allocator, 50 old: ?Doc, 51 new: ?Doc, 52 ) !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 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)))); 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 77 // it is keyed the same way 78 pub fn query( 79 comptime DB: type, 80 comptime hash_kind: hash.HashKind, 81 comptime kind: evt.EventKind, 82 haxy_moment: DB.HashMap(.read_only), 83 aa: std.mem.Allocator, 84 query_text: []const u8, 85 filter: ?DB.SortedSet(.read_only), 86 ) !srch.Query(DB) { 87 const cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, indexKey(kind))) orelse return .{ .terms = &.{} }; 88 const index = try DB.SortedMap(.read_only).init(cursor); 89 var results = try srch.Query(DB).init(index, aa, query_text); 90 if (filter) |set| try results.require(aa, set); 91 return results; 92 } 93 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 }); 98 } 99