tiny.sql.FileDatabaseWorkspace
Defined in tiny.sql.
The reusable storage an open database runs on, built once by a caller so that the pager, the caches, and the paths for every database opened through it come from storage laid out in advance.
API (36)
Actions
Public operations.
Capacity.deriveacquireacquireDigestMemoacquirePathStorageacquireReadCacheacquireTransactionStagingactivateallocatedeallocatedeinitinitreleasereleaseDigestMemoreleasePathStoragereleaseReadCachereleaseTransactionStagingretainedBytes
Types and contracts
Public types and contracts.
Fields and members
Public fields and members.
capacitydigest_memo_livelimitsloanpath_storagephaseread_cache_liveread_cache_loanstatestoragetransaction_staging_livetransaction_staging_loan
Source
Source: lib/sql/src/file.zig:816
zig
/// The reusable storage an open database runs on, built once by a caller so/// that the pager, the caches, and the paths for every database opened through/// it come from storage laid out in advance. The workspace covers the pager,/// path storage, read cache, digest memo, and transaction staging area. One set/// of limits sizes all of them, naming the write-ahead log header, the log byte/// ceiling, the path limits, read cache pages, and digest memo entries. The/// workspace takes storage from the caller with `init` or from an allocator/// with `allocate`, gives it back with the matching `deinit` or `deallocate`,/// and lends each region to one holder at a time. A second request receives/// `WorkspaceBusy`./// Because the path storage inside records the workspace's address, callers/// keep the workspace at one address across every database opened from it, and/// teardown refuses by assertion until every region has been returned.pub const DatabaseWorkspace = struct { pub const Limits = struct { header: wal.Header, max_wal_bytes: usize, path_storage: PathStorage.Limits, read_cache_pages: usize = default_read_cache_capacity, /// Direct-mapped slots, keyed by identity page, each holding one /// stored tree digest. Zero disables reuse. digest_memo_entries: usize = default_digest_memo_entries, }; pub const Storage = struct { pager: pager.Pager.Workspace, paths: PathStorage.Storage, read_cache: ReadCache.Storage, digest_memo: DigestMemo.Storage, transaction_staging: TransactionStaging.Storage, }; pub const Capacity = struct { wal_frames: usize, pager: pager.Pager.Workspace.Capacity, paths: PathStorage.Capacity, read_cache: ReadCache.Capacity, digest_memo: DigestMemo.Capacity, transaction_staging: TransactionStaging.Capacity, requested_bytes: usize, pub const DeriveError = pager.Pager.Workspace.Capacity.DeriveError || error{ InvalidWalLimit, CapacityOverflow }; pub fn derive(limits: Limits) DeriveError!Capacity { const wal_frames = try walFrameCapacity(limits.max_wal_bytes); const pager_capacity = try pager.Pager.Workspace.Capacity.derive(.{ .header = limits.header, .wal_frames = wal_frames, }); const transaction_staging = try TransactionStaging.Capacity.derive(.{ .frames = wal_frames, }); const paths = try PathStorage.Capacity.derive(limits.path_storage); const read_cache = try ReadCache.Capacity.derive(.{ .pages = limits.read_cache_pages, }); const digest_memo = try DigestMemo.Capacity.derive(limits.digest_memo_entries); var requested_bytes = pager_capacity.journal.storage_bytes; requested_bytes = std.math.add( usize, requested_bytes, pager_capacity.checkpoint.storage_bytes, ) catch return error.CapacityOverflow; requested_bytes = std.math.add( usize, requested_bytes, pager_capacity.checkpoint_once.storage_bytes, ) catch return error.CapacityOverflow; requested_bytes = std.math.add( usize, requested_bytes, pager_capacity.wal_index.storage_bytes, ) catch return error.CapacityOverflow; requested_bytes = std.math.add( usize, requested_bytes, paths.storageBytes(), ) catch return error.CapacityOverflow; requested_bytes = std.math.add( usize, requested_bytes, read_cache.storage_bytes, ) catch return error.CapacityOverflow; requested_bytes = std.math.add( usize, requested_bytes, digest_memo.storage_bytes, ) catch return error.CapacityOverflow; requested_bytes = std.math.add( usize, requested_bytes, transaction_staging.storage_bytes, ) catch return error.CapacityOverflow; return .{ .wal_frames = wal_frames, .pager = pager_capacity, .paths = paths, .read_cache = read_cache, .digest_memo = digest_memo, .transaction_staging = transaction_staging, .requested_bytes = requested_bytes, }; } }; pub const InitError = Capacity.DeriveError || PathStorage.IdentityError || error{StorageTooShort}; pub const AllocateError = Allocator.Error || Capacity.DeriveError || PathStorage.IdentityError; pub const AcquireError = Error || error{ WorkspaceBusy, WorkspaceCapacityExceeded }; const State = enum { free, busy, }; phase: alloc_phase.capacity.Phase, limits: Limits, capacity: Capacity, storage: Storage, path_storage: PathStorage, state: State = .free, loan: pager.Pager.Workspace.Capacity = undefined, read_cache_live: bool = false, read_cache_loan: ReadCache.Capacity = undefined, digest_memo_live: bool = false, transaction_staging_live: bool = false, transaction_staging_loan: TransactionStaging.Capacity = undefined, pub fn init(storage: Storage, limits: Limits) InitError!DatabaseWorkspace { const capacity = try Capacity.derive(limits); if (!fits(storage, capacity)) return error.StorageTooShort; const path_storage = try PathStorage.init( storage.paths[0..capacity.paths.storageBytes()], limits.path_storage, ); return .{ .phase = .initialization, .limits = limits, .capacity = capacity, .storage = storage, .path_storage = path_storage, }; } pub fn allocate(allocator: Allocator, limits: Limits) AllocateError!DatabaseWorkspace { const capacity = try Capacity.derive(limits); var pager_storage = try pager.Pager.Workspace.allocate(allocator, .{ .header = limits.header, .wal_frames = capacity.wal_frames, }); errdefer pager_storage.deallocate(allocator); const paths = if (capacity.paths.storageBytes() == 0) @as(PathStorage.Storage, &.{}) else try allocator.alloc(u8, capacity.paths.storageBytes()); errdefer if (paths.len != 0) allocator.free(paths); const read_cache = if (capacity.read_cache.storage_bytes == 0) @as(ReadCache.Storage, &.{}) else try allocator.alignedAlloc( u8, .fromByteUnits(ReadCache.storage_alignment), capacity.read_cache.storage_bytes, ); errdefer if (read_cache.len != 0) allocator.free(read_cache); const digest_memo = if (capacity.digest_memo.entries == 0) @as(DigestMemo.Storage, &.{}) else try allocator.alloc(DigestMemo.Entry, capacity.digest_memo.entries); errdefer if (digest_memo.len != 0) allocator.free(digest_memo); const transaction_staging = if (capacity.transaction_staging.storage_bytes == 0) @as(TransactionStaging.Storage, &.{}) else try allocator.alignedAlloc( u8, .fromByteUnits(TransactionStaging.storage_alignment), capacity.transaction_staging.storage_bytes, ); errdefer if (transaction_staging.len != 0) allocator.free(transaction_staging); var workspace = init(.{ .pager = pager_storage, .paths = paths, .read_cache = read_cache, .digest_memo = digest_memo, .transaction_staging = transaction_staging, }, limits) catch |err| switch (err) { error.StorageTooShort => unreachable, else => |other| return other, }; workspace.activate(); return workspace; } pub fn activate(self: *DatabaseWorkspace) void { std.debug.assert(self.phase == .initialization); self.assertValid(); self.phase = .steady; } pub fn acquire( self: *DatabaseWorkspace, allocator: Allocator, options: pager.InitOptions, ) AcquireError!pager.Pager { self.assertSteady(); if (self.state == .busy) return error.WorkspaceBusy; const loan = try pager.Pager.Workspace.Capacity.derive(options); if (options.wal_frames > self.capacity.wal_frames or !fitsPager(self.storage.pager, loan)) { return error.WorkspaceCapacityExceeded; } var storage = pager.Pager.Workspace.init( self.storage.pager.journal[0..loan.journal.storage_bytes], self.storage.pager.checkpoint[0..loan.checkpoint.storage_bytes], self.storage.pager.checkpoint_once[0..loan.checkpoint_once.storage_bytes], self.storage.pager.wal_index[0..loan.wal_index.storage_bytes], ); const owned = pager.Pager.init(allocator, &storage, options) catch |err| switch (err) { error.StorageTooShort => unreachable, else => return err, }; self.state = .busy; self.loan = loan; return owned; } pub fn release(self: *DatabaseWorkspace, owned: *pager.Pager) void { self.assertSteady(); std.debug.assert(self.state == .busy); const returned = owned.deinit(); assertReturned(self.storage.pager.journal, returned.journal, self.loan.journal.storage_bytes); assertReturned(self.storage.pager.checkpoint, returned.checkpoint, self.loan.checkpoint.storage_bytes); assertReturned( self.storage.pager.checkpoint_once, returned.checkpoint_once, self.loan.checkpoint_once.storage_bytes, ); assertReturned(self.storage.pager.wal_index, returned.wal_index, self.loan.wal_index.storage_bytes); self.state = .free; self.loan = undefined; } pub fn acquirePathStorage( self: *DatabaseWorkspace, limits: PathStorage.Limits, ) AcquireError!PathStorage.Loan { self.assertSteady(); return self.path_storage.acquire(limits) catch |err| switch (err) { error.PathStorageBusy => error.WorkspaceBusy, error.PathCapacityExceeded => error.WorkspaceCapacityExceeded, else => |other| other, }; } pub fn releasePathStorage( self: *DatabaseWorkspace, owned: *PathStorage.Loan, ) PathStorage.Loan.LoanError!void { self.assertSteady(); try self.path_storage.release(owned); } pub fn acquireReadCache( self: *DatabaseWorkspace, limits: ReadCache.Limits, ) AcquireError!ReadCache { self.assertSteady(); if (self.read_cache_live) return error.WorkspaceBusy; const loan = try ReadCache.Capacity.derive(limits); if (loan.storage_bytes > self.storage.read_cache.len) { return error.WorkspaceCapacityExceeded; } const owned = ReadCache.init( self.storage.read_cache[0..loan.storage_bytes], limits, ) catch |err| switch (err) { error.StorageTooShort => unreachable, else => return err, }; self.read_cache_live = true; self.read_cache_loan = loan; return owned; } pub fn releaseReadCache( self: *DatabaseWorkspace, owned: *ReadCache, ) void { self.assertSteady(); std.debug.assert(self.read_cache_live); const returned = owned.deinit(); assertReturned( self.storage.read_cache, returned, self.read_cache_loan.storage_bytes, ); self.read_cache_live = false; self.read_cache_loan = undefined; } pub fn acquireDigestMemo(self: *DatabaseWorkspace) AcquireError!DigestMemo { self.assertSteady(); if (self.digest_memo_live) return error.WorkspaceBusy; self.digest_memo_live = true; return DigestMemo.init(self.storage.digest_memo, self.capacity.digest_memo.entries); } pub fn releaseDigestMemo(self: *DatabaseWorkspace, owned: *DigestMemo) void { self.assertSteady(); std.debug.assert(self.digest_memo_live); std.debug.assert(owned.slots.ptr == self.storage.digest_memo.ptr); std.debug.assert(owned.slots.len == self.capacity.digest_memo.entries); owned.* = undefined; self.digest_memo_live = false; } pub fn acquireTransactionStaging( self: *DatabaseWorkspace, limits: TransactionStaging.Limits, ) AcquireError!TransactionStaging { self.assertSteady(); if (self.transaction_staging_live) return error.WorkspaceBusy; const loan = try TransactionStaging.Capacity.derive(limits); if (loan.storage_bytes > self.storage.transaction_staging.len) { return error.WorkspaceCapacityExceeded; } const owned = TransactionStaging.init( self.storage.transaction_staging[0..loan.storage_bytes], limits, ) catch |err| switch (err) { error.StorageTooShort => unreachable, else => return err, }; self.transaction_staging_live = true; self.transaction_staging_loan = loan; return owned; } pub fn releaseTransactionStaging( self: *DatabaseWorkspace, owned: *TransactionStaging, ) void { self.assertSteady(); std.debug.assert(self.transaction_staging_live); const returned = owned.deinit(); assertReturned( self.storage.transaction_staging, returned, self.transaction_staging_loan.storage_bytes, ); self.transaction_staging_live = false; self.transaction_staging_loan = undefined; } pub fn deinit(self: *DatabaseWorkspace) Storage { std.debug.assert(self.phase != .teardown); self.assertValid(); std.debug.assert(self.state == .free); std.debug.assert(!self.path_storage.loan_live); std.debug.assert(!self.read_cache_live); std.debug.assert(!self.digest_memo_live); std.debug.assert(!self.transaction_staging_live); const returned_paths = self.path_storage.deinit(); assertReturned( self.storage.paths, returned_paths, self.capacity.paths.storageBytes(), ); self.phase = .teardown; const storage = self.storage; self.* = undefined; return storage; } pub fn deallocate(self: *DatabaseWorkspace, allocator: Allocator) void { var storage = self.deinit(); if (storage.transaction_staging.len != 0) { allocator.free(storage.transaction_staging); } if (storage.read_cache.len != 0) allocator.free(storage.read_cache); if (storage.digest_memo.len != 0) allocator.free(storage.digest_memo); if (storage.paths.len != 0) allocator.free(storage.paths); storage.pager.deallocate(allocator); } pub fn retainedBytes(self: *const DatabaseWorkspace) usize { self.assertValid(); return self.capacity.requested_bytes; } fn assertSteady(self: *const DatabaseWorkspace) void { std.debug.assert(self.phase == .steady); self.assertValid(); } fn assertValid(self: *const DatabaseWorkspace) void { std.debug.assert(fits(self.storage, self.capacity)); } fn fits( storage: Storage, capacity: Capacity, ) bool { return fitsPager(storage.pager, capacity.pager) and storage.paths.len >= capacity.paths.storageBytes() and storage.read_cache.len >= capacity.read_cache.storage_bytes and storage.digest_memo.len >= capacity.digest_memo.entries and storage.transaction_staging.len >= capacity.transaction_staging.storage_bytes; } fn fitsPager( storage: pager.Pager.Workspace, capacity: pager.Pager.Workspace.Capacity, ) bool { return storage.journal.len >= capacity.journal.storage_bytes and storage.checkpoint.len >= capacity.checkpoint.storage_bytes and storage.checkpoint_once.len >= capacity.checkpoint_once.storage_bytes and storage.wal_index.len >= capacity.wal_index.storage_bytes; } fn assertReturned(full: []u8, returned: []u8, loan_bytes: usize) void { std.debug.assert(returned.ptr == full.ptr); std.debug.assert(returned.len == loan_bytes); std.debug.assert(returned.len <= full.len); }};Source: lib/sql/src/root.zig:179
zig
pub const FileDatabaseWorkspace = file.DatabaseWorkspace;Complete caller list for FileDatabaseWorkspace.allocate
10 direct callers.
lib.sql.src.file.test_file_database_rejects_path_overcapacity_before_opening_files[function] — test source atlib/sql/src/file.zig:4506in nearest public ownertiny.sql.indexlib.sql.src.file.test_file_database_rejects_read_cache_overcapacity_before_opening_files[function] — test source atlib/sql/src/file.zig:4535in nearest public ownertiny.sql.indexlib.sql.src.file.test_file_database_workspace_reuses_exact_pager_path_cache_memo_and_staging_regions[function] — test source atlib/sql/src/file.zig:4310in nearest public ownertiny.sql.indexlib.sql.src.file.test_file_recover_materializes_clean_base_pages[function] — test source atlib/sql/src/file.zig:6978in nearest public ownertiny.sql.indexlib.sql.src.file.test_file_recovery_ignores_uncommitted_wal_tail[function] — test source atlib/sql/src/file.zig:5095in nearest public ownertiny.sql.indexlib.sql.src.file.test_file_recovery_rejects_partial_database_pages[function] — test source atlib/sql/src/file.zig:7047in nearest public ownertiny.sql.indexlib.sql.src.file.test_file_recovery_reuses_clean_wal_header[function] — test source atlib/sql/src/file.zig:5131in nearest public ownertiny.sql.indexlib.sql.src.file.test_file_recovery_rewrites_committed_wal_frames[function] — test source atlib/sql/src/file.zig:7012in nearest public ownertiny.sql.indexlib.sql.src.file.test_file_recovery_rewrites_stale_wal_header_without_base_rewrite[function] — test source atlib/sql/src/file.zig:5161in nearest public ownertiny.sql.indexlib.sql.src.file.test_file_writes_base_and_wal_then_recovers_committed_page_images[function] — test source atlib/sql/src/file.zig:5060in nearest public ownertiny.sql.index
Audit
| Definitions | 25 |
|---|---|
| Public names | 25 |
| Members | 29 |
| Version | 26.7.0 |
| Revision | daab053ee433 |