lib/sql/src/profiling/search.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const bench = @import("bench");
  3 const coz = @import("coz");
  4 const sql = @import("sql");
  5 
  6 const search_document_count = 512;
  7 const search_query_repeats = 128;
  8 const search_large_document_count = 4_096;
  9 const search_large_query_repeats = 64;
 10 const search_rebuild_batch_size = 128;
 11 
 12 var phrase_pair_write_shape_reported = false;
 13 
 14 pub fn addTo(suite: *bench.Suite) !void {
 15     try suite.add("sql search rebuild putAll 512", searchRebuildPutAll, .{});
 16     try suite.add("sql search rebuild loadAll 512", searchRebuildLoadAll, .{});
 17     try suite.add("sql search rebuild loadAll 4096", searchRebuildLoadAllLarge, .{});
 18     try suite.add("sql search rebuild loadAll phrase-pair 4096", searchRebuildLoadAllPhrasePairsLarge, .{});
 19     try suite.add("sql search query warm 512x128", searchWarmQuery, .{});
 20     try suite.add("sql search query exact limit 512x128", searchWarmExactLimitQuery, .{});
 21     try suite.add("sql search query OR 512x128", searchWarmOrQuery, .{});
 22     try suite.add("sql search query OR varied limit 4096x64", searchWarmVariedOrLimitQuery, .{});
 23     try suite.add("sql search query ranked OR varied limit 4096x64", searchWarmRankedVariedOrLimitQuery, .{});
 24     try suite.add("sql search query ranked prefix 4096x64", searchWarmRankedPrefixLimitQuery, .{});
 25     try suite.add("sql search query ranked phrase 4096x64", searchWarmRankedPhraseLimitQuery, .{});
 26     try suite.add("sql search query prepared 512x128", searchWarmPreparedQuery, .{});
 27 }
 28 
 29 fn searchDocumentText(allocator: std.mem.Allocator, index: usize) []const u8 {
 30     return std.fmt.allocPrint(
 31         allocator,
 32         "issue-{d:0>4} sql cache rebuild forum smg tissue agent workflow priority-{d} body token-{d} native search query",
 33         .{ index, index % 5, index % 17 },
 34     ) catch @panic("search document text failed");
 35 }
 36 
 37 fn weightedSqlText(index: usize) []const u8 {
 38     return switch (index % 8) {
 39         0 => "sql",
 40         1 => "sql sql",
 41         2 => "sql sql sql",
 42         3 => "sql sql sql sql",
 43         4 => "sql sql sql sql sql",
 44         5 => "sql sql sql sql sql sql",
 45         6 => "sql sql sql sql sql sql sql",
 46         else => "sql sql sql sql sql sql sql sql",
 47     };
 48 }
 49 
 50 fn weightedCacheText(index: usize) []const u8 {
 51     return switch ((index / 3) % 5) {
 52         0 => "cache",
 53         1 => "cache cache",
 54         2 => "cache cache cache",
 55         3 => "cache cache cache cache",
 56         else => "cache cache cache cache cache",
 57     };
 58 }
 59 
 60 fn weightedSearchDocumentText(allocator: std.mem.Allocator, index: usize) []const u8 {
 61     return std.fmt.allocPrint(
 62         allocator,
 63         "issue-{d:0>4} {s} {s} rebuild forum smg tissue agent workflow priority-{d} body token-{d} native search query",
 64         .{ index, weightedSqlText(index), weightedCacheText(index), index % 5, index % 17 },
 65     ) catch @panic("weighted search document text failed");
 66 }
 67 
 68 fn rankedPrefixText(index: usize) []const u8 {
 69     if (index % 3 != 0) return "credential identity session policy";
 70     return switch ((index / 3) % 3) {
 71         0 => "authentication",
 72         1 => "authentication authentication",
 73         else => "authentication authentication authentication",
 74     };
 75 }
 76 
 77 fn rankedPhraseText(index: usize) []const u8 {
 78     if (index % 2 != 0) return "ranked scoring search engine";
 79     return switch ((index / 2) % 3) {
 80         0 => "ranked search",
 81         1 => "ranked search ranked search",
 82         else => "ranked search ranked search ranked search",
 83     };
 84 }
 85 
 86 fn rankedShapeSearchDocumentText(allocator: std.mem.Allocator, index: usize) []const u8 {
 87     return std.fmt.allocPrint(
 88         allocator,
 89         "issue-{d:0>4} {s} {s} sql cache forum smg tissue agent workflow priority-{d} body token-{d} native query",
 90         .{ index, rankedPrefixText(index), rankedPhraseText(index), index % 5, index % 17 },
 91     ) catch @panic("ranked shape search document text failed");
 92 }
 93 
 94 fn searchDocuments(allocator: std.mem.Allocator) []sql.SearchDocument {
 95     const documents = allocator.alloc(sql.SearchDocument, search_document_count) catch @panic("search documents failed");
 96     for (documents, 0..) |*document, index| {
 97         document.* = .{
 98             .rowid = @intCast(index + 1),
 99             .text = searchDocumentText(allocator, index),
100         };
101     }
102     return documents;
103 }
104 
105 fn weightedSearchDocuments(allocator: std.mem.Allocator, count: usize) []sql.SearchDocument {
106     const documents = allocator.alloc(sql.SearchDocument, count) catch @panic("weighted search documents failed");
107     for (documents, 0..) |*document, index| {
108         document.* = .{
109             .rowid = @intCast(index + 1),
110             .text = weightedSearchDocumentText(allocator, index),
111         };
112     }
113     return documents;
114 }
115 
116 fn rankedShapeSearchDocuments(allocator: std.mem.Allocator, count: usize) []sql.SearchDocument {
117     const documents = allocator.alloc(sql.SearchDocument, count) catch @panic("ranked shape search documents failed");
118     for (documents, 0..) |*document, index| {
119         document.* = .{
120             .rowid = @intCast(index + 1),
121             .text = rankedShapeSearchDocumentText(allocator, index),
122         };
123     }
124     return documents;
125 }
126 
127 fn freeSearchDocuments(allocator: std.mem.Allocator, documents: []sql.SearchDocument) void {
128     for (documents) |document| allocator.free(document.text);
129     allocator.free(documents);
130 }
131 
132 fn searchRebuildPutAll(sample_allocator: std.mem.Allocator) void {
133     var tmp = std.testing.tmpDir(.{});
134     defer tmp.cleanup();
135     const documents = searchDocuments(sample_allocator);
136     defer freeSearchDocuments(sample_allocator, documents);
137     var database = sql.FileDatabase.openForTesting(sample_allocator, tmp.dir, .{
138         .paths = .{ .database = "search.db", .wal = "search.wal" },
139         .header = .{
140             .sequence = 70,
141             .salt = .{ .first = 0x5155_4552, .second = 0x5950_5554 },
142         },
143     }) catch @panic("search putAll database open failed");
144     defer database.deinit();
145     var search = sql.Search.open(&database, .{ .index_prefixes = false }) catch @panic("search putAll open failed");
146     const phase = bench.phaseAt("sql.search.rebuild.put_all", @src());
147     phase.setName("search rebuild putAll 512");
148     defer phase.end();
149     var start: usize = 0;
150     while (start < documents.len) {
151         const end = @min(start + search_rebuild_batch_size, documents.len);
152         _ = search.putAll(sample_allocator, documents[start..end], .{ .durability = .buffered }) catch @panic("search putAll rebuild failed");
153         start = end;
154     }
155     database.syncWal() catch @panic("search putAll sync failed");
156     _ = database.checkpoint(.{ .restart_header = .{
157         .sequence = 71,
158         .salt = .{ .first = 0x5155_4552, .second = 0x5950_5554 },
159     } }) catch @panic("search putAll checkpoint failed");
160     std.mem.doNotOptimizeAway(database.wal_written + database.pager.frameCount());
161 }
162 
163 fn searchRebuildLoadAll(sample_allocator: std.mem.Allocator) void {
164     var tmp = std.testing.tmpDir(.{});
165     defer tmp.cleanup();
166     const documents = searchDocuments(sample_allocator);
167     defer freeSearchDocuments(sample_allocator, documents);
168     var database = sql.FileDatabase.openForTesting(sample_allocator, tmp.dir, .{
169         .paths = .{ .database = "search.db", .wal = "search.wal" },
170         .header = .{
171             .sequence = 72,
172             .salt = .{ .first = 0x5155_4552, .second = 0x4c4f_4144 },
173         },
174     }) catch @panic("search loadAll database open failed");
175     defer database.deinit();
176     var search = sql.Search.open(&database, .{ .index_prefixes = false }) catch @panic("search loadAll open failed");
177     const phase = bench.phaseAt("sql.search.rebuild.load_all", @src());
178     phase.setName("search rebuild loadAll 512");
179     defer phase.end();
180     _ = search.loadAll(sample_allocator, documents, .{ .durability = .buffered }) catch @panic("search loadAll rebuild failed");
181     database.syncWal() catch @panic("search loadAll sync failed");
182     _ = database.checkpoint(.{ .restart_header = .{
183         .sequence = 73,
184         .salt = .{ .first = 0x5155_4552, .second = 0x4c4f_4144 },
185     } }) catch @panic("search loadAll checkpoint failed");
186     std.mem.doNotOptimizeAway(database.wal_written + database.pager.frameCount());
187 }
188 
189 fn searchRebuildLoadAllLarge(sample_allocator: std.mem.Allocator) void {
190     var tmp = std.testing.tmpDir(.{});
191     defer tmp.cleanup();
192     const documents = weightedSearchDocuments(sample_allocator, search_large_document_count);
193     defer freeSearchDocuments(sample_allocator, documents);
194     var database = sql.FileDatabase.openForTesting(sample_allocator, tmp.dir, .{
195         .paths = .{ .database = "search.db", .wal = "search.wal" },
196         .header = .{
197             .sequence = 80,
198             .salt = .{ .first = 0x5155_4552, .second = 0x4c41_5247 },
199         },
200     }) catch @panic("search large loadAll database open failed");
201     defer database.deinit();
202     var search = sql.Search.open(&database, .{ .index_prefixes = false }) catch @panic("search large loadAll open failed");
203     const phase = bench.phaseAt("sql.search.rebuild.load_all_large", @src());
204     phase.setName("search rebuild loadAll 4096");
205     defer phase.end();
206     _ = search.loadAll(sample_allocator, documents, .{ .durability = .buffered }) catch @panic("search large loadAll rebuild failed");
207     coz.progressNamed("sql.search.rebuild.load_all_large.complete");
208     database.syncWal() catch @panic("search large loadAll sync failed");
209     _ = database.checkpoint(.{ .restart_header = .{
210         .sequence = 81,
211         .salt = .{ .first = 0x5155_4552, .second = 0x4c41_5247 },
212     } }) catch @panic("search large loadAll checkpoint failed");
213     std.mem.doNotOptimizeAway(database.wal_written + database.pager.frameCount());
214 }
215 
216 fn searchRebuildLoadAllPhrasePairsLarge(sample_allocator: std.mem.Allocator) void {
217     var tmp = std.testing.tmpDir(.{});
218     defer tmp.cleanup();
219     const documents = rankedShapeSearchDocuments(sample_allocator, search_large_document_count);
220     defer freeSearchDocuments(sample_allocator, documents);
221     var database = sql.FileDatabase.openForTesting(sample_allocator, tmp.dir, .{
222         .paths = .{ .database = "search.db", .wal = "search.wal" },
223         .header = .{
224             .sequence = 85,
225             .salt = .{ .first = 0x5155_4552, .second = 0x5050_4149 },
226         },
227     }) catch @panic("search phrase-pair loadAll database open failed");
228     defer database.deinit();
229     var search = sql.Search.open(&database, .{ .index_prefixes = false, .index_phrase_pairs = true }) catch @panic("search phrase-pair loadAll open failed");
230     const phase = bench.phaseAt("sql.search.rebuild.load_all_phrase_pair", @src());
231     phase.setName("search rebuild loadAll phrase-pair 4096");
232     defer phase.end();
233     _ = search.loadAll(sample_allocator, documents, .{ .durability = .buffered }) catch @panic("search phrase-pair loadAll rebuild failed");
234     coz.progressNamed("sql.search.rebuild.load_all_phrase_pair.complete");
235     reportPhrasePairWriteShape(sample_allocator, &search);
236     database.syncWal() catch @panic("search phrase-pair loadAll sync failed");
237     _ = database.checkpoint(.{ .restart_header = .{
238         .sequence = 86,
239         .salt = .{ .first = 0x5155_4552, .second = 0x5050_4149 },
240     } }) catch @panic("search phrase-pair loadAll checkpoint failed");
241     std.mem.doNotOptimizeAway(database.wal_written + database.pager.frameCount());
242 }
243 
244 fn reportPhrasePairWriteShape(allocator: std.mem.Allocator, search: *const sql.Search) void {
245     if (phrase_pair_write_shape_reported) return;
246     const stats = search.postingStats(allocator) catch @panic("search phrase-pair posting stats failed");
247     bench.stdout(
248         "sql.search.phrase_pair_write_shape entries={d} terms={d} postings={d} direct={d} segments={d} segment_postings={d} payload_bytes={d} key_text_bytes={d} term_key_text_bytes={d} phrase_terms={d} phrase_postings={d} phrase_direct={d} phrase_segments={d} phrase_segment_postings={d} phrase_payload_bytes={d} phrase_key_text_bytes={d} phrase_term_key_text_bytes={d} length_segments={d} length_postings={d}\n",
249         .{
250             stats.term_index_entries,
251             stats.posting_term_keys,
252             stats.livePostingEntries(),
253             stats.direct_posting_entries,
254             stats.segment_entries,
255             stats.segment_postings,
256             stats.posting_payload_bytes,
257             stats.posting_key_text_bytes,
258             stats.posting_term_key_text_bytes,
259             stats.phrase_pair_term_keys,
260             stats.phrasePairLivePostingEntries(),
261             stats.phrase_pair_direct_posting_entries,
262             stats.phrase_pair_segment_entries,
263             stats.phrase_pair_segment_postings,
264             stats.phrase_pair_posting_payload_bytes,
265             stats.phrase_pair_posting_key_text_bytes,
266             stats.phrase_pair_term_key_text_bytes,
267             stats.document_length_segment_entries,
268             stats.document_length_segment_postings,
269         },
270     );
271     phrase_pair_write_shape_reported = true;
272 }
273 
274 fn searchWarmQuery(sample_allocator: std.mem.Allocator) void {
275     var tmp = std.testing.tmpDir(.{});
276     defer tmp.cleanup();
277     const documents = searchDocuments(sample_allocator);
278     defer freeSearchDocuments(sample_allocator, documents);
279     var database = sql.FileDatabase.openForTesting(sample_allocator, tmp.dir, .{
280         .paths = .{ .database = "search.db", .wal = "search.wal" },
281         .header = .{
282             .sequence = 74,
283             .salt = .{ .first = 0x5155_4552, .second = 0x5741_524d },
284         },
285     }) catch @panic("search warm database open failed");
286     defer database.deinit();
287     var search = sql.Search.open(&database, .{ .index_prefixes = false }) catch @panic("search warm open failed");
288     _ = search.loadAll(sample_allocator, documents, .{ .durability = .buffered }) catch @panic("search warm load failed");
289     database.syncWal() catch @panic("search warm sync failed");
290     var checksum: usize = 0;
291     const phase = bench.phaseAt("sql.search.query.warm", @src());
292     phase.setName("search query warm 512x128");
293     defer phase.end();
294     var repeat: usize = 0;
295     while (repeat < search_query_repeats) : (repeat += 1) {
296         var results = search.query(sample_allocator, "sql AND cache", 20) catch @panic("search warm query failed");
297         defer results.deinit();
298         for (results.hits) |hit| checksum +%= @as(usize, @intCast(hit.rowid)) + hit.score;
299     }
300     std.mem.doNotOptimizeAway(checksum);
301 }
302 
303 fn searchWarmExactLimitQuery(sample_allocator: std.mem.Allocator) void {
304     var tmp = std.testing.tmpDir(.{});
305     defer tmp.cleanup();
306     const documents = searchDocuments(sample_allocator);
307     defer freeSearchDocuments(sample_allocator, documents);
308     var database = sql.FileDatabase.openForTesting(sample_allocator, tmp.dir, .{
309         .paths = .{ .database = "search.db", .wal = "search.wal" },
310         .header = .{
311             .sequence = 77,
312             .salt = .{ .first = 0x5155_4552, .second = 0x4c49_4d49 },
313         },
314     }) catch @panic("search exact limit database open failed");
315     defer database.deinit();
316     var search = sql.Search.open(&database, .{ .index_prefixes = false }) catch @panic("search exact limit open failed");
317     _ = search.loadAll(sample_allocator, documents, .{ .durability = .buffered }) catch @panic("search exact limit load failed");
318     database.syncWal() catch @panic("search exact limit sync failed");
319     var checksum: usize = 0;
320     const phase = bench.phaseAt("sql.search.query.exact_limit", @src());
321     phase.setName("search query exact limit 512x128");
322     defer phase.end();
323     var repeat: usize = 0;
324     while (repeat < search_query_repeats) : (repeat += 1) {
325         var results = search.query(sample_allocator, "native", 20) catch @panic("search exact limit query failed");
326         defer results.deinit();
327         std.debug.assert(results.total == search_document_count);
328         std.debug.assert(results.hits.len == 20);
329         for (results.hits) |hit| checksum +%= @as(usize, @intCast(hit.rowid)) + hit.score;
330     }
331     std.mem.doNotOptimizeAway(checksum);
332 }
333 
334 fn searchWarmOrQuery(sample_allocator: std.mem.Allocator) void {
335     var tmp = std.testing.tmpDir(.{});
336     defer tmp.cleanup();
337     const documents = searchDocuments(sample_allocator);
338     defer freeSearchDocuments(sample_allocator, documents);
339     var database = sql.FileDatabase.openForTesting(sample_allocator, tmp.dir, .{
340         .paths = .{ .database = "search.db", .wal = "search.wal" },
341         .header = .{
342             .sequence = 78,
343             .salt = .{ .first = 0x5155_4552, .second = 0x4f52_4f52 },
344         },
345     }) catch @panic("search OR database open failed");
346     defer database.deinit();
347     var search = sql.Search.open(&database, .{ .index_prefixes = false }) catch @panic("search OR open failed");
348     _ = search.loadAll(sample_allocator, documents, .{ .durability = .buffered }) catch @panic("search OR load failed");
349     database.syncWal() catch @panic("search OR sync failed");
350     var checksum: usize = 0;
351     const phase = bench.phaseAt("sql.search.query.or", @src());
352     phase.setName("search query OR 512x128");
353     defer phase.end();
354     var repeat: usize = 0;
355     while (repeat < search_query_repeats) : (repeat += 1) {
356         var results = search.query(sample_allocator, "sql OR cache", 20) catch @panic("search OR query failed");
357         defer results.deinit();
358         std.debug.assert(results.total == search_document_count);
359         std.debug.assert(results.hits.len == 20);
360         for (results.hits) |hit| checksum +%= @as(usize, @intCast(hit.rowid)) + hit.score;
361     }
362     std.mem.doNotOptimizeAway(checksum);
363 }
364 
365 fn searchWarmVariedOrLimitQuery(sample_allocator: std.mem.Allocator) void {
366     var tmp = std.testing.tmpDir(.{});
367     defer tmp.cleanup();
368     const documents = weightedSearchDocuments(sample_allocator, search_large_document_count);
369     defer freeSearchDocuments(sample_allocator, documents);
370     var database = sql.FileDatabase.openForTesting(sample_allocator, tmp.dir, .{
371         .paths = .{ .database = "search.db", .wal = "search.wal" },
372         .header = .{
373             .sequence = 79,
374             .salt = .{ .first = 0x5155_4552, .second = 0x424f_554e },
375         },
376     }) catch @panic("search large OR database open failed");
377     defer database.deinit();
378     var search = sql.Search.open(&database, .{ .index_prefixes = false }) catch @panic("search large OR open failed");
379     _ = search.loadAll(sample_allocator, documents, .{ .durability = .buffered }) catch @panic("search large OR load failed");
380     database.syncWal() catch @panic("search large OR sync failed");
381     var checksum: usize = 0;
382     const phase = bench.phaseAt("sql.search.query.or_varied_limit", @src());
383     phase.setName("search query OR varied limit 4096x64");
384     defer phase.end();
385     var repeat: usize = 0;
386     while (repeat < search_large_query_repeats) : (repeat += 1) {
387         var results = search.query(sample_allocator, "sql OR cache", 20) catch @panic("search large OR query failed");
388         defer results.deinit();
389         std.debug.assert(results.total == search_large_document_count);
390         std.debug.assert(results.hits.len == 20);
391         for (results.hits) |hit| checksum +%= @as(usize, @intCast(hit.rowid)) + hit.score;
392     }
393     std.mem.doNotOptimizeAway(checksum);
394 }
395 
396 fn searchWarmRankedVariedOrLimitQuery(sample_allocator: std.mem.Allocator) void {
397     var tmp = std.testing.tmpDir(.{});
398     defer tmp.cleanup();
399     const documents = weightedSearchDocuments(sample_allocator, search_large_document_count);
400     defer freeSearchDocuments(sample_allocator, documents);
401     var database = sql.FileDatabase.openForTesting(sample_allocator, tmp.dir, .{
402         .paths = .{ .database = "search.db", .wal = "search.wal" },
403         .header = .{
404             .sequence = 82,
405             .salt = .{ .first = 0x5155_4552, .second = 0x5241_4e4b },
406         },
407     }) catch @panic("search ranked large OR database open failed");
408     defer database.deinit();
409     var search = sql.Search.open(&database, .{ .index_prefixes = false }) catch @panic("search ranked large OR open failed");
410     _ = search.loadAll(sample_allocator, documents, .{ .durability = .buffered }) catch @panic("search ranked large OR load failed");
411     database.syncWal() catch @panic("search ranked large OR sync failed");
412     var checksum: u64 = 0;
413     const phase = bench.phaseAt("sql.search.query.ranked_or_varied_limit", @src());
414     phase.setName("search query ranked OR varied limit 4096x64");
415     defer phase.end();
416     var repeat: usize = 0;
417     while (repeat < search_large_query_repeats) : (repeat += 1) {
418         var results = search.queryRanked(sample_allocator, "sql OR cache", 20, .{}) catch @panic("search ranked large OR query failed");
419         defer results.deinit();
420         std.debug.assert(results.total == search_large_document_count);
421         std.debug.assert(results.hits.len == 20);
422         for (results.hits) |hit| checksum +%= @as(u64, @intCast(hit.rowid)) + @as(u64, @bitCast(hit.rank));
423     }
424     std.mem.doNotOptimizeAway(checksum);
425 }
426 
427 fn searchWarmRankedPrefixLimitQuery(sample_allocator: std.mem.Allocator) void {
428     var tmp = std.testing.tmpDir(.{});
429     defer tmp.cleanup();
430     const documents = rankedShapeSearchDocuments(sample_allocator, search_large_document_count);
431     defer freeSearchDocuments(sample_allocator, documents);
432     var database = sql.FileDatabase.openForTesting(sample_allocator, tmp.dir, .{
433         .paths = .{ .database = "search.db", .wal = "search.wal" },
434         .header = .{
435             .sequence = 83,
436             .salt = .{ .first = 0x5155_4552, .second = 0x5052_4658 },
437         },
438     }) catch @panic("search ranked prefix database open failed");
439     defer database.deinit();
440     var search = sql.Search.open(&database, .{ .index_prefixes = false }) catch @panic("search ranked prefix open failed");
441     _ = search.loadAll(sample_allocator, documents, .{ .durability = .buffered }) catch @panic("search ranked prefix load failed");
442     database.syncWal() catch @panic("search ranked prefix sync failed");
443     var checksum: u64 = 0;
444     const phase = bench.phaseAt("sql.search.query.ranked_prefix_limit", @src());
445     phase.setName("search query ranked prefix 4096x64");
446     defer phase.end();
447     var repeat: usize = 0;
448     while (repeat < search_large_query_repeats) : (repeat += 1) {
449         var results = search.queryRanked(sample_allocator, "auth*", 20, .{}) catch @panic("search ranked prefix query failed");
450         defer results.deinit();
451         std.debug.assert(results.total == (search_large_document_count + 2) / 3);
452         std.debug.assert(results.hits.len == 20);
453         for (results.hits) |hit| checksum +%= @as(u64, @intCast(hit.rowid)) + @as(u64, @bitCast(hit.rank));
454     }
455     std.mem.doNotOptimizeAway(checksum);
456 }
457 
458 fn searchWarmRankedPhraseLimitQuery(sample_allocator: std.mem.Allocator) void {
459     var tmp = std.testing.tmpDir(.{});
460     defer tmp.cleanup();
461     const documents = rankedShapeSearchDocuments(sample_allocator, search_large_document_count);
462     defer freeSearchDocuments(sample_allocator, documents);
463     var database = sql.FileDatabase.openForTesting(sample_allocator, tmp.dir, .{
464         .paths = .{ .database = "search.db", .wal = "search.wal" },
465         .header = .{
466             .sequence = 84,
467             .salt = .{ .first = 0x5155_4552, .second = 0x5048_5253 },
468         },
469     }) catch @panic("search ranked phrase database open failed");
470     defer database.deinit();
471     var search = sql.Search.open(&database, .{ .index_prefixes = false, .index_phrase_pairs = true }) catch @panic("search ranked phrase open failed");
472     _ = search.loadAll(sample_allocator, documents, .{ .durability = .buffered }) catch @panic("search ranked phrase load failed");
473     database.syncWal() catch @panic("search ranked phrase sync failed");
474     var checksum: u64 = 0;
475     const phase = bench.phaseAt("sql.search.query.ranked_phrase_limit", @src());
476     phase.setName("search query ranked phrase 4096x64");
477     defer phase.end();
478     var repeat: usize = 0;
479     while (repeat < search_large_query_repeats) : (repeat += 1) {
480         var results = search.queryRanked(sample_allocator, "\"ranked search\"", 20, .{}) catch @panic("search ranked phrase query failed");
481         defer results.deinit();
482         std.debug.assert(results.total == (search_large_document_count + 1) / 2);
483         std.debug.assert(results.hits.len == 20);
484         for (results.hits) |hit| checksum +%= @as(u64, @intCast(hit.rowid)) + @as(u64, @bitCast(hit.rank));
485     }
486     std.mem.doNotOptimizeAway(checksum);
487 }
488 
489 fn searchWarmPreparedQuery(sample_allocator: std.mem.Allocator) void {
490     var tmp = std.testing.tmpDir(.{});
491     defer tmp.cleanup();
492     const documents = searchDocuments(sample_allocator);
493     defer freeSearchDocuments(sample_allocator, documents);
494     var database = sql.FileDatabase.openForTesting(sample_allocator, tmp.dir, .{
495         .paths = .{ .database = "search.db", .wal = "search.wal" },
496         .header = .{
497             .sequence = 76,
498             .salt = .{ .first = 0x5155_4552, .second = 0x5052_4550 },
499         },
500     }) catch @panic("search prepared database open failed");
501     defer database.deinit();
502     var search = sql.Search.open(&database, .{ .index_prefixes = false }) catch @panic("search prepared open failed");
503     _ = search.loadAll(sample_allocator, documents, .{ .durability = .buffered }) catch @panic("search prepared load failed");
504     database.syncWal() catch @panic("search prepared sync failed");
505     var prepared = search.prepare(sample_allocator, "sql AND cache") catch @panic("search prepared parse failed");
506     defer prepared.deinit(sample_allocator);
507     var checksum: usize = 0;
508     const phase = bench.phaseAt("sql.search.query.prepared", @src());
509     phase.setName("search query prepared 512x128");
510     defer phase.end();
511     var repeat: usize = 0;
512     while (repeat < search_query_repeats) : (repeat += 1) {
513         var results = prepared.execute(&search, sample_allocator, 20) catch @panic("search prepared query failed");
514         defer results.deinit();
515         for (results.hits) |hit| checksum +%= @as(usize, @intCast(hit.rowid)) + hit.score;
516     }
517     std.mem.doNotOptimizeAway(checksum);
518 }