Skip to documentation
SLOP

tiny.smg.storage.store

Reference tiny.smg storage store

Defined in storage.

API (13)

Actions

Public operations.

Types and contracts

Public types and contracts.

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

Source

Called byCallsNo direct callersstorage.store.Storeclosestorage.store.Publisherclose
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersstorage.syncfinishRefreshstorage.store.Publisherfinish
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate; no linktools.smg.src.storage.storereleaseLockstorage.store.ReadStoreclose
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsstorage.store.Publisherclosetiny.glomDatabasedeinitprivate; no linktools.smg.src.storage.storereleaseLockstorage.store.Storeclose
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsconceptspublishstorage.graphpublishMatchedTimedstorage.graphpublishTimedtest; no linktools.smg.src.storage.storetest: expected publication rejects st...private; no linktools.smg.src.storage.storeopenWriterstorage.syncbeginRefreshstorage.storebeginPublish
Static calls · unresolved targets: 1 · external targets: 2.
Called byCallstest; no linktools.smg.src.storage.storetest: read store rejects an incomplet...private; no linktools.smg.src.storage.summarypublishTestingGraphprivate; no linktools.smg.src.storage.storeopenWriterstorage.syncbeginRefreshstorage.storebeginRepairPublish
Static calls · unresolved targets: 1 · external targets: 0.
Called byCallssearchrebuildStoredprivate; no linktools.smg.src.searchreplaceAndSyncForTestprivate; no linktools.smg.src.searchreplaceGraphForTestprivate; no linktools.smg.src.searchsyncStoredForTesttest; no linktools.smg.src.searchtest: stored search document plan pre...+19 moreprivate; no linktools.smg.src.storage.storeopenWriterstorage.storeopen
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsrulesaddStoredrulesremoveStoredrulessetNamespaceHandleImportBaselineStoredtest; no linktools.smg.src.storage.storetest: read store rejects an incomplet...private; no linktools.smg.src.storage.storeopenWriterstorage.storeopenMutation
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate; no linktools.smg.src.command.about.commandstoredprivate; no linktools.smg.src.command.check.refreshloadSourcecommand.sessionloadGraphAndConceptsconceptsloadSnapshotsearchsearchStored+15 moreprivate; no linktools.smg.src.storage.storereadStorestorage.storeopenRead
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest; no linktools.smg.src.storage.storetest: read store observation preserve...private; no linktools.smg.src.storage.storereadStorestorage.storetryOpenRead
Static calls · unresolved targets: 0 · external targets: 0.

Source: tools/smg/src/storage/root.zig:10

zig
pub const store = @import("store.zig");

Source: tools/smg/src/storage/store.zig

