tiny.sql.TreeWrite
Defined in tree.
API (20)
Actions
Public operations.
Fields and members
Public fields and members.
allocationbatch_root_countbatch_rootsdatabaseidentitiesmeta_pagereadreserved_page_maxsnapshottransactionvalidated_pages
Source
Source: lib/sql/src/tree.zig:397
zig
pub const Write = struct { database: *file.Database, meta_page: u32, reserved_page_max: u32, transaction: file.Transaction, read: file.ReadLease, snapshot: file.Snapshot, allocation: Allocation, identities: std.AutoHashMapUnmanaged(u32, IdentityScratch), batch_roots: [max_write_batches]u32 = undefined, batch_root_count: usize = 0, /// Snapshot pages this write validated, by page id modulo the /// capacity. The snapshot cannot change during the write, so a page /// validated once stays valid, and a collision costs one more /// validation. validated_pages: [validated_page_capacity]u32 = @splat(0), pub fn begin(database: *file.Database, options: WriteOptions) Error!Write { if (options.meta_page == 0) return error.InvalidPageId; const reserved_page_max = @max(options.reserved_page_max, options.meta_page); var transaction = try database.beginWrite(); errdefer transaction.deinit(); var read = try database.beginRead(); errdefer read.deinit(); const snapshot = read.snapshot(); return .{ .database = database, .meta_page = options.meta_page, .reserved_page_max = reserved_page_max, .transaction = transaction, .read = read, .snapshot = snapshot, .allocation = try Allocation.init(database, snapshot, options.meta_page, reserved_page_max), .identities = .empty, }; } pub fn beginTree(tree: *const Tree) Error!Write { return try Write.begin(tree.database, .{ .meta_page = tree.meta_page, .reserved_page_max = tree.reserved_page_max, }); } pub fn deinit(self: *Write) void { self.identities.deinit(self.database.allocator); self.transaction.deinit(); self.read.deinit(); self.* = undefined; } fn identityScratch(self: *Write, tree: *const Tree) Error!?*IdentityScratch { if (tree.identity_page == 0) return null; const slot = try self.identities.getOrPut(self.database.allocator, tree.root_page); if (!slot.found_existing) { slot.value_ptr.* = .{ .identity_page = tree.identity_page, .entries = 0, .key_bytes = 0, .value_bytes = 0, .state = lattice.State.empty, }; var image: [page.size]u8 = undefined; if (try self.readPage(tree.identity_page, &image)) { if (!zeroPage(&image)) { const loaded = try page.Identity.load(&image); slot.value_ptr.entries = loaded.entries(); slot.value_ptr.key_bytes = loaded.keyBytes(); slot.value_ptr.value_bytes = loaded.valueBytes(); slot.value_ptr.state = loaded.state(); } } } return slot.value_ptr; } pub fn put(self: *Write, tree: *Tree, key: []const u8, value: []const u8) Error!void { try self.ensureTree(tree); try tree.putInWrite(self, key, value); } pub fn delete(self: *Write, tree: *Tree, key: []const u8) Error!void { try self.ensureTree(tree); try tree.deleteInWrite(self, key); } pub fn clear(self: *Write, tree: *Tree) Error!void { try self.ensureTree(tree); try tree.clearInWrite(self); } pub fn claimBatch(self: *Write, root_page: u32) Error!void { if (root_page == 0) return error.InvalidPageId; for (self.batch_roots[0..self.batch_root_count]) |claimed| { if (claimed == root_page) return error.WriteBatchRepeated; } if (self.batch_root_count == self.batch_roots.len) { return error.WriteBatchLimitExceeded; } self.batch_roots[self.batch_root_count] = root_page; self.batch_root_count += 1; } pub fn allocateRoot(self: *Write) Error!u32 { const phase = trace.scope("tree.write.allocate_root"); defer phase.end(); const root_page = try self.allocatePage(); var image: [page.size]u8 = @splat(0); try self.putPage(root_page, &image); self.reserved_page_max = @max(self.reserved_page_max, root_page); return root_page; } fn allocatePage(self: *Write) Error!u32 { var meta = try page.Meta.load(&self.allocation.image); if (meta.isChained() and meta.freeCount() == 0) return try self.refillFreeList(&meta); const page_id = try meta.allocate(); self.allocation.dirty = true; return page_id; } fn releasePage(self: *Write, root_page: u32, page_id: u32) Error!void { if (page_id == self.meta_page or page_id == root_page) return error.InvalidPageId; var meta = try page.Meta.load(&self.allocation.image); meta.release(page_id) catch |err| switch (err) { error.FreeListFull => { try self.spillFreeList(&meta); try meta.release(page_id); }, else => return err, }; self.allocation.dirty = true; } fn spillFreeList(self: *Write, meta: *page.Meta) Error!void { const chain_page = try meta.allocate(); var entries: [page.meta_chain_page_entries + 1]u32 = undefined; const count = try meta.spillEntries(&entries); var fragment: [page.meta_chain_page_entries * free_entry_size]u8 = undefined; for (entries[0..count], 0..) |entry, index| { std.mem.writeInt(u32, fragment[index * free_entry_size ..][0..free_entry_size], entry, .big); } var image: [page.size]u8 = undefined; _ = try page.Overflow.init(&image, chain_page, meta.chainHead(), fragment[0 .. count * free_entry_size]); try self.transaction.putPage(chain_page, &image); try meta.adoptChain(chain_page); self.allocation.dirty = true; } fn refillFreeList(self: *Write, meta: *page.Meta) Error!u32 { const head = meta.chainHead(); var image: [page.size]u8 = undefined; try self.readExistingPage(head, &image); const overflow = try page.Overflow.load(&image); const content = overflow.content(); if (content.len % free_entry_size != 0) return error.InvalidPage; const count = content.len / free_entry_size; var entries: [page.meta_chain_page_entries]u32 = undefined; if (count > entries.len) return error.InvalidPage; for (entries[0..count], 0..) |*entry, index| { entry.* = std.mem.readInt(u32, content[index * free_entry_size ..][0..free_entry_size], .big); } try meta.refillFromChain(overflow.next(), entries[0..count]); self.allocation.dirty = true; return head; } pub fn commit(self: *Write, options: file.CommitOptions) Error!file.Commit { var identities = self.identities.valueIterator(); while (identities.next()) |scratch| { if (!scratch.dirty) continue; var image: [page.size]u8 = undefined; var identity = page.Identity.init(&image, scratch.identity_page); identity.setEntries(scratch.entries); identity.setKeyBytes(scratch.key_bytes); identity.setValueBytes(scratch.value_bytes); identity.setState(&scratch.state); try self.transaction.putPage(scratch.identity_page, &image); } try self.allocation.write(self.meta_page, &self.transaction); return try self.transaction.commit(options); } fn ensureTree(self: *const Write, tree: *const Tree) Error!void { if (self.database != tree.database) return error.TreeSpaceMismatch; if (self.meta_page != tree.meta_page) return error.TreeSpaceMismatch; if (self.reserved_page_max < tree.reserved_page_max) return error.TreeSpaceMismatch; } fn readRoot(self: *const Write, tree: *const Tree, image: *[page.size]u8) Error!void { if (try self.readPage(tree.root_page, image)) { if (!zeroPage(image)) return; } _ = page.Leaf.init(image, tree.root_page); } fn readExistingPage(self: *const Write, page_id: u32, image: *[page.size]u8) Error!void { if (try self.readPage(page_id, image)) return; return error.InvalidPage; } fn readPage(self: *const Write, page_id: u32, image: *[page.size]u8) Error!bool { if (try self.transaction.getPage(page_id)) |bytes| { image.* = bytes[0..page.size].*; return true; } return try self.snapshot.copyPage(page_id, image); } /// Reads a page for editing. A page this write staged comes back as /// its staged image, and a snapshot page as a copy in `scratch`. fn readPageSource(self: *Write, page_id: u32, scratch: *[page.size]u8) Error!?SourcedPage { if (try self.transaction.editPage(page_id)) |staged| { return .{ .bytes = staged, .source = .transaction }; } if (try self.snapshot.copyPage(page_id, scratch)) { return .{ .bytes = scratch, .source = .snapshot }; } return null; } /// Reads the root of `tree` as a leaf or branch. A reserved root that /// was never written reads as an empty leaf in `scratch`. fn readRootPage(self: *Write, tree: *const Tree, scratch: *[page.size]u8) Error!TreePage { const sourced = (try self.readPageSource(tree.root_page, scratch)) orelse return .{ .leaf = page.Leaf.init(scratch, tree.root_page) }; if (zeroPage(sourced.bytes)) return .{ .leaf = page.Leaf.init(scratch, tree.root_page) }; return try self.treePage(tree.root_page, sourced); } /// Reads a leaf or branch named by a branch this write read. fn readTreePage(self: *Write, page_id: u32, scratch: *[page.size]u8) Error!TreePage { const sourced = (try self.readPageSource(page_id, scratch)) orelse return error.InvalidPage; return try self.treePage(page_id, sourced); } /// Wraps a leaf or branch image, validating its cells unless this write /// wrote the page or already validated it in the snapshot. Safety /// builds assert that a page skipped that way still validates. fn treePage(self: *Write, page_id: u32, sourced: SourcedPage) Error!TreePage { std.debug.assert(page_id != 0); const slot = &self.validated_pages[page_id % validated_page_capacity]; if (sourced.source == .transaction or slot.* == page_id) { if (std.debug.runtime_safety) std.debug.assert(validTreePage(sourced.bytes)); return try trustedTreePage(sourced.bytes); } const loaded = try loadTreePage(sourced.bytes); slot.* = page_id; return loaded; } fn copyPage(self: *const Write, page_id: u32, image: *[page.size]u8) Error!bool { return try self.readPage(page_id, image); } fn putPage(self: *Write, page_id: u32, image: *const [page.size]u8) Error!void { try self.transaction.putPage(page_id, image); }};Source: lib/sql/src/root.zig:216
zig
pub const TreeWrite = tree.Write;Complete caller list for TreeWrite.beginTree
15 direct callers.
tiny.sql.Index.delete[method] atlib/sql/src/index.zig:218tiny.sql.Index.put[method] atlib/sql/src/index.zig:185tiny.sql.Index.putPayload[method] atlib/sql/src/index.zig:199tiny.sql.RowIdTable.delete[method] atlib/sql/src/table.zig:340tiny.sql.RowIdTable.put[method] atlib/sql/src/table.zig:285tiny.sql.RowIdTable.putEncoded[method] atlib/sql/src/table.zig:303tiny.sql.Tree.clear[method] atlib/sql/src/tree.zig:1087tiny.sql.Tree.delete[method] atlib/sql/src/tree.zig:1060tiny.sql.Tree.put[method] atlib/sql/src/tree.zig:966lib.sql.src.tree.test_allocated_roots_clear_recycled_page_images[function] — test source atlib/sql/src/tree.zig:3029in nearest public ownertiny.sql.treelib.sql.src.tree.test_tree_delete_retains_a_safe_lower_bound_when_exact_replacement_does_not_fit[function] — test source atlib/sql/src/tree.zig:2861in nearest public ownertiny.sql.treelib.sql.src.tree.test_tree_identity_accumulates_across_one_write_batch[function] — test source atlib/sql/src/tree.zig:3605in nearest public ownertiny.sql.treelib.sql.src.tree.test_tree_staged_summary_matches_committed_summary[function] — test source atlib/sql/src/tree.zig:2266in nearest public ownertiny.sql.treelib.sql.src.tree.test_tree_write_edits_the_pages_it_staged_in_place[function] — test source atlib/sql/src/tree.zig:3943in nearest public ownertiny.sql.treelib.sql.src.tree.test_tree_write_validates_each_snapshot_leaf_it_reads[function] — test source atlib/sql/src/tree.zig:3885in nearest public ownertiny.sql.tree
Complete call list for TreeWrite.commit
8 direct calls.
tiny.sql.FileTransaction.commit[method] atlib/sql/src/file.zig:3673tiny.sql.FileTransaction.putPage[method] atlib/sql/src/file.zig:3618tiny.sql.page.Identity.init[function] atlib/sql/src/page.zig:690tiny.sql.page.Identity.setEntries[method] atlib/sql/src/page.zig:711tiny.sql.page.Identity.setKeyBytes[method] atlib/sql/src/page.zig:719tiny.sql.page.Identity.setState[method] atlib/sql/src/page.zig:735tiny.sql.page.Identity.setValueBytes[method] atlib/sql/src/page.zig:727lib.sql.src.tree.Allocation.write[method] — private source atlib/sql/src/tree.zig:352in nearest public ownertiny.sql.tree
Audit
| Definitions | 10 |
|---|---|
| Public names | 20 |
| Members | 11 |
| Version | 26.7.0 |
| Revision | daab053ee433 |