Skip to documentation
SLOP

tiny.glom.Database

Reference tiny.glom Database

Defined in tiny.glom.

API (56)

Actions

Public operations.

Fields and members

Public fields and members.

No direct callersNo direct callstiny.glomDatabase
Static calls · unresolved targets: unknown · external targets: unknown.

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;
Called byCallsNo direct callersprivate; no linktools.glom.src.database.DatabasecheckpointAndReopenDatabasebulkCheckpoint
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest; no linktools.glom.src.maintest: search recovery rebuilds an inv...private; no linktools.glom.src.database.DatabaseclearSearchWithWalRetryprivate; no linktools.glom.src.database.DatabaserebuildCheckpointIfNeededDatabaseclearSearch
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsprivate; no linktools.glom.src.maindoctorReportprivate; no linktools.glom.src.mainrunContextprivate; no linktools.glom.src.mainrunIndexprivate; no linktools.glom.src.mainrunOptimizeprivate; no linktools.glom.src.mainrunSearch+9 moreDatabasedeinit
Static calls · unresolved targets: 1 · external targets: 5.
Called byCallsNo direct callersprivate; no linktools.glom.src.databasefreeSearchDocumentBatchDatabasedeinitSearchBatch
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate; no linktools.glom.src.database.DatabasecheckpointIfNeededprivate; no linktools.glom.src.database.DatabasedeleteDocumentSearchprivate; no linktools.glom.src.database.DatabasedeleteRowWithWalRetryprivate; no linktools.glom.src.database.DatabasedocumentRowidsByPathDatabasedeletePath
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callersprivate; no linktools.glom.src.database.DatabasecheckpointAndReopenDatabaseendBulk
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate; no linktools.glom.src.mainrunShowDatabasegetDocumentprivate; no linktools.glom.src.database.DatabaserootExistsprivate; no linktools.glom.src.databasedocumentFromBytesprivate; no linktools.glom.src.databasefreeDocumentRowDatabasefindDocument
Static calls · unresolved targets: 0 · external targets: 3.
Called byCallsNo direct callersprivate; no linktools.glom.src.database.DatabaserebuildDocumentSearchprivate; no linktools.glom.src.database.DatabaserebuildDocumentSearchIfCapabilitiesMi...private; no linktools.glom.src.databaseclearSearchDocumentBatchprivate; no linktools.glom.src.databaseflushSearchDocumentBatchDatabaseflushDocumentSearchBatch
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate; no linktools.glom.src.database.DatabaserootExistsprivate; no linktools.glom.src.databasedocumentViewFromBytesprivate; no linktools.glom.src.databasetranscriptRecordsCurrentprivate; no linktools.glom.src.databasetranscriptRecordsSaturateprivate; no linktools.glom.srcisTranscriptKindDatabasegetAllDocumentStates
Static calls · unresolved targets: 1 · external targets: 8.
Called byCallsprivate; no linktools.glom.src.maindoctorReportprivate; no linktools.glom.src.database.DatabaserootExistsprivate; no linktools.glom.src.databasedocumentFromBytesprivate; no linktools.glom.src.databasefreeDocumentRowDatabasegetAllPaths
Static calls · unresolved targets: 1 · external targets: 8.
Called byCallsDatabasefindDocumentprivate; no linktools.glom.src.mainrunContexttest; no linktools.glom.srctest: Glom index reads only the probe...private; no linktools.glom.src.database.DatabaserootExistsprivate; no linktools.glom.src.databasedocumentFromBytesprivate; no linktools.glom.src.databasefreeDocumentRowDatabasegetDocument
Static calls · unresolved targets: 0 · external targets: 3.
Called byCallsNo direct callersprivate; no linktools.glom.src.database.DatabaserootExistsDatabasehasDocuments
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate; no linktools.glom.src.database.DatabaseindexedMtimeNanosecondsDatabaseindexedAgeNanoseconds
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsprivate; no linktools.glom.src.maindoctorReportprivate; no linktools.glom.src.mainrunContextprivate; no linktools.glom.src.mainrunIndexprivate; no linktools.glom.src.mainrunOptimizeprivate; no linktools.glom.src.mainrunSearch+8 moreprivate; no linktools.glom.src.database.DatabaseinitWithPolicyDatabaseinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate; no linktools.glom.src.database.DatabasenextCachedRowidprivate; no linktools.glom.src.database.DatabasewriteDocumentDatabaseinsertDocumentRowNew
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate; no linktools.glom.src.database.DatabaseappendDocumentSearchprivate; no linktools.glom.src.database.DatabasenextCachedRowidprivate; no linktools.glom.src.database.DatabaseputDocumentDatabaseinsertDocumentRowNewSearch
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate; no linktools.glom.src.database.DatabasenextCachedRowidprivate; no linktools.glom.src.database.DatabasewriteDocumentDatabaseinsertNew
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsprivate; no linktools.glom.src.maindoctorReportDatabasenativeSearchAvailable
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate; no linktools.glom.src.mainrunOptimizeDatabaserebuildSearchprivate; no linktools.glom.src.databasefileSizeprivate; no linktools.glom.src.databasewalPathDatabaseoptimize
Static calls · unresolved targets: 1 · external targets: 2.
Called byCallsDatabaseoptimizeprivate; no linktools.glom.src.database.DatabaserebuildDocumentSearchDatabaserebuildSearch
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate; no linktools.glom.src.mainrunShowtest; no linktools.glom.src.maintest: empty result pages preserve pre...private; no linktools.glom.src.database.DatabaserootExistsprivate; no linktools.glom.src.databasesearchRefFromBytesprivate; no linktools.glom.src.databasesearchRefRowidDatabaseresolveSearchRef
Static calls · unresolved targets: 1 · external targets: 2.
Called byCallsNo direct callersprivate; no linktools.glom.src.databasepreflightSearchRefprivate; no linktools.glom.src.databaseputSearchRefInprivate; no linktools.glom.src.databasesearchRefRowidDatabasesaveDocumentSearchRefs
Static calls · unresolved targets: 0 · external targets: 3.
Called byCallsNo direct callstest; no linktools.glom.src.maintest: search recovery rebuilds an inv...Databasesearch
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsprivate; no linktools.glom.src.maindoctorReportprivate; no linktools.glom.src.mainrunStatstest; no linktools.glom.srctest: Glom index preflights every cha...private; no linktools.glom.src.database.DatabaserootExistsprivate; no linktools.glom.src.databasecountsFromMapprivate; no linktools.glom.src.databasedeinitCountMapprivate; no linktools.glom.src.databasedocumentFromBytesprivate; no linktools.glom.src.databasefileSize+3 moreDatabasestats
Static calls · unresolved targets: 0 · external targets: 4.
Called byCallsNo direct callersprivate; no linktools.glom.src.database.DatabasewriteDocumentDatabaseupdate
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate; no linktools.glom.src.database.DatabasewriteDocumentDatabaseupdateDocumentRow
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate; no linktools.glom.src.database.DatabaseappendDocumentSearchprivate; no linktools.glom.src.database.DatabaseputDocumentDatabaseupdateDocumentRowSearch
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate; no linktools.glom.src.database.DatabasecheckpointIfNeededprivate; no linktools.glom.src.database.DatabaseputValuesWithWalRetryprivate; no linktools.glom.src.databasedocumentViewFromBytesprivate; no linktools.glom.src.databasef64Bytesprivate; no linktools.glom.src.databasenowSeconds+4 moreDatabaseupdateSaturatedTranscript
Static calls · unresolved targets: 1 · external targets: 1.
Called byCallstest; no linktools.glom.src.maintest: search recovery rebuilds an inv...private; no linktools.glom.src.database.DatabaserowidForDocumentPathprivate; no linktools.glom.src.database.DatabasewriteDocumentDatabaseupsert
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate; no linktools.glom.src.database.DatabaserowidForDocumentPathprivate; no linktools.glom.src.database.DatabasewriteDocumentDatabaseupsertDocumentRow
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate; no linktools.glom.src.database.DatabaseappendDocumentSearchprivate; no linktools.glom.src.database.DatabaseputDocumentprivate; no linktools.glom.src.database.DatabaserowidForDocumentPathDatabaseupsertDocumentRowSearch
Static calls · unresolved targets: 0 · external targets: 0.

Complete caller list for Database.deinit

14 direct callers.

Complete caller list for Database.init

13 direct callers.

Complete call list for Database.stats

8 direct calls.

Complete call list for Database.updateSaturatedTranscript

9 direct calls.

Audit

Definitions36
Public names36
Members21
Version26.7.0
Revisiondaab053ee433