zig
const std = @import("std");const sql = @import("sql");const sys = @import("sys");const smg = @import("../root.zig");const storage = smg.storage;const database = storage.database;const paths = storage.paths;const sync = storage.sync;const text = smg.text;const fs_io = std.Options.debug_io;const gitignore_content = paths.database_file_name ++ "\n" ++ paths.wal_file_name ++    "\nnames.bin\nnames.bin.tmp\nsearch.sql\nsearch.sql.wal\nsearch.sql.tmp\nsearch.sql.tmp.wal\nsummary.json\nsummary.json.tmp\n" ++    paths.lock_file_name ++ "\n" ++ paths.refresh_file_name ++ "\n" ++    paths.source_file_name ++ "\n" ++ paths.source_temp_file_name ++ ".*\n";pub const Store = struct {    allocator: std.mem.Allocator,    dir_path: []const u8,    lock: std.Io.File,    database: database.Database,    pub fn close(self: *Store) void {        self.database.deinit();        releaseLock(&self.lock);        self.allocator.free(self.dir_path);        self.* = undefined;    }};pub const ReadStore = struct {    allocator: std.mem.Allocator,    dir_path: []const u8,    lock: std.Io.File,    reader: database.Reader,    pub fn close(self: *ReadStore) void {        self.reader.deinit();        releaseLock(&self.lock);        self.allocator.free(self.dir_path);        self.* = undefined;    }};pub const Publisher = struct {    store: Store,    root: []const u8,    pub fn finish(self: *Publisher) !void {        try sync.finishRefresh(self.store.allocator, self.root);    }    pub fn close(self: *Publisher) void {        self.store.close();        self.* = undefined;    }};pub fn open(allocator: std.mem.Allocator, root: []const u8, limits: smg.StorageLimits) !Store {    return try openWriter(allocator, root, limits, false);}pub fn openMutation(allocator: std.mem.Allocator, root: []const u8, limits: smg.StorageLimits) !Store {    return try openWriter(allocator, root, limits, true);}pub fn beginRepairPublish(allocator: std.mem.Allocator, root: []const u8, limits: smg.StorageLimits) !Publisher {    var opened = try openWriter(allocator, root, limits, false);    errdefer opened.close();    try sync.beginRefresh(allocator, root);    return .{ .store = opened, .root = root };}pub fn beginPublish(    allocator: std.mem.Allocator,    root: []const u8,    expected_head: sql.Hash,    limits: smg.StorageLimits,) !Publisher {    var opened = try openWriter(allocator, root, limits, true);    errdefer opened.close();    const current_head = (try opened.database.connection.checkout()).head;    if (!sql.version.same(current_head, expected_head)) return error.StaleSourceHead;    try sync.beginRefresh(allocator, root);    return .{ .store = opened, .root = root };}fn openWriter(    allocator: std.mem.Allocator,    root: []const u8,    limits: smg.StorageLimits,    require_complete: bool,) !Store {    const dir_path = try openDirPath(allocator, root);    errdefer allocator.free(dir_path);    var lock = try acquireExclusiveLock(allocator, dir_path);    errdefer releaseLock(&lock);    if (require_complete and try sync.refreshIncomplete(allocator, root)) return error.GraphRefreshIncomplete;    try ensureGitignore(allocator, dir_path, limits);    var dir = try std.Io.Dir.openDirAbsolute(fs_io, dir_path, .{});    defer dir.close(fs_io);    var opened = try database.open(allocator, dir, limits);    errdefer opened.deinit();    try database.checkpointIfWalLarge(&opened);    return .{        .allocator = allocator,        .dir_path = dir_path,        .lock = lock,        .database = opened,    };}pub fn openRead(allocator: std.mem.Allocator, root: []const u8, limits: smg.StorageLimits) !ReadStore {    return readStore(allocator, root, limits, true);}/// Opens the existing graph without waiting for a publisher or repairing storage.pub fn tryOpenRead(    allocator: std.mem.Allocator,    root: []const u8,    limits: smg.StorageLimits,) !ReadStore {    return readStore(allocator, root, limits, false);}fn readStore(    allocator: std.mem.Allocator,    root: []const u8,    limits: smg.StorageLimits,    wait: bool,) !ReadStore {    const dir_path = try existingDirPath(allocator, root);    errdefer allocator.free(dir_path);    var lock = try sharedLock(allocator, dir_path, wait);    errdefer releaseLock(&lock);    if (try sync.refreshIncomplete(allocator, root)) return error.GraphRefreshIncomplete;    var dir = try std.Io.Dir.openDirAbsolute(fs_io, dir_path, .{});    defer dir.close(fs_io);    var reader = switch (try database.openReader(allocator, dir, limits)) {        .ready => |ready| ready,        .repair_required => |reason| return switch (reason) {            .stale_refs, .root_mismatch => error.GraphRefreshIncomplete,            .file => |file_reason| switch (file_reason) {                .wal_too_large => error.WalRecoveryLimitBytesExceeded,                else => error.GraphStoreRepairRequired,            },            .missing_refs, .invalid_refs, .missing_branch => error.GraphStoreRepairRequired,        },    };    errdefer reader.deinit();    if (!try database.readerSchemaReady(&reader)) return error.GraphStoreMigrationRequired;    return .{        .allocator = allocator,        .dir_path = dir_path,        .lock = lock,        .reader = reader,    };}fn existingDirPath(allocator: std.mem.Allocator, root: []const u8) ![]const u8 {    return try std.fs.path.join(allocator, &.{ root, paths.smg_dir_name });}fn openDirPath(allocator: std.mem.Allocator, root: []const u8) ![]const u8 {    const dir_path = try std.fs.path.join(allocator, &.{ root, paths.smg_dir_name });    errdefer allocator.free(dir_path);    try sys.fs.createDirPath(dir_path);    return dir_path;}fn acquireExclusiveLock(allocator: std.mem.Allocator, dir_path: []const u8) !std.Io.File {    const lock_path = try std.fs.path.join(allocator, &.{ dir_path, paths.lock_file_name });    defer allocator.free(lock_path);    var lock = try std.Io.Dir.createFileAbsolute(fs_io, lock_path, .{        .truncate = false,        .read = true,        .permissions = @fromBackingInt(@intCast(0o600)),    });    errdefer lock.close(fs_io);    try lock.lock(fs_io, .exclusive);    try validateLock(lock, lock_path);    return lock;}fn sharedLock(allocator: std.mem.Allocator, dir_path: []const u8, wait: bool) !std.Io.File {    const lock_path = try std.fs.path.join(allocator, &.{ dir_path, paths.lock_file_name });    defer allocator.free(lock_path);    var lock = std.Io.Dir.openFileAbsolute(fs_io, lock_path, .{}) catch |err| switch (err) {        error.FileNotFound => return error.NoProject,        else => return err,    };    errdefer lock.close(fs_io);    if (wait) {        try lock.lock(fs_io, .shared);    } else if (!try lock.tryLock(fs_io, .shared)) return error.GraphStoreBusy;    try validateLock(lock, lock_path);    return lock;}fn validateLock(lock: std.Io.File, lock_path: []const u8) !void {    const current = std.Io.Dir.openFileAbsolute(fs_io, lock_path, .{}) catch |err| switch (err) {        error.FileNotFound => return error.GraphStoreBusy,        else => return err,    };    defer current.close(fs_io);    const held_identity = try sys.fd.identity(lock.handle);    const current_identity = try sys.fd.identity(current.handle);    if (!std.meta.eql(held_identity, current_identity)) return error.GraphStoreBusy;}fn releaseLock(lock: *std.Io.File) void {    lock.unlock(fs_io);    lock.close(fs_io);}fn ensureGitignore(    allocator: std.mem.Allocator,    dir_path: []const u8,    limits: smg.StorageLimits,) !void {    const path = try std.fs.path.join(allocator, &.{ dir_path, ".gitignore" });    defer allocator.free(path);    if (sys.fs.readFileAlloc(allocator, path, limits.max_gitignore_bytes)) |existing| {        defer allocator.free(existing);        if (std.mem.eql(u8, existing, gitignore_content)) return;    } else |_| {}    try text.writeFile(path, gitignore_content);}test "store open creates directory gitignore and lock" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    const root = try testRoot(allocator, 0);    defer sys.fs.deleteTree(root) catch {};    var opened = try open(allocator, root, smg.default_limits.storage);    const dir_path = try allocator.dupe(u8, opened.dir_path);    opened.close();    const gitignore_path = try std.fs.path.join(allocator, &.{ dir_path, ".gitignore" });    const gitignore = try sys.fs.readFileAlloc(allocator, gitignore_path, 4096);    try std.testing.expectEqualStrings(gitignore_content, gitignore);    try std.testing.expect(std.mem.indexOf(u8, gitignore, "search.sql.version") == null);    const lock_path = try std.fs.path.join(allocator, &.{ dir_path, paths.lock_file_name });    try std.testing.expect(sys.fs.exists(lock_path));}test "read store opens after writer closes" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    const root = try testRoot(allocator, 1);    defer sys.fs.deleteTree(root) catch {};    var writer = try open(allocator, root, smg.default_limits.storage);    writer.close();    var reader = try openRead(allocator, root, smg.default_limits.storage);    reader.close();}test "read store rejects an incomplete graph refresh until a writer completes it" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    const root = try testRoot(allocator, 2);    defer sys.fs.deleteTree(root) catch {};    var writer = try open(allocator, root, smg.default_limits.storage);    writer.close();    var interrupted = try beginRepairPublish(allocator, root, smg.default_limits.storage);    interrupted.close();    try std.testing.expectError(        error.GraphRefreshIncomplete,        openRead(allocator, root, smg.default_limits.storage),    );    try std.testing.expectError(        error.GraphRefreshIncomplete,        openMutation(allocator, root, smg.default_limits.storage),    );    var publisher = try beginRepairPublish(allocator, root, smg.default_limits.storage);    errdefer publisher.close();    try publisher.finish();    publisher.close();    var reader = try openRead(allocator, root, smg.default_limits.storage);    reader.close();}test "expected publication rejects stale heads before arming the refresh marker" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    const root = try testRoot(allocator, 6);    defer sys.fs.deleteTree(root) catch {};    var writer = try open(allocator, root, smg.default_limits.storage);    const source_head = (try writer.database.connection.checkout()).head;    writer.close();    var matching = try beginPublish(        allocator,        root,        source_head,        smg.default_limits.storage,    );    try std.testing.expect(try sync.refreshIncomplete(allocator, root));    try matching.finish();    matching.close();    try std.testing.expect(!try sync.refreshIncomplete(allocator, root));    var winner = try open(allocator, root, smg.default_limits.storage);    try database.edits.apply(&winner.database, .{ .concept_document_puts = &.{.{        .rowid = 1,        .name = "winner",        .document = "{\"name\":\"winner\"}",    }} });    const winner_head = try database.commit(&winner.database);    winner.close();    try std.testing.expect(!sql.version.same(source_head, winner_head));    try std.testing.expectError(        error.StaleSourceHead,        beginPublish(allocator, root, source_head, smg.default_limits.storage),    );    try std.testing.expect(!try sync.refreshIncomplete(allocator, root));    var reopened = try open(allocator, root, smg.default_limits.storage);    try std.testing.expect(sql.version.same(winner_head, (try reopened.database.connection.checkout()).head));    reopened.close();}test "read store shared locks coexist and exclude a publisher" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    const root = try testRoot(allocator, 3);    defer sys.fs.deleteTree(root) catch {};    var writer = try open(allocator, root, smg.default_limits.storage);    writer.close();    const dir_path = try existingDirPath(allocator, root);    const lock_path = try std.fs.path.join(allocator, &.{ dir_path, paths.lock_file_name });    {        var first = try sharedLock(allocator, dir_path, true);        defer releaseLock(&first);        var second = try sharedLock(allocator, dir_path, true);        defer releaseLock(&second);        var exclusive = try std.Io.Dir.openFileAbsolute(fs_io, lock_path, .{});        defer exclusive.close(fs_io);        try std.testing.expect(!try exclusive.tryLock(fs_io, .exclusive));    }}test "store lock validation accepts the current shared and exclusive lock" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    const root = try testRoot(allocator, 7);    defer sys.fs.deleteTree(root) catch {};    const dir_path = try openDirPath(allocator, root);    const lock_path = try std.fs.path.join(allocator, &.{ dir_path, paths.lock_file_name });    for ([_]bool{ false, true }) |shared| {        var lock = if (shared)            try sharedLock(allocator, dir_path, false)        else            try acquireExclusiveLock(allocator, dir_path);        defer releaseLock(&lock);        try validateLock(lock, lock_path);        var contender = try std.Io.Dir.openFileAbsolute(fs_io, lock_path, .{});        defer contender.close(fs_io);        try std.testing.expect(!try contender.tryLock(fs_io, .exclusive));    }}test "store lock validation refuses retirement while the replacement remains exclusive" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    const root = try testRoot(allocator, 8);    defer sys.fs.deleteTree(root) catch {};    const dir_path = try openDirPath(allocator, root);    const retired_path = try std.fs.path.join(allocator, &.{ root, "retired" });    const lock_path = try std.fs.path.join(allocator, &.{ dir_path, paths.lock_file_name });    for ([_]bool{ false, true }) |shared| {        var lock = if (shared)            try sharedLock(allocator, dir_path, false)        else            try acquireExclusiveLock(allocator, dir_path);        defer releaseLock(&lock);        try sys.fs.rename(dir_path, retired_path);        defer sys.fs.deleteTree(retired_path) catch {};        try sys.fs.createDirPath(dir_path);        var replacement = try acquireExclusiveLock(allocator, dir_path);        defer releaseLock(&replacement);        try std.testing.expectError(error.GraphStoreBusy, validateLock(lock, lock_path));        try std.testing.expectError(error.GraphStoreBusy, sharedLock(allocator, dir_path, false));        try validateLock(replacement, lock_path);    }}test "store lock validation refuses retirement without creating a replacement" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    const root = try testRoot(allocator, 9);    defer sys.fs.deleteTree(root) catch {};    const dir_path = try openDirPath(allocator, root);    const retired_path = try std.fs.path.join(allocator, &.{ root, "retired" });    const lock_path = try std.fs.path.join(allocator, &.{ dir_path, paths.lock_file_name });    var lock = try acquireExclusiveLock(allocator, dir_path);    defer releaseLock(&lock);    try sys.fs.rename(dir_path, retired_path);    try std.testing.expectError(error.GraphStoreBusy, validateLock(lock, lock_path));    try std.testing.expect(!sys.fs.exists(dir_path));}test "read store observation preserves absence and refuses a held writer" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    const root = try testRoot(allocator, 10);    defer sys.fs.deleteTree(root) catch {};    try std.testing.expectError(error.NoProject, tryOpenRead(allocator, root, smg.default_limits.storage));    try std.testing.expectError(error.NoProject, openRead(allocator, root, smg.default_limits.storage));    try std.testing.expect(!sys.fs.exists(root));    {        var writer = try open(allocator, root, smg.default_limits.storage);        defer writer.close();        try std.testing.expectError(            error.GraphStoreBusy,            tryOpenRead(allocator, root, smg.default_limits.storage),        );    }    var reader = try tryOpenRead(allocator, root, smg.default_limits.storage);    defer reader.close();}test "read store returns typed migration and repair requirements" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    const migration_root = try testRoot(allocator, 4);    defer sys.fs.deleteTree(migration_root) catch {};    {        var writer = try open(allocator, migration_root, smg.default_limits.storage);        defer writer.close();        var dropped = try writer.database.connection.execute(allocator, "DROP TABLE nodes", .{ .durability = .buffered });        dropped.deinit(allocator);        _ = try database.commit(&writer.database);    }    try std.testing.expectError(        error.GraphStoreMigrationRequired,        openRead(allocator, migration_root, smg.default_limits.storage),    );    const repair_root = try testRoot(allocator, 5);    defer sys.fs.deleteTree(repair_root) catch {};    {        var writer = try open(allocator, repair_root, smg.default_limits.storage);        writer.close();    }    const database_path = try std.fs.path.join(allocator, &.{ repair_root, paths.smg_dir_name, paths.database_file_name });    const wal_path = try std.fs.path.join(allocator, &.{ repair_root, paths.smg_dir_name, paths.wal_file_name });    try sys.fs.deleteFile(database_path);    try sys.fs.deleteFile(wal_path);    try std.testing.expectError(        error.GraphStoreRepairRequired,        openRead(allocator, repair_root, smg.default_limits.storage),    );}fn testRoot(allocator: std.mem.Allocator, offset: i64) ![]const u8 {    return try std.fmt.allocPrint(allocator, "/tmp/smg-storage-store-test-{x}", .{@as(u64, @intCast(@max(0, sys.time.realMilliTimestamp() + offset)))});}

Complete caller list for storage.store.open

24 direct callers.

Complete caller list for storage.store.openRead

20 direct callers.

Audit

Definitions14
Public names14
Members10
Version26.7.0
Revisiondaab053ee433