tiny.sql.Search
Defined in tiny.sql.
API (27)
Actions
Public operations.
advanceSourcecapabilitiescleardeletegetTextindexedSourceloadAllloadAllForSourceloadAllNewopenpostingStatsprepareputputAllputForSourcequeryqueryRankedqueryScoreBandsourceMatchessourceSchemaMatches
Fields and members
Public fields and members.
Source
Source: lib/sql/src/search/engine.zig:119
zig
pub const Search = struct { space: space_mod.Space, documents: table_mod.Table, terms: index_mod.Index, index_prefixes: bool, index_phrase_pairs: bool, omit_phrase_pair_singletons: bool, field_names: []const []const u8, pub fn open(database: *file.Database, options: Options) Error!Search { const roots = [_]space_mod.RootSpec{ .{ .root_page = options.documents_root }, .{ .root_page = options.terms_root }, }; const space = try space_mod.Space.open(database, .{ .meta_page = options.meta_page, .roots = &roots, .reserved_page_max = options.reserved_page_max, }); return .{ .space = space, .documents = try space.rowidTable(options.documents_root), .terms = try space.index(options.terms_root, &.{.{ .collation = .binary }}), .index_prefixes = options.index_prefixes, .index_phrase_pairs = options.index_phrase_pairs, .omit_phrase_pair_singletons = options.omit_phrase_pair_singletons, .field_names = options.field_names, }; } pub fn put(self: *Search, allocator: Allocator, rowid: i64, text: []const u8, options: file.CommitOptions) Error!file.Commit { const phase = trace.scope("search.put"); defer phase.end(); var write = try self.space.beginWrite(); defer write.deinit(); try self.putIn(&write, allocator, rowid, text); return try write.commit(options); } pub fn putForSource( self: *Search, allocator: Allocator, rowid: i64, text: []const u8, expected: Source, next: Source, options: file.CommitOptions, ) Error!?file.Commit { const phase = trace.scope("search.put_for_source"); defer phase.end(); if (!try self.sourceMatches(allocator, expected)) return null; var write = try self.space.beginWrite(); defer write.deinit(); try self.putIn(&write, allocator, rowid, text); try self.putSourceIn(&write, next); return try write.commit(options); } pub fn advanceSource(self: *Search, allocator: Allocator, expected: Source, next: Source, options: file.CommitOptions) Error!?file.Commit { const phase = trace.scope("search.advance_source"); defer phase.end(); if (!try self.sourceMatches(allocator, expected)) return null; var write = try self.space.beginWrite(); defer write.deinit(); try self.putSourceIn(&write, next); return try write.commit(options); } pub fn putAll(self: *Search, allocator: Allocator, documents: []const Document, options: file.CommitOptions) Error!file.Commit { const phase = trace.scope("search.put_all"); defer phase.end(); var write = try self.space.beginWrite(); defer write.deinit(); var stats = try self.corpusStatsOrEmpty(allocator); try self.ensureRequestedCapabilitiesIn(&write, allocator, stats); for (documents) |document| { const indexed_text = token_mod.documentText(document.text); const old = try self.documents.get(allocator, document.rowid); defer if (old) |bytes| allocator.free(bytes); if (old) |bytes| { try self.deletePostingsIn(&write, allocator, document.rowid, try token_mod.textFromRow(bytes)); const old_length = try self.documentLength(allocator, document.rowid); try self.deleteDocumentLengthIn(&write, document.rowid); stats = try posting_mod.subtractDocumentStats(stats, old_length); } try self.putTextIn(&write, allocator, document.rowid, indexed_text); try self.putPostingsIn(&write, allocator, document.rowid, indexed_text); const length = token_mod.documentTokenLength(indexed_text); try self.putDocumentLengthIn(&write, document.rowid, length); stats = try posting_mod.addDocumentStats(stats, length); } try self.putCorpusStatsIn(&write, stats); return try write.commit(options); } pub fn loadAll(self: *Search, allocator: Allocator, documents: []const Document, options: file.CommitOptions) Error!file.Commit { const phase = trace.scope("search.load_all"); defer phase.end(); return try self.loadAllWithSource(allocator, documents, null, options); } pub fn loadAllForSource(self: *Search, allocator: Allocator, documents: []const Document, source_value: Source, options: file.CommitOptions) Error!file.Commit { const phase = trace.scope("search.load_all_for_source"); defer phase.end(); return try self.loadAllWithSource(allocator, documents, source_value, options); } fn loadAllWithSource(self: *Search, allocator: Allocator, documents: []const Document, source_value: ?Source, options: file.CommitOptions) Error!file.Commit { var postings = try self.collectPostings(allocator, documents); var postings_live = true; defer if (postings_live) postings.deinit(allocator); std.mem.sort(posting_mod.Posting, postings.items, postings.keys, posting_mod.postingLess); var write = try self.space.beginWrite(); defer write.deinit(); try self.clearIn(&write, allocator); try self.putRequestedCapabilitiesIn(&write); try self.putSortedPostingsIn(&write, allocator, postings.items, postings.keys); postings.deinit(allocator); postings_live = false; var stats: posting_mod.CorpusStats = .{ .documents = 0, .total_tokens = 0 }; { var lengths: std.ArrayList(posting_mod.DocumentLength) = .empty; defer lengths.deinit(allocator); for (documents) |document| { const indexed_text = token_mod.documentText(document.text); try self.putTextIn(&write, allocator, document.rowid, indexed_text); const length = token_mod.documentTokenLength(indexed_text); try lengths.append(allocator, .{ .rowid = document.rowid, .length = length }); stats = try posting_mod.addDocumentStats(stats, length); } try self.putSortedDocumentLengthsIn(&write, allocator, lengths.items); } try self.putCorpusStatsIn(&write, stats); if (source_value) |value| try self.putSourceIn(&write, value); return try write.commit(options); } pub fn loadAllNew(self: *Search, allocator: Allocator, documents: []const Document, options: file.CommitOptions) Error!file.Commit { const phase = trace.scope("search.load_all_new"); defer phase.end(); var postings = try self.collectPostings(allocator, documents); var postings_live = true; defer if (postings_live) postings.deinit(allocator); std.mem.sort(posting_mod.Posting, postings.items, postings.keys, posting_mod.postingLess); var write = try self.space.beginWrite(); defer write.deinit(); var stats = try self.corpusStatsOrEmpty(allocator); try self.ensureRequestedCapabilitiesIn(&write, allocator, stats); try self.putSortedPostingsIn(&write, allocator, postings.items, postings.keys); postings.deinit(allocator); postings_live = false; var lengths: std.ArrayList(posting_mod.DocumentLength) = .empty; defer lengths.deinit(allocator); for (documents) |document| { const indexed_text = token_mod.documentText(document.text); try self.putTextIn(&write, allocator, document.rowid, indexed_text); const length = token_mod.documentTokenLength(indexed_text); try lengths.append(allocator, .{ .rowid = document.rowid, .length = length }); stats = try posting_mod.addDocumentStats(stats, length); } try self.putSortedDocumentLengthsIn(&write, allocator, lengths.items); try self.putCorpusStatsIn(&write, stats); return try write.commit(options); } pub fn clear(self: *Search, allocator: Allocator, options: file.CommitOptions) Error!file.Commit { const phase = trace.scope("search.clear"); defer phase.end(); var write = try self.space.beginWrite(); defer write.deinit(); try self.clearIn(&write, allocator); try self.putRequestedCapabilitiesIn(&write); return try write.commit(options); } pub fn delete(self: *Search, allocator: Allocator, rowid: i64, options: file.CommitOptions) Error!file.Commit { const phase = trace.scope("search.delete"); defer phase.end(); const old = (try self.documents.get(allocator, rowid)) orelse return error.KeyNotFound; defer allocator.free(old); var write = try self.space.beginWrite(); defer write.deinit(); var stats = try self.corpusStatsOrEmpty(allocator); try self.ensureRequestedCapabilitiesIn(&write, allocator, stats); try self.deletePostingsIn(&write, allocator, rowid, try token_mod.textFromRow(old)); const old_length = try self.documentLength(allocator, rowid); try self.deleteDocumentLengthIn(&write, rowid); stats = try posting_mod.subtractDocumentStats(stats, old_length); try self.putCorpusStatsIn(&write, stats); try self.documents.deleteIn(&write, rowid); return try write.commit(options); } fn getDocumentText(self: anytype, allocator: Allocator, rowid: i64) Error!?text_mod.DocumentText { const bytes = (try self.documents.get(allocator, rowid)) orelse return null; errdefer allocator.free(bytes); return .{ .bytes = bytes, .text = try token_mod.textFromRow(bytes), }; } pub fn getText(self: anytype, allocator: Allocator, rowid: i64) Error!?[]u8 { var document = (try self.getDocumentText(allocator, rowid)) orelse return null; defer document.deinit(allocator); return try allocator.dupe(u8, document.text); } pub fn capabilities(self: anytype, allocator: Allocator) Error!posting_mod.Capabilities { return try self.indexCapabilities(allocator); } pub fn indexedSource(self: anytype, allocator: Allocator) Error!?Source { var lookup: index_mod.Scan = undefined; try self.terms.lookupPayloads(&lookup, allocator, &posting_mod.sourceValues()); defer lookup.deinit(); const entry = (try lookup.next()) orelse return null; if (entry.rowid != posting_mod.internal_meta_rowid) return error.InvalidSearchIndex; const value = try posting_mod.sourcePayloadValue(entry.payload); if (try lookup.next() != null) return error.InvalidSearchIndex; return value; } pub fn sourceMatches(self: anytype, allocator: Allocator, expected: Source) Error!bool { const current = (try self.indexedSource(allocator)) orelse return false; return current.same(expected); } pub fn sourceSchemaMatches(self: anytype, allocator: Allocator, schema: u64) Error!bool { const current = (try self.indexedSource(allocator)) orelse return false; return current.schema == schema; } pub fn postingStats(self: anytype, allocator: Allocator) Error!posting_mod.PostingStats { var scan: index_mod.Scan = undefined; try self.terms.scanPayloads(&scan, allocator, null, null); defer scan.deinit(); var stats: posting_mod.PostingStats = .{}; var previous_key: [posting_mod.phrase_pair_posting_key_max_bytes]u8 = undefined; var previous_key_len: usize = 0; var have_previous_key = false; var values: [1]row.Value = undefined; var scratch: [page.size]u8 = undefined; while (try scan.next()) |entry| { stats.term_index_entries += 1; stats.payload_bytes += entry.payload.len; const decoded = try key_mod.decodeIndex(&values, &scratch, entry.key); if (decoded.values.len != 1) return error.InvalidSearchIndex; const key_text = switch (decoded.values[0]) { .text => |text| text, else => return error.InvalidSearchIndex, }; if (std.mem.eql(u8, key_text, posting_mod.document_length_key)) { stats.document_length_entries += 1; if (posting_mod.lengthSegmentPayloadCount(entry.payload)) |count| { stats.document_length_segment_entries += 1; stats.document_length_segment_postings += count; } else { _ = try posting_mod.documentLengthPayloadMaybe(entry.payload); stats.document_length_direct_entries += 1; } continue; } if (std.mem.eql(u8, key_text, posting_mod.corpus_stats_key) or std.mem.eql(u8, key_text, posting_mod.capabilities_key) or std.mem.eql(u8, key_text, posting_mod.source_key)) { stats.metadata_entries += 1; continue; } if (key_text.len == 0 or key_text.len > previous_key.len) return error.InvalidSearchIndex; if (!have_previous_key or !std.mem.eql(u8, previous_key[0..previous_key_len], key_text)) { stats.posting_term_keys += 1; stats.posting_term_key_text_bytes += key_text.len; switch (key_text[0]) { '=' => { stats.exact_term_keys += 1; stats.exact_term_key_text_bytes += key_text.len; }, '*' => { stats.prefix_term_keys += 1; stats.prefix_term_key_text_bytes += key_text.len; }, posting_mod.phrase_pair_posting_kind => { stats.phrase_pair_term_keys += 1; stats.phrase_pair_term_key_text_bytes += key_text.len; }, else => return error.InvalidSearchIndex, } @memcpy(previous_key[0..key_text.len], key_text); previous_key_len = key_text.len; have_previous_key = true; } stats.posting_entries += 1; stats.posting_payload_bytes += entry.payload.len; stats.posting_key_text_bytes += key_text.len; const phrase_pair = key_text[0] == posting_mod.phrase_pair_posting_kind; switch (key_text[0]) { '=' => { stats.exact_posting_key_text_bytes += key_text.len; stats.exact_posting_payload_bytes += entry.payload.len; }, '*' => { stats.prefix_posting_key_text_bytes += key_text.len; stats.prefix_posting_payload_bytes += entry.payload.len; }, posting_mod.phrase_pair_posting_kind => { stats.phrase_pair_posting_key_text_bytes += key_text.len; stats.phrase_pair_posting_payload_bytes += entry.payload.len; }, else => return error.InvalidSearchIndex, } if (posting_mod.segmentPayloadCount(entry.payload)) |count| { stats.segment_entries += 1; stats.segment_postings += count; if (phrase_pair) { stats.phrase_pair_segment_entries += 1; stats.phrase_pair_segment_postings += count; } } else { const count = try posting_mod.postingPayloadCount(entry.payload); if (count == 0) { stats.direct_posting_tombstones += 1; if (phrase_pair) stats.phrase_pair_direct_posting_tombstones += 1; } else { stats.direct_posting_entries += 1; if (phrase_pair) stats.phrase_pair_direct_posting_entries += 1; } } } return stats; } fn putIn(self: *Search, write: *tree.Write, allocator: Allocator, rowid: i64, text: []const u8) Error!void { const indexed_text = token_mod.documentText(text); const old = try self.documents.get(allocator, rowid); defer if (old) |bytes| allocator.free(bytes); var stats = try self.corpusStatsOrEmpty(allocator); try self.ensureRequestedCapabilitiesIn(write, allocator, stats); if (old) |bytes| { const old_text = try token_mod.textFromRow(bytes); if (std.mem.eql(u8, old_text, indexed_text)) return; const old_length = try self.documentLength(allocator, rowid); try self.replacePostingsIn(write, allocator, rowid, old_text, indexed_text); try self.putTextIn(write, allocator, rowid, indexed_text); const length = token_mod.documentTokenLength(indexed_text); if (old_length != length) { try self.putDocumentLengthIn(write, rowid, length); stats = try posting_mod.subtractDocumentStats(stats, old_length); stats = try posting_mod.addDocumentStats(stats, length); try self.putCorpusStatsIn(write, stats); } return; } try self.putTextIn(write, allocator, rowid, indexed_text); try self.putPostingsIn(write, allocator, rowid, indexed_text); const length = token_mod.documentTokenLength(indexed_text); try self.putDocumentLengthIn(write, rowid, length); stats = try posting_mod.addDocumentStats(stats, length); try self.putCorpusStatsIn(write, stats); } fn requestedCapabilities(self: anytype) posting_mod.Capabilities { return .{ .phrase_pair_postings = self.index_phrase_pairs, .phrase_pair_singletons_omitted = self.index_phrase_pairs and self.omit_phrase_pair_singletons, }; } fn indexCapabilities(self: anytype, allocator: Allocator) Error!posting_mod.Capabilities { var lookup: index_mod.Scan = undefined; try self.terms.lookupPayloads(&lookup, allocator, &posting_mod.capabilitiesValues()); defer lookup.deinit(); const entry = (try lookup.next()) orelse return .{}; if (entry.rowid != posting_mod.internal_meta_rowid) return error.InvalidSearchIndex; const value = try posting_mod.capabilitiesPayloadValue(entry.payload); if (try lookup.next() != null) return error.InvalidSearchIndex; return value; } fn canUsePhrasePairPostings(self: anytype, allocator: Allocator) Error!bool { if (!self.index_phrase_pairs) return false; return (try self.indexCapabilities(allocator)).phrase_pair_postings; } fn phrasePairPostingsArePartial(self: anytype, allocator: Allocator) Error!bool { if (!self.index_phrase_pairs) return false; return (try self.indexCapabilities(allocator)).phrase_pair_singletons_omitted; } fn ensureRequestedCapabilitiesIn(self: *Search, write: *tree.Write, allocator: Allocator, stats: posting_mod.CorpusStats) Error!void { const requested = self.requestedCapabilities(); if (posting_mod.capabilitiesEmpty(requested)) return; const current = try self.indexCapabilities(allocator); if (posting_mod.capabilitiesInclude(current, requested)) return; if (stats.documents != 0) return error.SearchIndexRebuildRequired; try self.putCapabilitiesIn(write, posting_mod.mergeCapabilities(current, requested)); } fn putRequestedCapabilitiesIn(self: *Search, write: *tree.Write) Error!void { const requested = self.requestedCapabilities(); if (posting_mod.capabilitiesEmpty(requested)) return; try self.putCapabilitiesIn(write, requested); } fn putCapabilitiesIn(self: *Search, write: *tree.Write, capabilities_value: posting_mod.Capabilities) Error!void { var payload: [posting_mod.capabilities_payload_size]u8 = undefined; try self.terms.putPayloadIn(write, posting_mod.internal_meta_rowid, &posting_mod.capabilitiesValues(), try posting_mod.capabilitiesPayload(&payload, capabilities_value)); } fn putSourceIn(self: *Search, write: *tree.Write, source_value: Source) Error!void { var payload: [posting_mod.source_payload_size]u8 = undefined; try self.terms.putPayloadIn(write, posting_mod.internal_meta_rowid, &posting_mod.sourceValues(), try posting_mod.sourcePayload(&payload, source_value)); } pub fn query(self: anytype, allocator: Allocator, text: []const u8, limit: usize) Error!Results { const phase = trace.scope("search.query"); defer phase.end(); var parsed = try query_mod.parseQuery(allocator, text, self.field_names); defer parsed.deinit(allocator); return try self.queryParsed(allocator, parsed, limit); } pub fn queryScoreBand(self: anytype, allocator: Allocator, text: []const u8, limit: usize) Error!Results { const phase = trace.scope("search.query_score_band"); defer phase.end(); var parsed = try query_mod.parseQuery(allocator, text, self.field_names); defer parsed.deinit(allocator); return try self.queryParsedScoreBand(allocator, parsed, limit); } pub fn queryRanked(self: anytype, allocator: Allocator, text: []const u8, limit: usize, options: rank_mod.RankingOptions) Error!RankedResults { const phase = trace.scope("search.query_ranked"); defer phase.end(); var parsed = try query_mod.parseQuery(allocator, text, self.field_names); defer parsed.deinit(allocator); return try self.queryParsedRanked(allocator, parsed, limit, options); } pub fn prepare(self: anytype, allocator: Allocator, text: []const u8) Error!Prepared { const phase = trace.scope("search.prepare"); defer phase.end(); return .{ .parsed = try query_mod.parseQuery(allocator, text, self.field_names) }; } fn queryParsed(self: anytype, allocator: Allocator, parsed: query_mod.Query, limit: usize) Error!Results { if (parsed.clauses.len == 0) return .{ .allocator = allocator, .hits = &.{}, .total = 0 }; if (parsed.clauses.len == 1) { if (limit != 0) { if (try self.clauseBoundedIndexHits(allocator, parsed.clauses[0], limit)) |bounded_hits| { std.mem.sort(hit_mod.Hit, bounded_hits.hits, {}, hit_mod.hitLess); return .{ .allocator = allocator, .hits = bounded_hits.hits, .total = bounded_hits.total }; } } if (try self.clauseIndexHits(allocator, parsed.clauses[0])) |index_hits| { const total = index_hits.len; return .{ .allocator = allocator, .hits = try hit_mod.sortAndLimitOwnedHits(allocator, index_hits, limit), .total = total }; } } if (limit != 0) { if (try self.queryBoundedExactOrHits(allocator, parsed, limit)) |bounded_hits| { std.mem.sort(hit_mod.Hit, bounded_hits.hits, {}, hit_mod.hitLess); return .{ .allocator = allocator, .hits = bounded_hits.hits, .total = bounded_hits.total }; } } var hits: []hit_mod.Hit = &.{}; var have_hits = false; errdefer if (have_hits) allocator.free(hits); for (parsed.clauses) |clause| { const clause_hits = (try self.clauseIndexHits(allocator, clause)) orelse try self.scoredClauseHits(allocator, clause); var clause_hits_owned = true; errdefer if (clause_hits_owned) allocator.free(clause_hits); if (!have_hits) { hits = clause_hits; have_hits = true; clause_hits_owned = false; continue; } const merged = try hit_mod.unionHits(allocator, hits, clause_hits); allocator.free(hits); allocator.free(clause_hits); clause_hits_owned = false; hits = merged; } const total = hits.len; const owned = try hit_mod.sortAndLimitOwnedHits(allocator, hits, limit); return .{ .allocator = allocator, .hits = owned, .total = total }; } fn queryParsedScoreBand(self: anytype, allocator: Allocator, parsed: query_mod.Query, limit: usize) Error!Results { if (limit == 0) return try self.queryParsed(allocator, parsed, 0); if (parsed.clauses.len == 0) return .{ .allocator = allocator, .hits = &.{}, .total = 0 }; if (parsed.clauses.len == 1) { if (try self.clauseScoreBandIndexHits(allocator, parsed.clauses[0], limit)) |band_hits| { std.mem.sort(hit_mod.Hit, band_hits.hits, {}, hit_mod.hitLess); return .{ .allocator = allocator, .hits = band_hits.hits, .total = band_hits.total }; } if (try self.clauseIndexHits(allocator, parsed.clauses[0])) |index_hits| { const total = index_hits.len; return .{ .allocator = allocator, .hits = try hit_mod.sortAndScoreBandOwnedHits(allocator, index_hits, limit), .total = total }; } } if (try self.queryScoreBandExactOrHits(allocator, parsed, limit)) |band_hits| { std.mem.sort(hit_mod.Hit, band_hits.hits, {}, hit_mod.hitLess); return .{ .allocator = allocator, .hits = band_hits.hits, .total = band_hits.total }; } var full = try self.queryParsed(allocator, parsed, 0); errdefer full.deinit(); full.hits = try hit_mod.sortAndScoreBandOwnedHits(allocator, full.hits, limit); return full; } fn queryParsedRanked(self: anytype, allocator: Allocator, parsed: query_mod.Query, limit: usize, options: rank_mod.RankingOptions) Error!RankedResults { if (try self.queryParsedRankedExactOr(allocator, parsed, limit, options)) |ranked| return ranked; if (query_mod.singlePositivePrefixRankingAtom(parsed)) |atom| { return try self.rankPrefixAtom(allocator, atom, limit, options); } if (query_mod.singlePositivePhrasePairRankingAtom(parsed)) |atom| { return try self.rankPhrasePairAtom(allocator, atom, limit, options); } const rows = try self.queryParsedCandidateRows(allocator, parsed); defer if (rows.len != 0) allocator.free(rows); if (rows.len == 0) return .{ .allocator = allocator, .hits = &.{}, .total = 0 }; const stats = try self.corpusStats(allocator); if (query_mod.singlePositiveNonExactRankingAtom(parsed)) |atom| { return try self.rankSingleAtomCandidateRows(allocator, rows, atom, stats, limit, options); } const terms = try self.collectRankingTerms(allocator, parsed, stats.documents); defer rank_mod.deinitRankingTerms(allocator, terms); return try self.rankCandidateRows(allocator, rows, terms, stats, limit, options); } fn queryParsedCandidateRows(self: anytype, allocator: Allocator, parsed: query_mod.Query) Error![]i64 { var rows: []i64 = &.{}; var have_rows = false; errdefer if (have_rows) allocator.free(rows); for (parsed.clauses) |clause| { const clause_rows = try self.clauseCandidates(allocator, clause); var clause_rows_owned = true; errdefer if (clause_rows_owned) allocator.free(clause_rows); if (!have_rows) { rows = clause_rows; have_rows = true; clause_rows_owned = false; continue; } const merged = try hit_mod.unionRows(allocator, rows, clause_rows); allocator.free(rows); allocator.free(clause_rows); clause_rows_owned = false; rows = merged; } return if (have_rows) rows else &.{}; } fn rankSingleAtomCandidateRows(self: anytype, allocator: Allocator, rows: []const i64, atom: *const query_mod.Atom, stats: posting_mod.CorpusStats, limit: usize, options: rank_mod.RankingOptions) Error!RankedResults { var lengths = try rank_mod.DocumentLengthCursor.init(self, allocator); defer lengths.deinit(); var candidates: std.ArrayList(rank_mod.SingleAtomCandidate) = .empty; defer candidates.deinit(allocator); var document_frequency: usize = 0; if (text_mod.useDocumentTextScanRows(rows)) { var documents: text_mod.DocumentTextCursor = undefined; try documents.init(self, allocator, rows[0]); defer documents.deinit(); for (rows) |rowid| { const frequency = (try documents.atomFrequencyFor(rowid, atom.*)) orelse continue; try rank_mod.appendSingleAtomCandidate(allocator, &candidates, &lengths, &document_frequency, rowid, frequency); } } else { for (rows) |rowid| { var document = (try self.getDocumentText(allocator, rowid)) orelse continue; const frequency = text_mod.atomFrequency(document.text, atom.*); document.deinit(allocator); try rank_mod.appendSingleAtomCandidate(allocator, &candidates, &lengths, &document_frequency, rowid, frequency); } } const idf = try rank_mod.bm25Idf(stats.documents, document_frequency); var hits: std.ArrayList(hit_mod.RankedHit) = .empty; errdefer hits.deinit(allocator); for (candidates.items) |candidate| { try hit_mod.appendRankedHit(allocator, &hits, limit, .{ .rowid = candidate.rowid, .rank = -rank_mod.bm25Score(idf, candidate.frequency, candidate.length, stats, options), }); } std.mem.sort(hit_mod.RankedHit, hits.items, {}, hit_mod.rankedHitLess); return .{ .allocator = allocator, .hits = try hits.toOwnedSlice(allocator), .total = document_frequency, }; } fn rankPrefixAtom(self: anytype, allocator: Allocator, atom: *const query_mod.Atom, limit: usize, options: rank_mod.RankingOptions) Error!RankedResults { const hits = try self.prefixHits(allocator, atom.tokens[0]); defer if (hits.len != 0) allocator.free(hits); if (hits.len == 0) return .{ .allocator = allocator, .hits = &.{}, .total = 0 }; const stats = try self.corpusStats(allocator); const idf = try rank_mod.bm25Idf(stats.documents, hits.len); var lengths = try rank_mod.DocumentLengthCursor.init(self, allocator); defer lengths.deinit(); var ranked: std.ArrayList(hit_mod.RankedHit) = .empty; errdefer ranked.deinit(allocator); if (text_mod.useDocumentTextScanHits(hits)) { var documents: text_mod.DocumentTextCursor = undefined; try documents.init(self, allocator, hits[0].rowid); defer documents.deinit(); for (hits) |hit| { const frequency = (try documents.atomFrequencyFor(hit.rowid, atom.*)) orelse continue; try rank_mod.appendPrefixRankedHit(allocator, &ranked, &lengths, hit.rowid, frequency, idf, stats, options, limit); } } else { for (hits) |hit| { var document = (try self.getDocumentText(allocator, hit.rowid)) orelse continue; const frequency = text_mod.atomFrequency(document.text, atom.*); document.deinit(allocator); try rank_mod.appendPrefixRankedHit(allocator, &ranked, &lengths, hit.rowid, frequency, idf, stats, options, limit); } } std.mem.sort(hit_mod.RankedHit, ranked.items, {}, hit_mod.rankedHitLess); return .{ .allocator = allocator, .hits = try ranked.toOwnedSlice(allocator), .total = hits.len, }; } fn rankPhrasePairAtom(self: anytype, allocator: Allocator, atom: *const query_mod.Atom, limit: usize, options: rank_mod.RankingOptions) Error!RankedResults { std.debug.assert(query_mod.isPhrasePairAtom(atom.*)); const hits = if (try self.canUsePhrasePairPostings(allocator) and !try self.phrasePairPostingsArePartial(allocator)) try self.phrasePairHits(allocator, atom.tokens[0], atom.tokens[1]) else try self.phrasePairVerifiedHits(allocator, atom.*, 1); defer if (hits.len != 0) allocator.free(hits); if (hits.len == 0) return .{ .allocator = allocator, .hits = &.{}, .total = 0 }; const stats = try self.corpusStats(allocator); const idf = try rank_mod.bm25Idf(stats.documents, hits.len); var lengths = try rank_mod.DocumentLengthCursor.init(self, allocator); defer lengths.deinit(); var ranked: std.ArrayList(hit_mod.RankedHit) = .empty; errdefer ranked.deinit(allocator); for (hits) |hit| { try hit_mod.appendRankedHit(allocator, &ranked, limit, .{ .rowid = hit.rowid, .rank = -rank_mod.bm25Score(idf, hit.score, try lengths.lengthFor(hit.rowid), stats, options), }); } std.mem.sort(hit_mod.RankedHit, ranked.items, {}, hit_mod.rankedHitLess); return .{ .allocator = allocator, .hits = try ranked.toOwnedSlice(allocator), .total = hits.len, }; } fn queryParsedRankedExactOr(self: anytype, allocator: Allocator, parsed: query_mod.Query, limit: usize, options: rank_mod.RankingOptions) Error!?RankedResults { if (parsed.clauses.len == 0) return .{ .allocator = allocator, .hits = &.{}, .total = 0 }; for (parsed.clauses) |clause| _ = query_mod.exactClauseTerm(clause) orelse return null; const stats = try self.corpusStats(allocator); const cursors = try allocator.alloc(rank_mod.ExactRankingCursor, parsed.clauses.len); var cursor_count: usize = 0; defer { for (cursors[0..cursor_count]) |*cursor| cursor.deinit(); allocator.free(cursors); } for (parsed.clauses) |clause| { const term_text = query_mod.exactClauseTerm(clause).?; cursors[cursor_count] = .{ .cursor = try rank_mod.ExactTermCursor.init(self, allocator, term_text), .idf = 0.0, }; cursor_count += 1; const document_frequency = try cursors[cursor_count - 1].cursor.documentFrequency(); cursors[cursor_count - 1].idf = try rank_mod.bm25Idf(stats.documents, document_frequency); try cursors[cursor_count - 1].cursor.advance(); } var lengths = try rank_mod.DocumentLengthCursor.init(self, allocator); defer lengths.deinit(); var hits: std.ArrayList(hit_mod.RankedHit) = .empty; errdefer hits.deinit(allocator); var total: usize = 0; while (try rank_mod.nextExactRankedOrHit(cursors[0..cursor_count], &lengths, stats, options)) |hit| { total += 1; try hit_mod.appendRankedHit(allocator, &hits, limit, hit); } std.mem.sort(hit_mod.RankedHit, hits.items, {}, hit_mod.rankedHitLess); return .{ .allocator = allocator, .hits = try hits.toOwnedSlice(allocator), .total = total, }; } fn rankCandidateRows(self: anytype, allocator: Allocator, rows: []const i64, terms: []const rank_mod.RankingTerm, stats: posting_mod.CorpusStats, limit: usize, options: rank_mod.RankingOptions) Error!RankedResults { var lengths = try rank_mod.DocumentLengthCursor.init(self, allocator); defer lengths.deinit(); var hits: std.ArrayList(hit_mod.RankedHit) = .empty; errdefer hits.deinit(allocator); var document_text: ?text_mod.DocumentText = null; defer text_mod.deinitDocumentText(allocator, &document_text); var document_text_rowid: ?i64 = null; var total: usize = 0; for (rows) |rowid| { const length = try lengths.lengthFor(rowid); var score: f64 = 0.0; for (terms) |term| { const frequency = if (term.posting_hits.len != 0) hit_mod.postingHitFrequency(term.posting_hits, rowid) else frequency: { if (document_text_rowid == null or document_text_rowid.? != rowid) { text_mod.deinitDocumentText(allocator, &document_text); document_text_rowid = null; document_text = (try self.getDocumentText(allocator, rowid)) orelse break :frequency 0; document_text_rowid = rowid; } break :frequency text_mod.atomFrequency(document_text.?.text, term.atom.*); }; if (frequency == 0) continue; score += rank_mod.bm25Score(term.idf, frequency, length, stats, options); } total += 1; try hit_mod.appendRankedHit(allocator, &hits, limit, .{ .rowid = rowid, .rank = -score }); } std.mem.sort(hit_mod.RankedHit, hits.items, {}, hit_mod.rankedHitLess); return .{ .allocator = allocator, .hits = try hits.toOwnedSlice(allocator), .total = total, }; } fn putPostingsIn(self: *Search, write: *tree.Write, allocator: Allocator, rowid: i64, text: []const u8) Error!void { const counts = try posting_mod.documentTermCounts(allocator, text, self.index_prefixes, self.index_phrase_pairs and !self.omit_phrase_pair_singletons); defer posting_mod.freeTermCounts(allocator, counts); for (counts) |term_count| try self.putPostingIn(write, rowid, term_count); } fn replacePostingsIn(self: *Search, write: *tree.Write, allocator: Allocator, rowid: i64, old_text: []const u8, new_text: []const u8) Error!void { const old_counts = try posting_mod.documentTermCounts(allocator, old_text, self.index_prefixes, self.index_phrase_pairs); defer posting_mod.freeTermCounts(allocator, old_counts); const new_counts = try posting_mod.documentTermCounts(allocator, new_text, self.index_prefixes, self.index_phrase_pairs and !self.omit_phrase_pair_singletons); defer posting_mod.freeTermCounts(allocator, new_counts); for (old_counts) |old_count| { const new_count = termCount(new_counts, old_count.key) orelse { try self.deletePostingIn(write, rowid, old_count.key); continue; }; if (old_count.count != new_count.count) try self.putPostingIn(write, rowid, new_count); } for (new_counts) |new_count| { if (termCount(old_counts, new_count.key) == null) try self.putPostingIn(write, rowid, new_count); } } fn putPostingIn(self: *Search, write: *tree.Write, rowid: i64, term_count: posting_mod.TermCount) Error!void { var payload: [page.size]u8 = undefined; try self.terms.putPayloadIn(write, rowid, &posting_mod.postingValues(term_count.key), try posting_mod.postingPayload(&payload, term_count.count)); } fn deletePostingIn(self: *Search, write: *tree.Write, rowid: i64, key: []const u8) Error!void { self.terms.deleteIn(write, rowid, &posting_mod.postingValues(key)) catch |err| switch (err) { error.KeyNotFound => {}, else => return err, }; var payload: [page.size]u8 = undefined; try self.terms.putPayloadIn(write, rowid, &posting_mod.postingValues(key), try posting_mod.postingPayload(&payload, 0)); } fn putDocumentLengthIn(self: *Search, write: *tree.Write, rowid: i64, length: usize) Error!void { var payload: [posting_mod.document_length_payload_size]u8 = undefined; try self.terms.putPayloadIn(write, rowid, &posting_mod.documentLengthValues(), try posting_mod.documentLengthPayload(&payload, length)); } fn deleteDocumentLengthIn(self: *Search, write: *tree.Write, rowid: i64) Error!void { var payload: [posting_mod.document_length_payload_size]u8 = undefined; try self.terms.putPayloadIn(write, rowid, &posting_mod.documentLengthValues(), posting_mod.documentLengthTombstonePayload(&payload)); } fn putCorpusStatsIn(self: *Search, write: *tree.Write, stats: posting_mod.CorpusStats) Error!void { var payload: [posting_mod.corpus_stats_payload_size]u8 = undefined; try self.terms.putPayloadIn(write, posting_mod.internal_meta_rowid, &posting_mod.corpusStatsValues(), try posting_mod.corpusStatsPayload(&payload, stats)); } fn putSortedDocumentLengthsIn(self: *Search, write: *tree.Write, allocator: Allocator, lengths: []posting_mod.DocumentLength) Error!void { std.mem.sort(posting_mod.DocumentLength, lengths, {}, posting_mod.documentLengthLess); if (posting_mod.canSegmentDocumentLengths(lengths)) { const payload = try posting_mod.documentLengthSegmentPayload(allocator, lengths); defer allocator.free(payload); try self.terms.putPayloadIn(write, try posting_mod.segmentRowid(lengths[0].rowid), &posting_mod.documentLengthValues(), payload); return; } for (lengths) |document_length| try self.putDocumentLengthIn(write, document_length.rowid, document_length.length); } fn putSortedPostingsIn(self: *Search, write: *tree.Write, allocator: Allocator, postings: []const posting_mod.Posting, keys: []const []const u8) Error!void { var start: usize = 0; while (start < postings.len) { var end = start + 1; while (end < postings.len and postings[start].key_index == postings[end].key_index) : (end += 1) {} const group = postings[start..end]; if (group[0].key_index >= keys.len) return error.InvalidSearchIndex; const key = keys[group[0].key_index]; if (posting_mod.canSegmentPostings(group)) { const payload = try posting_mod.postingSegmentPayload(allocator, group); defer allocator.free(payload); try self.terms.putPayloadIn(write, try posting_mod.segmentRowid(group[0].rowid), &posting_mod.postingValues(key), payload); } else if (self.omit_phrase_pair_singletons and posting_mod.isPhrasePairPostingKey(key)) { start = end; continue; } else { for (group) |posting| { var payload: [page.size]u8 = undefined; try self.terms.putPayloadIn(write, posting.rowid, &posting_mod.postingValues(key), try posting_mod.postingPayload(&payload, posting.count)); } } start = end; } } fn putTextIn(self: *Search, write: *tree.Write, allocator: Allocator, rowid: i64, text: []const u8) Error!void { const values = [_]row.Value{.{ .text = text }}; const size = try row.encodedSize(&values); if (size <= page.size) { var row_bytes: [page.size]u8 = undefined; try self.documents.putEncodedIn(write, rowid, try row.encode(&row_bytes, &values)); return; } const row_bytes = try allocator.alloc(u8, size); defer allocator.free(row_bytes); try self.documents.putEncodedIn(write, rowid, try row.encode(row_bytes, &values)); } fn deletePostingsIn(self: *Search, write: *tree.Write, allocator: Allocator, rowid: i64, text: []const u8) Error!void { const counts = try posting_mod.documentTermCounts(allocator, text, self.index_prefixes, self.index_phrase_pairs); defer posting_mod.freeTermCounts(allocator, counts); for (counts) |term_count| try self.deletePostingIn(write, rowid, term_count.key); } fn clauseIndexHits(self: anytype, allocator: Allocator, clause: query_mod.Clause) Error!?[]hit_mod.Hit { if (clause.positive.len == 0) return null; const first = try self.atomIndexHits(allocator, clause.positive[0]); var current = first orelse return null; errdefer allocator.free(current); var index: usize = 1; while (index < clause.positive.len) : (index += 1) { const next_maybe = try self.atomIndexHits(allocator, clause.positive[index]); const next = next_maybe orelse { allocator.free(current); return null; }; defer allocator.free(next); const merged = try hit_mod.intersectHits(allocator, current, next); allocator.free(current); current = merged; } for (clause.negative) |atom| { const excluded = try self.atomDocuments(allocator, atom); defer allocator.free(excluded); const filtered = try hit_mod.subtractHits(allocator, current, excluded); allocator.free(current); current = filtered; } return current; } fn clauseBoundedIndexHits(self: anytype, allocator: Allocator, clause: query_mod.Clause, limit: usize) Error!?hit_mod.HitSet { if (clause.negative.len != 0 or clause.positive.len != 1) return null; return try self.atomBoundedIndexHits(allocator, clause.positive[0], limit); } fn clauseScoreBandIndexHits(self: anytype, allocator: Allocator, clause: query_mod.Clause, limit: usize) Error!?hit_mod.HitSet { if (clause.negative.len != 0 or clause.positive.len != 1) return null; return try self.atomScoreBandIndexHits(allocator, clause.positive[0], limit); } fn queryBoundedExactOrHits(self: anytype, allocator: Allocator, parsed: query_mod.Query, limit: usize) Error!?hit_mod.HitSet { if (parsed.clauses.len < 2) return null; for (parsed.clauses) |clause| _ = query_mod.exactClauseTerm(clause) orelse return null; const cursors = try allocator.alloc(rank_mod.ExactTermCursor, parsed.clauses.len); var cursor_count: usize = 0; defer { for (cursors[0..cursor_count]) |*cursor| cursor.deinit(); allocator.free(cursors); } for (parsed.clauses) |clause| { const term = query_mod.exactClauseTerm(clause).?; cursors[cursor_count] = try rank_mod.ExactTermCursor.init(self, allocator, term); cursor_count += 1; try cursors[cursor_count - 1].advance(); } var hits: std.ArrayList(hit_mod.Hit) = .empty; errdefer hits.deinit(allocator); var total: usize = 0; while (try rank_mod.nextExactOrHit(cursors)) |hit| { total += 1; try hit_mod.appendBoundedHit(allocator, &hits, limit, hit); } return .{ .hits = try hits.toOwnedSlice(allocator), .total = total }; } fn queryScoreBandExactOrHits(self: anytype, allocator: Allocator, parsed: query_mod.Query, limit: usize) Error!?hit_mod.HitSet { var bounded_hits = (try self.queryBoundedExactOrHits(allocator, parsed, limit)) orelse return null; errdefer if (bounded_hits.hits.len != 0) allocator.free(bounded_hits.hits); if (bounded_hits.total <= limit) return bounded_hits; const cutoff = hit_mod.minHitScore(bounded_hits.hits); allocator.free(bounded_hits.hits); bounded_hits.hits = &.{}; bounded_hits.hits = try self.queryExactOrHitsAtLeastScore(allocator, parsed, cutoff); return bounded_hits; } fn queryExactOrHitsAtLeastScore(self: anytype, allocator: Allocator, parsed: query_mod.Query, cutoff: usize) Error![]hit_mod.Hit { if (parsed.clauses.len < 2) return &.{}; const cursors = try allocator.alloc(rank_mod.ExactTermCursor, parsed.clauses.len); var cursor_count: usize = 0; defer { for (cursors[0..cursor_count]) |*cursor| cursor.deinit(); allocator.free(cursors); } for (parsed.clauses) |clause| { const term = query_mod.exactClauseTerm(clause).?; cursors[cursor_count] = try rank_mod.ExactTermCursor.init(self, allocator, term); cursor_count += 1; try cursors[cursor_count - 1].advance(); } var hits: std.ArrayList(hit_mod.Hit) = .empty; errdefer hits.deinit(allocator); while (try rank_mod.nextExactOrHit(cursors)) |hit| { if (hit.score >= cutoff) try hits.append(allocator, hit); } return try hits.toOwnedSlice(allocator); } fn atomIndexHits(self: anytype, allocator: Allocator, atom: query_mod.Atom) Error!?[]hit_mod.Hit { if (atom.tokens.len == 0 or atom.prefix or atom.near_window != null or atom.field != null) return null; if (atom.phrase) { if (!query_mod.isPhrasePairAtom(atom)) return null; const hits = if (try self.canUsePhrasePairPostings(allocator) and !try self.phrasePairPostingsArePartial(allocator)) try self.phrasePairHits(allocator, atom.tokens[0], atom.tokens[1]) else try self.phrasePairVerifiedHits(allocator, atom, 1); errdefer if (hits.len != 0) allocator.free(hits); for (hits) |*hit| hit.score = std.math.mul(usize, hit.score, atom.tokens.len) catch return error.InvalidSearchIndex; return hits; } var current = try self.termHits(allocator, atom.tokens[0]); errdefer allocator.free(current); var index: usize = 1; while (index < atom.tokens.len) : (index += 1) { const next = try self.termHits(allocator, atom.tokens[index]); defer allocator.free(next); const merged = try hit_mod.intersectHits(allocator, current, next); allocator.free(current); current = merged; } return current; } fn atomBoundedIndexHits(self: anytype, allocator: Allocator, atom: query_mod.Atom, limit: usize) Error!?hit_mod.HitSet { if (atom.tokens.len != 1 or atom.prefix or atom.phrase or atom.near_window != null or atom.field != null) return null; return try self.termHitsBounded(allocator, atom.tokens[0], limit); } fn atomScoreBandIndexHits(self: anytype, allocator: Allocator, atom: query_mod.Atom, limit: usize) Error!?hit_mod.HitSet { if (atom.tokens.len != 1 or atom.prefix or atom.phrase or atom.near_window != null or atom.field != null) return null; return try self.termHitsScoreBand(allocator, atom.tokens[0], limit); } fn clauseCandidates(self: anytype, allocator: Allocator, clause: query_mod.Clause) Error![]i64 { var current = if (clause.positive.len == 0) try self.allDocuments(allocator) else try self.atomDocuments(allocator, clause.positive[0]); errdefer allocator.free(current); var index: usize = if (clause.positive.len == 0) 0 else 1; while (index < clause.positive.len) : (index += 1) { const next = try self.atomDocuments(allocator, clause.positive[index]); defer allocator.free(next); const merged = try hit_mod.intersectRows(allocator, current, next); allocator.free(current); current = merged; } for (clause.negative) |atom| { const excluded = try self.atomDocuments(allocator, atom); defer allocator.free(excluded); const filtered = try hit_mod.subtractRows(allocator, current, excluded); allocator.free(current); current = filtered; } return current; } fn scoredClauseHits(self: anytype, allocator: Allocator, clause: query_mod.Clause) Error![]hit_mod.Hit { const candidates = try self.clauseCandidates(allocator, clause); defer allocator.free(candidates); var hits: std.ArrayList(hit_mod.Hit) = .empty; errdefer hits.deinit(allocator); for (candidates) |rowid| { const doc = (try self.documents.get(allocator, rowid)) orelse continue; defer allocator.free(doc); const score = text_mod.clauseScore(try token_mod.textFromRow(doc), clause); if (score == 0) continue; try hits.append(allocator, .{ .rowid = rowid, .score = score }); } return try hits.toOwnedSlice(allocator); } fn atomDocuments(self: anytype, allocator: Allocator, atom: query_mod.Atom) Error![]i64 { if (atom.tokens.len == 0) return &.{}; if (query_mod.isPhrasePairAtom(atom)) { const hits = if (try self.canUsePhrasePairPostings(allocator) and !try self.phrasePairPostingsArePartial(allocator)) try self.phrasePairHits(allocator, atom.tokens[0], atom.tokens[1]) else try self.phrasePairVerifiedHits(allocator, atom, 1); defer if (hits.len != 0) allocator.free(hits); var rows: std.ArrayList(i64) = .empty; errdefer rows.deinit(allocator); try rows.ensureUnusedCapacity(allocator, hits.len); for (hits) |hit| rows.appendAssumeCapacity(hit.rowid); return try rows.toOwnedSlice(allocator); } var current = try self.termDocuments(allocator, atom.tokens[0], atom.prefix); errdefer allocator.free(current); if (atom.prefix) { if (atom.field == null) return current; const filtered = try self.verifiedRowsFromCandidates(allocator, current, atom); allocator.free(current); return filtered; } var index: usize = 1; while (index < atom.tokens.len) : (index += 1) { const next = try self.termDocuments(allocator, atom.tokens[index], false); defer allocator.free(next); const merged = try hit_mod.intersectRows(allocator, current, next); allocator.free(current); current = merged; } if (atom.phrase or atom.near_window != null or atom.field != null) { const filtered = try self.verifiedRowsFromCandidates(allocator, current, atom); allocator.free(current); return filtered; } return current; } fn verifiedRowsFromCandidates(self: anytype, allocator: Allocator, rows: []const i64, atom: query_mod.Atom) Error![]i64 { var out: std.ArrayList(i64) = .empty; errdefer out.deinit(allocator); for (rows) |rowid| { var document = (try self.getDocumentText(allocator, rowid)) orelse continue; const frequency = text_mod.atomFrequency(document.text, atom); document.deinit(allocator); if (frequency != 0) try out.append(allocator, rowid); } return try out.toOwnedSlice(allocator); } fn phrasePairVerifiedHits(self: anytype, allocator: Allocator, atom: query_mod.Atom, score_multiplier: usize) Error![]hit_mod.Hit { std.debug.assert(query_mod.isPhrasePairAtom(atom)); var left = try rank_mod.ExactTermCursor.init(self, allocator, atom.tokens[0]); defer left.deinit(); try left.advance(); var right = try rank_mod.ExactTermCursor.init(self, allocator, atom.tokens[1]); defer right.deinit(); try right.advance(); var indexed: ?rank_mod.ExactTermCursor = if (try self.canUsePhrasePairPostings(allocator)) try rank_mod.ExactTermCursor.initPhrasePair(self, allocator, atom.tokens[0], atom.tokens[1]) else null; defer if (indexed) |*cursor| cursor.deinit(); if (indexed) |*cursor| try cursor.advance(); var hits: std.ArrayList(hit_mod.Hit) = .empty; errdefer hits.deinit(allocator); var documents: ?text_mod.DocumentTextCursor = null; defer if (documents) |*cursor| cursor.deinit(); while (try rank_mod.nextPhrasePairCandidateRow(&left, &right)) |rowid| { const indexed_frequency = if (indexed) |*cursor| try rank_mod.postingCursorFrequencyAt(cursor, rowid) else 0; const frequency = if (indexed_frequency != 0) indexed_frequency else frequency: { if (documents == null) { documents = @as(text_mod.DocumentTextCursor, undefined); errdefer documents = null; try documents.?.init(self, allocator, rowid); } if (documents) |*cursor| break :frequency (try cursor.atomFrequencyFor(rowid, atom)) orelse 0; unreachable; }; if (frequency != 0) try hits.append(allocator, .{ .rowid = rowid, .score = std.math.mul(usize, frequency, score_multiplier) catch return error.InvalidSearchIndex, }); } return try hits.toOwnedSlice(allocator); } fn termDocuments(self: anytype, allocator: Allocator, term: []const u8, prefix: bool) Error![]i64 { if (prefix and !self.index_prefixes) return try self.prefixDocuments(allocator, term); const hits = try self.termHitsForKind(allocator, if (prefix) '*' else '=', term); defer allocator.free(hits); var rows: std.ArrayList(i64) = .empty; errdefer rows.deinit(allocator); try rows.ensureUnusedCapacity(allocator, hits.len); for (hits) |hit| rows.appendAssumeCapacity(hit.rowid); return try hit_mod.sortedUniqueRows(allocator, &rows); } fn termHits(self: anytype, allocator: Allocator, term: []const u8) Error![]hit_mod.Hit { return try self.termHitsForKind(allocator, '=', term); } fn termHitsForKind(self: anytype, allocator: Allocator, kind: u8, term: []const u8) Error![]hit_mod.Hit { var key_buffer: [token_mod.max_token_bytes + 1]u8 = undefined; const key_text = posting_mod.postingKeyBuffer(&key_buffer, kind, term); return try self.postingHits(allocator, key_text); } fn postingHits(self: anytype, allocator: Allocator, key_text: []const u8) Error![]hit_mod.Hit { var lookup: index_mod.Scan = undefined; try self.terms.lookupPayloads(&lookup, allocator, &.{.{ .text = key_text }}); defer lookup.deinit(); var raw: std.ArrayList(hit_mod.RawHit) = .empty; errdefer raw.deinit(allocator); while (try lookup.next()) |entry| try hit_mod.appendRawHits(allocator, &raw, entry.rowid, entry.payload); return try hit_mod.coalescedHits(allocator, &raw); } fn phrasePairHits(self: anytype, allocator: Allocator, first: []const u8, second: []const u8) Error![]hit_mod.Hit { var key_buffer: [posting_mod.phrase_pair_posting_key_max_bytes]u8 = undefined; const key_text = posting_mod.phrasePairPostingKeyBuffer(&key_buffer, first, second); return try self.postingHits(allocator, key_text); } fn termHitsBounded(self: anytype, allocator: Allocator, term: []const u8, limit: usize) Error!hit_mod.HitSet { const hits = try self.termHits(allocator, term); errdefer if (hits.len != 0) allocator.free(hits); const total = hits.len; return .{ .hits = try hit_mod.sortAndLimitOwnedHits(allocator, hits, limit), .total = total }; } fn termHitsScoreBand(self: anytype, allocator: Allocator, term: []const u8, limit: usize) Error!hit_mod.HitSet { if (limit == 0) { const hits = try self.termHits(allocator, term); return .{ .hits = hits, .total = hits.len }; } var cursor = try rank_mod.ExactTermCursor.init(self, allocator, term); defer cursor.deinit(); var hits: std.ArrayList(hit_mod.Hit) = .empty; errdefer hits.deinit(allocator); var total: usize = 0; while (true) { try cursor.advance(); const hit = cursor.current orelse break; total += 1; try hit_mod.appendBoundedHit(allocator, &hits, limit, hit); } if (total <= limit) { const owned = try hits.toOwnedSlice(allocator); std.mem.sort(hit_mod.Hit, owned, {}, hit_mod.hitLess); return .{ .hits = owned, .total = total }; } const cutoff = hit_mod.minHitScore(hits.items); hits.clearRetainingCapacity(); cursor.reset(); while (true) { try cursor.advance(); const hit = cursor.current orelse break; if (hit.score >= cutoff) try hits.append(allocator, hit); } const owned = try hits.toOwnedSlice(allocator); std.mem.sort(hit_mod.Hit, owned, {}, hit_mod.hitLess); return .{ .hits = owned, .total = total }; } fn termHitsAtLeastScore(self: anytype, allocator: Allocator, term: []const u8, cutoff: usize) Error![]hit_mod.Hit { const source = try self.termHits(allocator, term); defer allocator.free(source); var hits: std.ArrayList(hit_mod.Hit) = .empty; errdefer hits.deinit(allocator); for (source) |hit| { const score = hit.score; if (score < cutoff) continue; try hits.append(allocator, hit); } return try hits.toOwnedSlice(allocator); } fn prefixHits(self: anytype, allocator: Allocator, term: []const u8) Error![]hit_mod.Hit { if (self.index_prefixes) return try self.termHitsForKind(allocator, '*', term); var start_buffer: [token_mod.max_token_bytes + 1]u8 = undefined; const start = posting_mod.postingKeyBuffer(&start_buffer, '=', term); var end_buffer: [token_mod.max_token_bytes + 2]u8 = undefined; const end = posting_mod.prefixEndBuffer(&end_buffer, start); var lookup: index_mod.Scan = undefined; try self.terms.scanPayloads( &lookup, allocator, &.{.{ .text = start }}, &.{.{ .text = end }}, ); defer lookup.deinit(); var raw: std.ArrayList(hit_mod.RawHit) = .empty; errdefer raw.deinit(allocator); while (try lookup.next()) |entry| try hit_mod.appendRawHits(allocator, &raw, entry.rowid, entry.payload); const hits = try hit_mod.coalescedHits(allocator, &raw); return hits; } fn prefixDocuments(self: anytype, allocator: Allocator, term: []const u8) Error![]i64 { const hits = try self.prefixHits(allocator, term); defer allocator.free(hits); var rows: std.ArrayList(i64) = .empty; errdefer rows.deinit(allocator); try rows.ensureUnusedCapacity(allocator, hits.len); for (hits) |hit| rows.appendAssumeCapacity(hit.rowid); return try hit_mod.sortedUniqueRows(allocator, &rows); } fn allDocuments(self: anytype, allocator: Allocator) Error![]i64 { var scan: table_mod.Scan = undefined; try self.documents.scan(&scan, allocator, null, null); defer scan.deinit(); var rows: std.ArrayList(i64) = .empty; errdefer rows.deinit(allocator); while (try scan.next()) |entry| try rows.append(allocator, entry.rowid); return try hit_mod.sortedUniqueRows(allocator, &rows); } fn corpusStats(self: anytype, allocator: Allocator) Error!posting_mod.CorpusStats { var lookup: index_mod.Scan = undefined; try self.terms.lookupPayloads(&lookup, allocator, &posting_mod.corpusStatsValues()); defer lookup.deinit(); const entry = (try lookup.next()) orelse return error.InvalidSearchIndex; if (entry.rowid != posting_mod.internal_meta_rowid) return error.InvalidSearchIndex; const stats = try posting_mod.corpusStatsPayloadValue(entry.payload); if (try lookup.next() != null) return error.InvalidSearchIndex; return stats; } fn corpusStatsOrEmpty(self: anytype, allocator: Allocator) Error!posting_mod.CorpusStats { return self.corpusStats(allocator) catch |err| switch (err) { error.InvalidSearchIndex => stats: { var scan: table_mod.Scan = undefined; try self.documents.scan(&scan, allocator, null, null); defer scan.deinit(); if (try scan.next() != null) return error.InvalidSearchIndex; break :stats .{ .documents = 0, .total_tokens = 0 }; }, else => return err, }; } fn documentLength(self: anytype, allocator: Allocator, rowid: i64) Error!usize { var cursor = try rank_mod.DocumentLengthCursor.init(self, allocator); defer cursor.deinit(); return try cursor.lengthFor(rowid); } fn documentLengths(self: anytype, allocator: Allocator) Error![]posting_mod.DocumentLength { var cursor = try rank_mod.DocumentLengthCursor.init(self, allocator); defer cursor.deinit(); var lengths: std.ArrayList(posting_mod.DocumentLength) = .empty; errdefer lengths.deinit(allocator); while (cursor.rowid) |rowid| { try lengths.append(allocator, .{ .rowid = rowid, .length = cursor.length, }); try cursor.advance(); } return try lengths.toOwnedSlice(allocator); } fn collectRankingTerms(self: anytype, allocator: Allocator, parsed: query_mod.Query, document_count: usize) Error![]rank_mod.RankingTerm { var terms: std.ArrayList(rank_mod.RankingTerm) = .empty; errdefer { for (terms.items) |*term| term.deinit(allocator); terms.deinit(allocator); } for (parsed.clauses) |*clause| { for (clause.positive) |*atom| { if (atom.tokens.len == 0) continue; if (query_mod.isExactRankingAtom(atom.*)) { const hits = try self.termHits(allocator, atom.tokens[0]); const idf = rank_mod.bm25Idf(document_count, hits.len) catch |err| { if (hits.len != 0) allocator.free(hits); return err; }; terms.append(allocator, .{ .atom = atom, .posting_hits = hits, .document_frequency = hits.len, .idf = idf, }) catch |err| { if (hits.len != 0) allocator.free(hits); return err; }; } else if (query_mod.isPhrasePairAtom(atom.*)) { const hits = if (try self.canUsePhrasePairPostings(allocator) and !try self.phrasePairPostingsArePartial(allocator)) try self.phrasePairHits(allocator, atom.tokens[0], atom.tokens[1]) else try self.phrasePairVerifiedHits(allocator, atom.*, 1); const idf = rank_mod.bm25Idf(document_count, hits.len) catch |err| { if (hits.len != 0) allocator.free(hits); return err; }; terms.append(allocator, .{ .atom = atom, .posting_hits = hits, .document_frequency = hits.len, .idf = idf, }) catch |err| { if (hits.len != 0) allocator.free(hits); return err; }; } else { const frequency = try self.atomDocumentFrequency(allocator, atom.*); try terms.append(allocator, .{ .atom = atom, .posting_hits = &.{}, .document_frequency = frequency, .idf = try rank_mod.bm25Idf(document_count, frequency), }); } } } return try terms.toOwnedSlice(allocator); } fn atomDocumentFrequency(self: anytype, allocator: Allocator, atom: query_mod.Atom) Error!usize { const rows = try self.atomDocuments(allocator, atom); defer allocator.free(rows); if (!atom.phrase) return rows.len; var count: usize = 0; for (rows) |rowid| { var document = (try self.getDocumentText(allocator, rowid)) orelse continue; const frequency = text_mod.atomFrequency(document.text, atom); document.deinit(allocator); if (frequency != 0) count += 1; } return count; } fn clearIn(self: *Search, write: *tree.Write, allocator: Allocator) Error!void { _ = allocator; try write.clear(&self.terms.entries); try write.clear(&self.documents.rows); } fn collectTermKeys(self: anytype, allocator: Allocator) Error![]const []const u8 { var scan: index_mod.Scan = undefined; try self.terms.scan(&scan, allocator, null, null); defer scan.deinit(); var keys: std.ArrayList([]const u8) = .empty; errdefer posting_mod.freeStringSlice(allocator, keys.items); while (try scan.next()) |entry| try keys.append(allocator, try allocator.dupe(u8, entry.key)); return try keys.toOwnedSlice(allocator); } fn collectPostings(self: anytype, allocator: Allocator, documents: []const Document) Error!posting_mod.PostingSet { var postings: std.ArrayList(posting_mod.Posting) = .empty; errdefer postings.deinit(allocator); var keys: std.ArrayList([]const u8) = .empty; errdefer posting_mod.deinitStringList(allocator, &keys); var key_map = posting_mod.PostingKeyMap.init(allocator); defer key_map.deinit(); const phrase_pairs = self.index_phrase_pairs and !self.omit_phrase_pair_singletons; if (!self.index_prefixes) { const postings_capacity = bulkCapacity(documents.len, if (phrase_pairs) 32 else 16, 0); try postings.ensureTotalCapacity(allocator, postings_capacity); const key_capacity = bulkCapacity(documents.len, if (phrase_pairs) 2 else 1, 64); try keys.ensureTotalCapacity(allocator, key_capacity); try key_map.ensureTotalCapacity(hashCapacityHint(key_capacity)); } const term_count_capacity = try documentTermCountScratchCapacity(documents, self.index_prefixes, phrase_pairs); const term_counts: []posting_mod.TermCount = if (term_count_capacity == 0) &.{} else try allocator.alloc(posting_mod.TermCount, term_count_capacity); defer if (term_counts.len != 0) allocator.free(term_counts); for (documents) |document| { const counts = try posting_mod.documentInternedTermCounts(allocator, token_mod.documentText(document.text), self.index_prefixes, phrase_pairs, &key_map, &keys, term_counts); try postings.ensureUnusedCapacity(allocator, counts.len); for (counts) |term_count| { const key_index = key_map.get(term_count.key) orelse return error.InvalidSearchIndex; const count = std.math.cast(u32, term_count.count) orelse return error.InvalidSearchIndex; postings.appendAssumeCapacity(.{ .rowid = document.rowid, .key_index = key_index, .count = count }); } } if (self.index_phrase_pairs and self.omit_phrase_pair_singletons) try posting_mod.collectPartialPhrasePairPostings(allocator, documents, &postings, &keys); const items = try postings.toOwnedSlice(allocator); errdefer if (items.len != 0) allocator.free(items); return .{ .items = items, .keys = try keys.toOwnedSlice(allocator), }; }};Source: lib/sql/src/root.zig:251
zig
pub const Search = search.Search;Also reachable as
Complete call list for Search.loadAllNew
7 direct calls.
lib.sql.src.search.engine.Search.collectPostings[method] — private source atlib/sql/src/search/engine.zig:1522in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.Search.corpusStatsOrEmpty[method] — private source atlib/sql/src/search/engine.zig:1402in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.Search.ensureRequestedCapabilitiesIn[method] — private source atlib/sql/src/search/engine.zig:520in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.Search.putCorpusStatsIn[method] — private source atlib/sql/src/search/engine.zig:940in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.Search.putSortedDocumentLengthsIn[method] — private source atlib/sql/src/search/engine.zig:945in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.Search.putSortedPostingsIn[method] — private source atlib/sql/src/search/engine.zig:956in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.Search.putTextIn[method] — private source atlib/sql/src/search/engine.zig:981in nearest public ownerlib.sql.src.search.engine
Complete caller list for Search.open
55 direct callers.
lib.sql.src.search.engine.test_search_OR_unions_exact_hits_and_sums_duplicate_scores[function] — test source atlib/sql/src/search/engine.zig:2655in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_bounds_oversized_documents_to_page_safe_text[function] — test source atlib/sql/src/search/engine.zig:3262in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_bulk_load_can_rebuild_identical_exact_postings_across_reopen[function] — test source atlib/sql/src/search/engine.zig:3182in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_bulk_load_rebuilds_changed_phrase_pair_postings[function] — test source atlib/sql/src/search/engine.zig:3217in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_bulk_load_replaces_existing_documents[function] — test source atlib/sql/src/search/engine.zig:3074in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_bulk_load_stores_fresh_documents_in_sorted_posting_order[function] — test source atlib/sql/src/search/engine.zig:2924in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_bulk_load_stores_repeated_terms_in_immutable_segments[function] — test source atlib/sql/src/search/engine.zig:2951in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_bulk_put_stores_documents_in_one_commit[function] — test source atlib/sql/src/search/engine.zig:2897in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_can_skip_prefix_postings_for_exact_workloads[function] — test source atlib/sql/src/search/engine.zig:3295in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_changed_put_updates_exact_and_phrase_posting_counts[function] — test source atlib/sql/src/search/engine.zig:2866in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_clear_removes_indexed_documents_and_postings[function] — test source atlib/sql/src/search/engine.zig:3112in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_exact-only_index_reopens[function] — test source atlib/sql/src/search/engine.zig:3319in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_exact-only_prefix_rows_remain_sorted_for_conjunctions[function] — test source atlib/sql/src/search/engine.zig:2131in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_exact_ranking_uses_posting_term_frequencies[function] — test source atlib/sql/src/search/engine.zig:2250in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_field_separators_bound_phrase_and_near_matches[function] — test source atlib/sql/src/search/engine.zig:1889in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_identical_put_commits_no_pages[function] — test source atlib/sql/src/search/engine.zig:2843in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_limited_OR_keeps_best_hits_before_rowid_order[function] — test source atlib/sql/src/search/engine.zig:2716in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_limited_OR_streams_multiple_exact_terms[function] — test source atlib/sql/src/search/engine.zig:2740in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_limited_OR_streams_segmented_terms_with_direct_tombstones[function] — test source atlib/sql/src/search/engine.zig:2774in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_limited_results_keep_total[function] — test source atlib/sql/src/search/engine.zig:2079in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_loadAllNew_loads_cleared_sorted_batches_across_reopen[function] — test source atlib/sql/src/search/engine.zig:3140in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_negative_field_atoms_subtract_only_field_matches[function] — test source atlib/sql/src/search/engine.zig:1974in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_phrase_matching_preserves_skipped_token_gaps[function] — test source atlib/sql/src/search/engine.zig:2153in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_phrase_pair_capability_persists_across_empty_index_opt_in[function] — test source atlib/sql/src/search/engine.zig:2450in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_phrase_pair_incremental_opt_in_requires_rebuild[function] — test source atlib/sql/src/search/engine.zig:2491in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_phrase_pair_singleton_omission_segments_repeated_pairs[function] — test source atlib/sql/src/search/engine.zig:2411in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_phrase_pair_singleton_omission_verifies_candidates[function] — test source atlib/sql/src/search/engine.zig:2372in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_posting_stats_classify_phrase_pair_segments[function] — test source atlib/sql/src/search/engine.zig:3026in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_prepared_query_reuses_parsed_exact_clauses[function] — test source atlib/sql/src/search/engine.zig:2050in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_ranked_field_clauses_verify_candidates_and_keep_negative_matches[function] — test source atlib/sql/src/search/engine.zig:2010in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_ranked_phrase_excludes_token_intersection_false_positives[function] — test source atlib/sql/src/search/engine.zig:2327in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_ranked_phrase_pair_uses_posting_frequency[function] — test source atlib/sql/src/search/engine.zig:2348in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_ranked_prefix_uses_prefix_posting_frequency[function] — test source atlib/sql/src/search/engine.zig:2528in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_ranked_query_boosts_rare_exact_terms_over_common_frequency[function] — test source atlib/sql/src/search/engine.zig:2276in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_ranked_query_keeps_stats_valid_across_load_replace_and_delete[function] — test source atlib/sql/src/search/engine.zig:2551in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_ranked_query_normalizes_document_length[function] — test source atlib/sql/src/search/engine.zig:2305in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_read-only_reader_queries_a_fixed_file_snapshot_without_write_declarations[function] — test source atlib/sql/src/search/engine.zig:1698in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_rejects_stale_posting_payloads[function] — test source atlib/sql/src/search/engine.zig:2822in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_reopens_glom_shaped_document_text[function] — test source atlib/sql/src/search/engine.zig:3435in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_reopens_large_rowids[function] — test source atlib/sql/src/search/engine.zig:3407in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_replacement_and_delete_update_postings_atomically[function] — test source atlib/sql/src/search/engine.zig:2182in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_replacement_tolerates_missing_old_postings[function] — test source atlib/sql/src/search/engine.zig:2223in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_score_band_keeps_OR_cutoff_ties[function] — test source atlib/sql/src/search/engine.zig:2690in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_score_band_keeps_exact_term_cutoff_ties[function] — test source atlib/sql/src/search/engine.zig:2593in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_score_band_streams_segmented_exact_term_cutoff_ties[function] — test source atlib/sql/src/search/engine.zig:2626in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_shared_tablespace_writes_later_reserved_table_root[function] — test source atlib/sql/src/search/engine.zig:3468in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_shared_tablespace_writes_refs_after_reopen_query[function] — test source atlib/sql/src/search/engine.zig:3502in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_shares_tablespace_with_rowid_table_roots[function] — test source atlib/sql/src/search/engine.zig:3346in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_shares_tablespace_with_two_search_roots[function] — test source atlib/sql/src/search/engine.zig:3376in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_source_revisions_gate_document_updates[function] — test source atlib/sql/src/search/engine.zig:3550in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_stores_large_documents_through_overflow_rows[function] — test source atlib/sql/src/search/engine.zig:3239in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_subtracts_negative_doclists_before_scoring[function] — test source atlib/sql/src/search/engine.zig:2106in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_supports_exact_prefix_phrase_boolean_and_reopen[function] — test source atlib/sql/src/search/engine.zig:1766in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_supports_fts_near_expressions[function] — test source atlib/sql/src/search/engine.zig:1840in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.test_search_supports_grouped_boolean_and_field_filters[function] — test source atlib/sql/src/search/engine.zig:1927in nearest public ownerlib.sql.src.search.engine
Complete call list for Search.putAll
9 direct calls.
lib.sql.src.search.engine.Search.corpusStatsOrEmpty[method] — private source atlib/sql/src/search/engine.zig:1402in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.Search.deleteDocumentLengthIn[method] — private source atlib/sql/src/search/engine.zig:935in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.Search.deletePostingsIn[method] — private source atlib/sql/src/search/engine.zig:994in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.Search.documentLength[method] — private source atlib/sql/src/search/engine.zig:1415in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.Search.ensureRequestedCapabilitiesIn[method] — private source atlib/sql/src/search/engine.zig:520in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.Search.putCorpusStatsIn[method] — private source atlib/sql/src/search/engine.zig:940in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.Search.putDocumentLengthIn[method] — private source atlib/sql/src/search/engine.zig:930in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.Search.putPostingsIn[method] — private source atlib/sql/src/search/engine.zig:893in nearest public ownerlib.sql.src.search.enginelib.sql.src.search.engine.Search.putTextIn[method] — private source atlib/sql/src/search/engine.zig:981in nearest public ownerlib.sql.src.search.engine
Audit
| Definitions | 21 |
|---|---|
| Public names | 42 |
| Members | 7 |
| Version | 26.7.0 |
| Revision | daab053ee433 |