tiny.glom.Database
Defined in tiny.glom.
API (56)
Actions
Public operations.
beginbeginBulkbulkCheckpointcheckpointclearSearchcommitdeinitdeinitSearchBatchdeletePathendBulkfindDocumentflushDocumentSearchBatchgetAllDocumentStatesgetAllPathsgetDocumenthasDocumentsindexedAgeNanosecondsinitinsertDocumentRowNewinsertDocumentRowNewSearchinsertNewnativeSearchAvailableoptimizerebuildSearchresolveSearchRefsaveDocumentSearchRefssearchstatsupdateupdateDocumentRowupdateDocumentRowSearchupdateSaturatedTranscriptupsertupsertDocumentRowupsertDocumentRowSearch
Fields and members
Public fields and members.
allocatorbulk_capacitycheckpoint_wal_bytesdb_namedir_pathdocumentsdocuments_searchfilefile_liveinitializedlock_filemax_wal_bytesnext_document_rowidnormal_capacitypathsearch_refsspacetemporary_dirwal_nameworkspacewrite_mode
Source
Source: tools/glom/src/database.zig:70
zig
pub const Database = struct { allocator: std.mem.Allocator, path: []u8, dir_path: []u8, db_name: []u8, wal_name: []u8, temporary_dir: ?[]u8, lock_file: std.Io.File, workspace: *sql.FileDatabase.Workspace, file: *sql.FileDatabase, file_live: bool, space: sql.Space, documents: sql.RowIdTable, search_refs: sql.RowIdTable, documents_search: sql.Search, initialized: bool, next_document_rowid: ?i64, max_wal_bytes: usize, checkpoint_wal_bytes: usize, normal_capacity: sql.PagerCapacity, bulk_capacity: sql.PagerCapacity, write_mode: WriteMode, pub fn init(allocator: std.mem.Allocator, maybe_path: ?[]const u8) !Database { return try initWithPolicy(allocator, maybe_path, .{}); } fn initWithPolicy( allocator: std.mem.Allocator, maybe_path: ?[]const u8, policy: InitPolicy, ) !Database { try validateInitPolicy(policy); const path_info = if (maybe_path) |value| if (std.mem.eql(u8, value, ":memory:")) try temporaryPath(allocator) else PathInfo{ .path = try allocator.dupe(u8, value), .temporary_dir = null } else PathInfo{ .path = try defaultPath(allocator), .temporary_dir = null }; errdefer freePathInfo(allocator, path_info); const dir_part = std.fs.path.dirname(path_info.path) orelse "."; try std.Io.Dir.cwd().createDirPath(fs_io, dir_part); const dir_path = try allocator.dupe(u8, dir_part); errdefer allocator.free(dir_path); const db_name = try allocator.dupe(u8, std.fs.path.basename(path_info.path)); errdefer allocator.free(db_name); const wal_name = try std.fmt.allocPrint(allocator, "{s}.wal", .{db_name}); errdefer allocator.free(wal_name); const wal_absolute = try std.fmt.allocPrint(allocator, "{s}.wal", .{path_info.path}); defer allocator.free(wal_absolute); const initialized = fileSize(path_info.path) != 0 or fileSize(wal_absolute) != 0; var dir = try openDir(dir_path); defer dir.close(fs_io); const lock_name = try std.fmt.allocPrint(allocator, "{s}.lock", .{db_name}); defer allocator.free(lock_name); const lock_file = try dir.createFile(fs_io, lock_name, .{ .truncate = false, .read = true, .permissions = @fromBackingInt(@intCast(0o600)), }); errdefer lock_file.close(fs_io); if (!try lock_file.tryLock(fs_io, .exclusive)) return error.DatabaseBusy; errdefer { lock_file.unlock(fs_io); lock_file.close(fs_io); } const file_ptr = try allocator.create(sql.FileDatabase); errdefer allocator.destroy(file_ptr); const workspace = try allocator.create(sql.FileDatabase.Workspace); errdefer allocator.destroy(workspace); workspace.* = try sql.FileDatabase.Workspace.allocate(allocator, .{ .header = databaseHeader(), .max_wal_bytes = policy.max_wal_bytes, .path_storage = sql.file.PathStorage.Limits.forDirect(.{ .database = db_name, .wal = wal_name, }), }); errdefer workspace.deallocate(allocator); file_ptr.* = try openFileWithPolicy( allocator, workspace, dir, db_name, wal_name, policy, ); errdefer file_ptr.deinit(); const space = try sql.Space.open(file_ptr, .{ .meta_page = meta_root, .roots = &roots, .reserved_page_max = root_max }); return .{ .allocator = allocator, .path = path_info.path, .dir_path = dir_path, .db_name = db_name, .wal_name = wal_name, .temporary_dir = path_info.temporary_dir, .lock_file = lock_file, .workspace = workspace, .file = file_ptr, .file_live = true, .space = space, .documents = try space.rowidTable(documents_root), .search_refs = try space.rowidTable(search_refs_root), .documents_search = try openDocumentSearch(file_ptr), .initialized = initialized, .next_document_rowid = null, .max_wal_bytes = policy.max_wal_bytes, .checkpoint_wal_bytes = policy.checkpoint_wal_bytes, .normal_capacity = policy.normal_capacity, .bulk_capacity = policy.bulk_capacity, .write_mode = .normal, }; } pub fn deinit(self: *Database) void { if (self.file_live) self.file.deinit(); self.allocator.destroy(self.file); self.workspace.deallocate(self.allocator); self.allocator.destroy(self.workspace); self.lock_file.unlock(fs_io); self.lock_file.close(fs_io); if (self.temporary_dir) |dir| { std.Io.Dir.cwd().deleteTree(fs_io, dir) catch {}; self.allocator.free(dir); } self.allocator.free(self.wal_name); self.allocator.free(self.db_name); self.allocator.free(self.dir_path); self.allocator.free(self.path); } pub fn commit(self: *Database) !void { try self.file.syncWal(); } pub fn checkpoint(self: *Database) !void { _ = try nativeCheckpoint(self); } pub fn begin(self: *Database) !void { _ = self; } pub fn getAllDocumentStates(self: *Database, allocator: std.mem.Allocator) !std.StringHashMap(DocumentState) { var map = std.StringHashMap(DocumentState).init(allocator); if (!self.initialized or !try self.rootExists(documents_root)) return map; errdefer { var iter = map.keyIterator(); while (iter.next()) |key| allocator.free(key.*); map.deinit(); } var scan: sql.TableScan = undefined; try self.documents.scan(&scan, allocator, null, null); defer scan.deinit(); while (try scan.next()) |entry| { const doc = try documentViewFromBytes(entry.bytes); const transcript = isTranscriptKind(doc.kind); const records_current = transcript and transcriptRecordsCurrent(doc.transcript_records, doc.content.len); try map.put(try allocator.dupe(u8, doc.path), .{ .rowid = entry.rowid, .mtime = if (transcript and !records_current) -std.math.inf(f64) else doc.mtime, .size = doc.size, .transcript_saturated = records_current and transcriptRecordsSaturate( doc.transcript_records, doc.content.len, ), }); } return map; } pub fn getAllPaths(self: *Database, allocator: std.mem.Allocator) !std.StringHashMap(void) { var map = std.StringHashMap(void).init(allocator); if (!self.initialized or !try self.rootExists(documents_root)) return map; errdefer { var iter = map.keyIterator(); while (iter.next()) |key| allocator.free(key.*); map.deinit(); } var scan: sql.TableScan = undefined; try self.documents.scan(&scan, allocator, null, null); defer scan.deinit(); while (try scan.next()) |entry| { const doc = try documentFromBytes(allocator, entry.bytes); defer freeDocumentRow(allocator, doc); try map.put(try allocator.dupe(u8, doc.path), {}); } return map; } pub fn nativeSearchAvailable(self: *Database) bool { _ = self; return true; } pub fn upsert(self: *Database, doc: data.Document) !void { const rowid = try self.rowidForDocumentPath(doc.path); try self.writeDocument(rowid, doc, true); } pub fn update(self: *Database, rowid: i64, doc: data.Document) !void { std.debug.assert(rowid > 0); try self.writeDocument(rowid, doc, true); } pub fn insertNew(self: *Database, doc: data.Document) !void { const rowid = try self.nextCachedRowid(&self.documents, documents_root, &self.next_document_rowid); try self.writeDocument(rowid, doc, true); } pub fn updateSaturatedTranscript( self: *Database, rowid: i64, mtime_value: f64, size: i64, ) !void { std.debug.assert(rowid > 0); std.debug.assert(size >= 0); const bytes = (try self.documents.get(self.allocator, rowid)) orelse return error.KeyNotFound; defer self.allocator.free(bytes); const doc = try documentViewFromBytes(bytes); if (!isTranscriptKind(doc.kind) or !transcriptRecordsSaturate(doc.transcript_records, doc.content.len)) { return error.InvalidRow; } const mtime = f64Bytes(mtime_value); const indexed_at = f64Bytes(nowSeconds()); const values = [_]sql.RowValue{ .{ .text = doc.source }, .{ .text = doc.path }, .{ .text = doc.kind }, optionalText(doc.project), optionalText(doc.title), .{ .text = doc.content }, optionalText(doc.metadata), .{ .blob = &mtime }, .{ .integer = size }, .{ .blob = &indexed_at }, optionalBlob(doc.transcript_records), }; try self.putValuesWithWalRetry(&self.documents, rowid, &values); self.initialized = true; try self.checkpointIfNeeded(); } pub fn upsertDocumentRow(self: *Database, doc: data.Document) !void { const rowid = try self.rowidForDocumentPath(doc.path); try self.writeDocument(rowid, doc, false); } pub fn updateDocumentRow(self: *Database, rowid: i64, doc: data.Document) !void { std.debug.assert(rowid > 0); try self.writeDocument(rowid, doc, false); } pub fn insertDocumentRowNew(self: *Database, doc: data.Document) !void { const rowid = try self.nextCachedRowid(&self.documents, documents_root, &self.next_document_rowid); try self.writeDocument(rowid, doc, false); } pub fn insertDocumentRowNewSearch(self: *Database, doc: data.Document, batch: *SearchBatch) !void { const rowid = try self.nextCachedRowid(&self.documents, documents_root, &self.next_document_rowid); try self.putDocument(rowid, doc); try self.appendDocumentSearch(rowid, doc, batch); } pub fn upsertDocumentRowSearch(self: *Database, doc: data.Document, batch: *SearchBatch) !void { const rowid = try self.rowidForDocumentPath(doc.path); try self.putDocument(rowid, doc); try self.appendDocumentSearch(rowid, doc, batch); } pub fn updateDocumentRowSearch(self: *Database, rowid: i64, doc: data.Document, batch: *SearchBatch) !void { std.debug.assert(rowid > 0); try self.putDocument(rowid, doc); try self.appendDocumentSearch(rowid, doc, batch); } fn writeDocument(self: *Database, rowid: i64, doc: data.Document, index_search: bool) !void { try self.putDocument(rowid, doc); if (!index_search) return; const text = try documentSearchText(self.allocator, doc.source, doc.path, doc.kind, doc.project, doc.title, doc.content); defer self.allocator.free(text); try self.putDocumentSearch(rowid, text); } pub fn deletePath(self: *Database, path: []const u8) !void { const docs = try self.documentRowidsByPath(self.allocator, path); defer self.allocator.free(docs); for (docs) |rowid| { try self.deleteRowWithWalRetry(&self.documents, rowid); try self.checkpointIfNeeded(); try self.deleteDocumentSearch(rowid); } } pub fn beginBulk(self: *Database) !void { std.debug.assert(self.write_mode == .normal); try self.file.reserve(self.bulk_capacity); self.write_mode = .bulk; } pub fn endBulk(self: *Database) !void { std.debug.assert(self.write_mode == .bulk); _ = try self.checkpointAndReopen(self.normal_capacity); self.write_mode = .normal; } pub fn bulkCheckpoint(self: *Database) !void { std.debug.assert(self.write_mode == .bulk); _ = try self.checkpointAndReopen(self.bulk_capacity); } fn rebuildCheckpointIfNeeded(self: *Database) !void { try self.rebuildCheckpointIfNeededAt(rebuild_checkpoint_wal_bytes); } fn rebuildCheckpointIfNeededAt(self: *Database, wal_bytes: usize) !void { if (self.file.pager.walBytes().len >= wal_bytes) { _ = try self.checkpointAndReopen(self.bulk_capacity); } } pub fn rebuildSearch(self: *Database) !void { try self.rebuildDocumentSearch(); } pub fn clearSearch(self: *Database) !void { try self.clearSearchWithWalRetry(&self.documents_search); try self.rebuildCheckpointIfNeeded(); } pub fn flushDocumentSearchBatch(self: *Database, batch: *SearchBatch) !void { if (try self.rebuildDocumentSearchIfCapabilitiesMissing()) { clearSearchDocumentBatch(self.allocator, batch); return; } flushSearchDocumentBatch(self, &self.documents_search, batch, rebuild_checkpoint_wal_bytes) catch |err| switch (err) { error.SearchIndexRebuildRequired => { clearSearchDocumentBatch(self.allocator, batch); try self.rebuildDocumentSearch(); }, else => return err, }; } pub fn deinitSearchBatch(self: *Database, batch: *SearchBatch) void { freeSearchDocumentBatch(self.allocator, batch); } fn rebuildDocumentSearch(self: *Database) !void { try self.rebuildDocumentSearchWithPolicy(search_rebuild_document_batch_limit, rebuild_checkpoint_wal_bytes); } fn rebuildDocumentSearchWithPolicy(self: *Database, batch_limit: usize, checkpoint_wal_bytes: usize) !void { std.debug.assert(batch_limit > 0); std.debug.assert(checkpoint_wal_bytes > 0); try self.clearSearchWithWalRetry(&self.documents_search); try self.rebuildCheckpointIfNeededAt(checkpoint_wal_bytes); var batch: std.ArrayList(sql.SearchDocument) = .empty; defer freeSearchDocumentBatch(self.allocator, &batch); if (!try self.rootExists(documents_root)) return; var next_rowid: ?i64 = null; var complete = false; while (!complete) { var batch_bytes: usize = 0; complete = true; { var scan: sql.TableScan = undefined; try self.documents.scan(&scan, self.allocator, next_rowid, null); defer scan.deinit(); while (try scan.next()) |entry| { next_rowid = std.math.add(i64, entry.rowid, 1) catch return error.InvalidRow; const doc = try documentFromBytes(self.allocator, entry.bytes); defer freeDocumentRow(self.allocator, doc); { const text = try documentSearchText(self.allocator, doc.source, doc.path, doc.kind, doc.project, doc.title, doc.content); errdefer self.allocator.free(text); batch_bytes = std.math.add(usize, batch_bytes, text.len) catch std.math.maxInt(usize); try batch.append(self.allocator, .{ .rowid = entry.rowid, .text = text }); } if (batch.items.len >= batch_limit or batch_bytes >= search_rebuild_document_batch_bytes) { complete = false; break; } } } try flushSearchDocumentBatch(self, &self.documents_search, &batch, checkpoint_wal_bytes); } } pub fn saveDocumentSearchRefs(self: *Database, rows: []const data.SearchResult) !void { const row_count = std.math.cast(i64, rows.len) orelse return error.SearchReferenceCapacityExceeded; _ = std.math.add(i64, 1_000_000, row_count) catch return error.SearchReferenceCapacityExceeded; var count_buffer: [32]u8 = undefined; const count_text = try std.fmt.bufPrint(&count_buffer, "{d}", .{rows.len}); try preflightSearchRef("documents", 0, count_text); for (rows, 1..) |row, ordinal| { try preflightSearchRef("documents", @intCast(ordinal), row.path); } var write = try self.space.beginWrite(); defer write.deinit(); var row_buffer: [search_ref_row_bytes]u8 = undefined; try putSearchRefIn( &self.search_refs, &write, &row_buffer, searchRefRowid("documents", 0), "documents", 0, count_text, ); for (rows, 1..) |row, ordinal| { try putSearchRefIn( &self.search_refs, &write, &row_buffer, searchRefRowid("documents", @intCast(ordinal)), "documents", @intCast(ordinal), row.path, ); } _ = try write.commit(.{ .durability = .buffered }); self.initialized = true; } pub fn resolveSearchRef(self: *Database, allocator: std.mem.Allocator, scope: []const u8, ref: []const u8) !?[]u8 { if (!std.mem.startsWith(u8, ref, "@")) return null; if (!try self.rootExists(search_refs_root)) return null; const ordinal = std.fmt.parseInt(i64, ref[1..], 10) catch return null; const count_bytes = (try self.search_refs.get(allocator, searchRefRowid(scope, 0))) orelse return null; defer allocator.free(count_bytes); const count_row = try searchRefFromBytes(count_bytes); const count = std.fmt.parseInt(i64, count_row.target, 10) catch return null; if (ordinal <= 0 or ordinal > count) return null; const bytes = (try self.search_refs.get(allocator, searchRefRowid(scope, ordinal))) orelse return null; defer allocator.free(bytes); const row = try searchRefFromBytes(bytes); if (!std.mem.eql(u8, row.scope, scope) or row.ordinal != ordinal) return null; return try allocator.dupe(u8, row.target); } pub fn search( self: *Database, storage: *search_mod.Storage, query: []const u8, filters: data.SearchFilters, ) !data.SearchPage { return try storage.execute(self, query, filters, searchInto); } fn searchInto( self: *Database, allocator: std.mem.Allocator, storage: *search_mod.Storage, query: []const u8, filters: data.SearchFilters, ) !data.SearchPage { if (!try self.rootExists(documents_search_rows_root)) return .{ .rows = &.{}, .total = 0 }; const native_limit = nativeDocumentSearchLimit(filters); var prepared = try self.documents_search.prepare(allocator, query); defer prepared.deinit(allocator); var results = try prepared.executeRanked( &self.documents_search, allocator, native_limit, .{}, ); defer results.deinit(); var rows: std.ArrayList(data.SearchResult) = .empty; errdefer { freeSearchResults(allocator, rows.items); rows.deinit(allocator); } var total: usize = if (native_limit == 0 or filters.role != null) 0 else results.total; for (results.hits) |hit| { const bytes = (try storage.get(&self.documents, hit.rowid)) orelse continue; const doc = try documentViewFromBytes(bytes); if (!documentMatches(doc, filters)) continue; if (filters.role) |role| { const record_bytes = doc.transcript_records orelse continue; const records = try TranscriptRecordsView.init(record_bytes); var record_index: usize = 0; while (record_index < records.count()) : (record_index += 1) { const record = try records.record(record_index, doc.content.len); if (record.role != role or !transcriptRecordMatchesBounds(record, filters)) continue; const content = doc.content[record.start..record.end]; if (!prepared.matches(content)) continue; total += 1; if (filters.limit > 0 and rows.items.len >= filters.limit) continue; try rows.append(allocator, try searchResultFromDocument(allocator, doc, .{ .prepared = &prepared }, hit.rank, record)); } continue; } if (native_limit == 0) total += 1; if (filters.limit > 0 and rows.items.len >= filters.limit) continue; try rows.append(allocator, try searchResultFromDocument(allocator, doc, .{ .prepared = &prepared }, hit.rank, null)); } return .{ .rows = try rows.toOwnedSlice(allocator), .total = total }; } pub fn getDocument(self: *Database, allocator: std.mem.Allocator, path: []const u8) !?data.DocumentRow { if (!try self.rootExists(documents_root)) return null; var scan: sql.TableScan = undefined; try self.documents.scan(&scan, allocator, null, null); defer scan.deinit(); while (try scan.next()) |entry| { const doc = try documentFromBytes(allocator, entry.bytes); if (std.mem.eql(u8, doc.path, path)) return doc; freeDocumentRow(allocator, doc); } return null; } pub fn findDocument(self: *Database, allocator: std.mem.Allocator, fragment: []const u8) !?data.DocumentRow { if (try self.getDocument(allocator, fragment)) |doc| return doc; if (!try self.rootExists(documents_root)) return null; var scan: sql.TableScan = undefined; try self.documents.scan(&scan, allocator, null, null); defer scan.deinit(); while (try scan.next()) |entry| { const doc = try documentFromBytes(allocator, entry.bytes); if (std.mem.indexOf(u8, doc.path, fragment) != null) return doc; freeDocumentRow(allocator, doc); } return null; } pub fn hasDocuments(self: *Database) bool { return self.rootExists(documents_root) catch false; } pub fn indexedAgeNanoseconds(self: *Database) ?i128 { const mtime = self.indexedMtimeNanoseconds() orelse return null; const age = sys.time.realNanoTimestamp() - mtime; return if (age < 0) 0 else age; } fn indexedMtimeNanoseconds(self: *Database) ?i128 { const wal_path = walPath(self.allocator, self.path) catch return newestIndexMtimeNanoseconds(self.path, null); defer self.allocator.free(wal_path); return newestIndexMtimeNanoseconds(self.path, wal_path); } pub fn stats(self: *Database, allocator: std.mem.Allocator) !data.Stats { const wal_path = try walPath(allocator, self.path); defer allocator.free(wal_path); if (!try self.rootExists(documents_root)) return .{ .total = 0, .by_kind = &.{}, .by_source = &.{}, .total_content_bytes = 0, .db_bytes = fileSize(self.path) + fileSize(wal_path), .last_indexed = null, }; var by_kind_map = std.StringHashMap(i64).init(allocator); defer deinitCountMap(allocator, &by_kind_map); var by_source_map = std.StringHashMap(i64).init(allocator); defer deinitCountMap(allocator, &by_source_map); var total: i64 = 0; var total_content_bytes: i64 = 0; var last_indexed: ?f64 = null; var scan: sql.TableScan = undefined; try self.documents.scan(&scan, allocator, null, null); defer scan.deinit(); while (try scan.next()) |entry| { const doc = try documentFromBytes(allocator, entry.bytes); defer freeDocumentRow(allocator, doc); total += 1; total_content_bytes += doc.size; if (last_indexed == null or doc.indexed_at > last_indexed.?) last_indexed = doc.indexed_at; try incrementCount(allocator, &by_kind_map, doc.kind); try incrementCount(allocator, &by_source_map, doc.source); } return .{ .total = total, .by_kind = try countsFromMap(allocator, &by_kind_map), .by_source = try countsFromMap(allocator, &by_source_map), .total_content_bytes = total_content_bytes, .db_bytes = fileSize(self.path) + fileSize(wal_path), .last_indexed = last_indexed, }; } pub fn optimize(self: *Database, rebuild_search: bool, vacuum: bool) !OptimizeReport { const wal_path = try walPath(self.allocator, self.path); defer self.allocator.free(wal_path); const before = fileSize(self.path) + fileSize(wal_path); if (rebuild_search) try self.rebuildSearch(); const checkpoint_result = try nativeCheckpoint(self); if (vacuum) try self.file.flush(); const after = fileSize(self.path) + fileSize(wal_path); return .{ .db_path = self.path, .rebuild_search = rebuild_search, .vacuum = vacuum, .before = before, .after = after, .checkpoint = checkpoint_result }; } fn putDocument(self: *Database, rowid: i64, doc: data.Document) !void { const mtime = f64Bytes(doc.mtime); const indexed_at = f64Bytes(nowSeconds()); const transcript_records = if (doc.transcript_records) |records| try encodeTranscriptRecords(self.allocator, records) else null; defer if (transcript_records) |bytes| self.allocator.free(bytes); const values = [_]sql.RowValue{ .{ .text = doc.source }, .{ .text = doc.path }, .{ .text = doc.kind }, optionalText(doc.project), optionalText(doc.title), .{ .text = documentContent(doc.content) }, optionalText(doc.metadata), .{ .blob = &mtime }, .{ .integer = doc.size }, .{ .blob = &indexed_at }, optionalBlob(transcript_records), }; try self.putValuesWithWalRetry(&self.documents, rowid, &values); if (self.next_document_rowid) |next| { if (rowid >= next) self.next_document_rowid = rowid + 1; } self.initialized = true; try self.checkpointIfNeeded(); } fn appendDocumentSearch(self: *Database, rowid: i64, doc: data.Document, batch: *SearchBatch) !void { const text = try documentSearchText(self.allocator, doc.source, doc.path, doc.kind, doc.project, doc.title, doc.content); errdefer self.allocator.free(text); try batch.append(self.allocator, .{ .rowid = rowid, .text = text }); } fn checkpointAndReopen(self: *Database, capacity: sql.PagerCapacity) !Checkpoint { const checkpoint_result = try nativeCheckpoint(self); try self.reopenFile(capacity); return checkpoint_result; } fn reopenFile(self: *Database, capacity: sql.PagerCapacity) !void { var dir = try openDir(self.dir_path); defer dir.close(fs_io); self.file.deinit(); self.file_live = false; self.file.* = try sql.FileDatabase.open( self.allocator, self.workspace, dir, .{ .paths = .{ .database = self.db_name, .wal = self.wal_name }, .header = databaseHeader(), .max_wal_bytes = self.max_wal_bytes, }, ); self.file_live = true; try self.file.reserve(capacity); self.space = try sql.Space.open(self.file, .{ .meta_page = meta_root, .roots = &roots, .reserved_page_max = root_max }); self.documents = try self.space.rowidTable(documents_root); self.search_refs = try self.space.rowidTable(search_refs_root); self.documents_search = try openDocumentSearch(self.file); } fn writeCapacity(self: *const Database) sql.PagerCapacity { return switch (self.write_mode) { .normal => self.normal_capacity, .bulk => self.bulk_capacity, }; } fn checkpointIfNeeded(self: *Database) !void { if (self.file.pager.walBytes().len >= self.checkpoint_wal_bytes) { _ = try self.checkpointAndReopen(self.writeCapacity()); } } fn checkpointForWalRetry(self: *Database) !void { _ = try self.checkpointAndReopen(self.writeCapacity()); } fn retryWalMutation(self: *Database, comptime mutation: anytype, args: anytype) !void { for (0..2) |attempt| { @call(.auto, mutation, args) catch |err| switch (err) { error.WalLimitExceeded => { if (attempt != 0) return err; try self.checkpointForWalRetry(); continue; }, else => return err, }; return; } unreachable; } fn putValuesWithWalRetry( self: *Database, table: *sql.RowIdTable, rowid: i64, values: []const sql.RowValue, ) !void { try self.retryWalMutation(putValuesMutation, .{ table, self.allocator, rowid, values }); } fn deleteRowWithWalRetry(self: *Database, table: *sql.RowIdTable, rowid: i64) !void { try self.retryWalMutation(deleteRowMutation, .{ table, rowid }); } fn putDocumentSearchWithWalRetry(self: *Database, rowid: i64, value: []const u8) !void { try self.retryWalMutation( putSearchMutation, .{ &self.documents_search, self.allocator, rowid, value }, ); } fn deleteDocumentSearchWithWalRetry(self: *Database, rowid: i64) !void { try self.retryWalMutation( deleteSearchMutation, .{ &self.documents_search, self.allocator, rowid }, ); } fn clearSearchWithWalRetry(self: *Database, search_index: *sql.Search) !void { try self.retryWalMutation(clearSearchMutation, .{ search_index, self.allocator }); } fn loadSearchBatchWithWalRetry( self: *Database, search_index: *sql.Search, documents: []const sql.SearchDocument, ) !void { try self.retryWalMutation( loadSearchBatchMutation, .{ search_index, self.allocator, documents }, ); } fn putDocumentSearch(self: *Database, rowid: i64, text: []const u8) !void { if (try self.rebuildDocumentSearchIfCapabilitiesMissing()) return; self.putDocumentSearchWithWalRetry(rowid, text) catch |err| switch (err) { error.SearchIndexRebuildRequired => return try self.rebuildDocumentSearch(), else => return err, }; try self.checkpointIfNeeded(); } fn deleteDocumentSearch(self: *Database, rowid: i64) !void { _ = try self.rebuildDocumentSearchIfCapabilitiesMissing(); self.deleteDocumentSearchWithWalRetry(rowid) catch |err| switch (err) { error.KeyNotFound => return, error.SearchIndexRebuildRequired => return try self.rebuildDocumentSearch(), else => return err, }; try self.checkpointIfNeeded(); } fn rebuildDocumentSearchIfCapabilitiesMissing(self: *Database) !bool { if (!documents_search_index_phrase_pairs) return false; if ((try self.documents_search.capabilities(self.allocator)).phrase_pair_postings) return false; if (!try self.tableHasRows(documents_root, &self.documents)) return false; try self.rebuildDocumentSearch(); return true; } fn rowidForDocumentPath(self: *Database, path: []const u8) !i64 { if (!self.initialized or !try self.rootExists(documents_root)) return 1; var scan: sql.TableScan = undefined; try self.documents.scan(&scan, self.allocator, null, null); defer scan.deinit(); var max_rowid: i64 = 0; while (try scan.next()) |entry| { max_rowid = @max(max_rowid, entry.rowid); const view = try sql.RowView.init(entry.bytes); if (std.mem.eql(u8, (try view.column(1)).text, path)) return entry.rowid; } return max_rowid + 1; } fn nextCachedRowid(self: *Database, table: *const sql.RowIdTable, root_page: u32, cache: *?i64) !i64 { if (!self.initialized or !try self.rootExists(root_page)) { cache.* = 2; return 1; } if (cache.*) |rowid| { cache.* = rowid + 1; return rowid; } var scan: sql.TableScan = undefined; try table.scan(&scan, self.allocator, null, null); defer scan.deinit(); var max_rowid: i64 = 0; while (try scan.next()) |entry| max_rowid = @max(max_rowid, entry.rowid); const rowid = max_rowid + 1; cache.* = rowid + 1; return rowid; } fn documentRowidsByPath(self: *Database, allocator: std.mem.Allocator, path: []const u8) ![]i64 { if (!try self.rootExists(documents_root)) return &.{}; var rowids: std.ArrayList(i64) = .empty; var scan: sql.TableScan = undefined; try self.documents.scan(&scan, allocator, null, null); defer scan.deinit(); while (try scan.next()) |entry| { const view = try sql.RowView.init(entry.bytes); if (std.mem.eql(u8, (try view.column(1)).text, path)) try rowids.append(allocator, entry.rowid); } return try rowids.toOwnedSlice(allocator); } fn documentByRowid(self: *Database, allocator: std.mem.Allocator, rowid: i64) !?data.DocumentRow { const bytes = (try self.documents.get(allocator, rowid)) orelse return null; defer allocator.free(bytes); return try documentFromBytes(allocator, bytes); } fn tableHasRows(self: *Database, root_page: u32, table: *const sql.RowIdTable) !bool { if (!try self.rootExists(root_page)) return false; var scan: sql.TableScan = undefined; try table.scan(&scan, self.allocator, null, null); defer scan.deinit(); return (try scan.next()) != null; } fn rootExists(self: *Database, root_page: u32) !bool { var read = try self.file.beginRead(); defer read.deinit(); const snapshot = read.snapshot(); var image: [sql.page.size]u8 = undefined; if (!try snapshot.copyPage(root_page, &image)) return false; _ = sql.page.kind(&image) catch return false; return true; }};Source: tools/glom/src/root.zig:16
zig
pub const Database = database.Database;Complete caller list for Database.deinit
14 direct callers.
tools.glom.src.main.doctorReport[function] — private; no exact target attools/glom/src/main.zig:992in nearest public ownertools.glom.src.maintools.glom.src.main.runContext[function] — private; no exact target attools/glom/src/main.zig:563in nearest public ownertools.glom.src.maintools.glom.src.main.runIndex[function] — private; no exact target attools/glom/src/main.zig:483in nearest public ownertools.glom.src.maintools.glom.src.main.runOptimize[function] — private; no exact target attools/glom/src/main.zig:607in nearest public ownertools.glom.src.maintools.glom.src.main.runSearch[function] — private; no exact target attools/glom/src/main.zig:513in nearest public ownertools.glom.src.maintools.glom.src.main.runShow[function] — private; no exact target attools/glom/src/main.zig:625in nearest public ownertools.glom.src.maintools.glom.src.main.runStats[function] — private; no exact target attools/glom/src/main.zig:598in nearest public ownertools.glom.src.maintools.glom.src.main.test_empty_result_pages_preserve_previous_search_refs[function] — test; no exact target attools/glom/src/main.zig:1764in nearest public ownertools.glom.src.maintools.glom.src.main.test_search_recovery_rebuilds_an_invalid_index_outside_strict_storage[function] — test; no exact target attools/glom/src/main.zig:1797in nearest public ownertools.glom.src.maintools.glom.src.test_Glom_index_preflights_every_changed_whole-file_input_before_database_mutation[function] — test; no exact target attools/glom/src/index.zig:1549in nearest public ownertiny.glom.indextools.glom.src.test_Glom_index_reads_only_the_probe_for_a_saturated_transcript[function] — test; no exact target attools/glom/src/index.zig:1611in nearest public ownertiny.glom.indextools.glom.src.test_Glom_index_reports_a_transcript_record_larger_than_its_window[function] — test; no exact target attools/glom/src/index.zig:1767in nearest public ownertiny.glom.indextools.glom.src.test_index_commits_native_search_rows_for_reopen[function] — test; no exact target attools/glom/src/index.zig:1500in nearest public ownertiny.glom.indextiny.smg.storage.store.Store.close[method] attools/smg/src/storage/store.zig:23
Complete caller list for Database.init
13 direct callers.
tools.glom.src.main.doctorReport[function] — private; no exact target attools/glom/src/main.zig:992in nearest public ownertools.glom.src.maintools.glom.src.main.runContext[function] — private; no exact target attools/glom/src/main.zig:563in nearest public ownertools.glom.src.maintools.glom.src.main.runIndex[function] — private; no exact target attools/glom/src/main.zig:483in nearest public ownertools.glom.src.maintools.glom.src.main.runOptimize[function] — private; no exact target attools/glom/src/main.zig:607in nearest public ownertools.glom.src.maintools.glom.src.main.runSearch[function] — private; no exact target attools/glom/src/main.zig:513in nearest public ownertools.glom.src.maintools.glom.src.main.runShow[function] — private; no exact target attools/glom/src/main.zig:625in nearest public ownertools.glom.src.maintools.glom.src.main.runStats[function] — private; no exact target attools/glom/src/main.zig:598in nearest public ownertools.glom.src.maintools.glom.src.main.test_empty_result_pages_preserve_previous_search_refs[function] — test; no exact target attools/glom/src/main.zig:1764in nearest public ownertools.glom.src.maintools.glom.src.main.test_search_recovery_rebuilds_an_invalid_index_outside_strict_storage[function] — test; no exact target attools/glom/src/main.zig:1797in nearest public ownertools.glom.src.maintools.glom.src.test_Glom_index_preflights_every_changed_whole-file_input_before_database_mutation[function] — test; no exact target attools/glom/src/index.zig:1549in nearest public ownertiny.glom.indextools.glom.src.test_Glom_index_reads_only_the_probe_for_a_saturated_transcript[function] — test; no exact target attools/glom/src/index.zig:1611in nearest public ownertiny.glom.indextools.glom.src.test_Glom_index_reports_a_transcript_record_larger_than_its_window[function] — test; no exact target attools/glom/src/index.zig:1767in nearest public ownertiny.glom.indextools.glom.src.test_index_commits_native_search_rows_for_reopen[function] — test; no exact target attools/glom/src/index.zig:1500in nearest public ownertiny.glom.index
Complete call list for Database.stats
8 direct calls.
tools.glom.src.database.Database.rootExists[method] — private; no exact target attools/glom/src/database.zig:896in nearest public ownertiny.glom.indextools.glom.src.database.countsFromMap[function] — private; no exact target attools/glom/src/database.zig:1447in nearest public ownertiny.glom.indextools.glom.src.database.deinitCountMap[function] — private; no exact target attools/glom/src/database.zig:1441in nearest public ownertiny.glom.indextools.glom.src.database.documentFromBytes[function] — private; no exact target attools/glom/src/database.zig:1091in nearest public ownertiny.glom.indextools.glom.src.database.fileSize[function] — private; no exact target attools/glom/src/database.zig:1511in nearest public ownertiny.glom.indextools.glom.src.database.freeDocumentRow[function] — private; no exact target attools/glom/src/database.zig:1422in nearest public ownertiny.glom.indextools.glom.src.database.incrementCount[function] — private; no exact target attools/glom/src/database.zig:1433in nearest public ownertiny.glom.indextools.glom.src.database.walPath[function] — private; no exact target attools/glom/src/database.zig:1550in nearest public ownertiny.glom.index
Complete call list for Database.updateSaturatedTranscript
9 direct calls.
tools.glom.src.database.Database.checkpointIfNeeded[method] — private; no exact target attools/glom/src/database.zig:742in nearest public ownertiny.glom.indextools.glom.src.database.Database.putValuesWithWalRetry[method] — private; no exact target attools/glom/src/database.zig:767in nearest public ownertiny.glom.indextools.glom.src.database.documentViewFromBytes[function] — private; no exact target attools/glom/src/database.zig:1108in nearest public ownertiny.glom.indextools.glom.src.database.f64Bytes[function] — private; no exact target attools/glom/src/database.zig:1179in nearest public ownertiny.glom.indextools.glom.src.database.nowSeconds[function] — private; no exact target attools/glom/src/database.zig:1506in nearest public ownertiny.glom.indextools.glom.src.database.optionalBlob[function] — private; no exact target attools/glom/src/database.zig:1138in nearest public ownertiny.glom.indextools.glom.src.database.optionalText[function] — private; no exact target attools/glom/src/database.zig:1134in nearest public ownertiny.glom.indextools.glom.src.database.transcriptRecordsSaturate[function] — private; no exact target attools/glom/src/database.zig:1255in nearest public ownertiny.glom.indextools.glom.src.isTranscriptKind[function] — private; no exact target attools/glom/src/index.zig:504in nearest public ownertiny.glom.index
Audit
| Definitions | 36 |
|---|---|
| Public names | 36 |
| Members | 21 |
| Version | 26.7.0 |
| Revision | daab053ee433 |