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 }