lib/sql/src/history/identity.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const sql = @import("../root.zig");
3 const conflict_mod = @import("conflict.zig");
4 const record_mod = @import("record.zig");
5
6 const version = sql.version;
7
8 /// Object kinds have separate key spaces even when their key bytes agree.
9 pub const Kind = enum(u8) {
10 commit,
11 conflict,
12 database_root,
13 relation_root,
14 relation_rows,
15 conflict_root,
16 row_chunk,
17 chunk_index_page,
18 tree_node,
19 relation_spans,
20 };
21
22 pub const Key = struct {
23 kind: Kind,
24 bytes: version.Hash,
25 };
26
27 /// Node position is zero except for one entry inside a tree-node batch.
28 pub const Location = struct {
29 record_offset: u64,
30 payload_len: u32,
31 envelope_hash: version.Hash,
32 node_position: u32 = 0,
33
34 pub fn encodedLen(self: Location) u64 {
35 return record_mod.record_header_size + @as(u64, self.payload_len);
36 }
37 };
38
39 pub const Entry = struct {
40 key: Key,
41 location: Location,
42 };
43
44 /// Scratch contains only borrowed slices into the current payload.
45 pub const Scratch = struct {
46 relations: []version.RelationEntry,
47 conflicts: []version.ConflictEntry,
48 };
49
50 pub const Error = error{ InvalidHistory, KeyScratchExhausted };
51
52 /// Emits each typed object key from one checked envelope's payload.
53 pub fn each(
54 kind: record_mod.RecordKind,
55 payload: []const u8,
56 location: Location,
57 scratch: Scratch,
58 context: anytype,
59 comptime accept: fn (@TypeOf(context), Entry) anyerror!void,
60 ) anyerror!void {
61 var reader = record_mod.PayloadReader.init(payload);
62 switch (kind) {
63 .commit => {
64 const root = try reader.hash();
65 const count = try reader.readU32();
66 const bytes = std.math.mul(usize, count, version.hash_bytes) catch
67 return error.InvalidHistory;
68 if (bytes != reader.remaining()) return error.InvalidHistory;
69 const parents = std.mem.bytesAsSlice(version.Hash, payload[reader.cursor..]);
70 try accept(context, .{
71 .key = .{ .kind = .commit, .bytes = version.Commit.init(root, parents).hash },
72 .location = location,
73 });
74 },
75 .database_root => {
76 const conflicts = try reader.hash();
77 const count = try reader.readU32();
78 if (count > scratch.relations.len) return error.KeyScratchExhausted;
79 const entries = scratch.relations[0..count];
80 for (entries) |*entry| {
81 entry.* = .{ .name = try reader.readBytes(), .hash = try reader.hash() };
82 }
83 try reader.finish();
84 std.mem.sort(version.RelationEntry, entries, {}, relationLessThan);
85 const root = version.DatabaseRoot.init(entries, .{ .hash = conflicts });
86 try accept(context, .{
87 .key = .{ .kind = .database_root, .bytes = root.hash },
88 .location = location,
89 });
90 },
91 .relation_root => {
92 if (payload.len < version.hash_bytes) return error.InvalidHistory;
93 try accept(context, .{
94 .key = .{ .kind = .relation_root, .bytes = payload[payload.len - 32 ..][0..32].* },
95 .location = location,
96 });
97 },
98 .relation_rows, .relation_spans, .row_chunk, .chunk_index_page => {
99 const key = try reader.hash();
100 const object_kind: Kind = switch (kind) {
101 .relation_rows => .relation_rows,
102 .relation_spans => .relation_spans,
103 .row_chunk => .row_chunk,
104 .chunk_index_page => .chunk_index_page,
105 else => unreachable,
106 };
107 try accept(context, .{
108 .key = .{ .kind = object_kind, .bytes = key },
109 .location = location,
110 });
111 },
112 .conflict => {
113 const artifact = try conflict_mod.decodeConflictArtifactPayload(&reader);
114 try reader.finish();
115 try accept(context, .{
116 .key = .{ .kind = .conflict, .bytes = artifact.hash },
117 .location = location,
118 });
119 },
120 .conflict_root => {
121 const count = try reader.readU32();
122 if (count > scratch.conflicts.len) return error.KeyScratchExhausted;
123 const entries = scratch.conflicts[0..count];
124 for (entries) |*entry| {
125 const entry_kind = try conflict_mod.conflictKind(try reader.readU8());
126 const relation = try reader.readBytes();
127 const rowid = if (entry_kind == .row) try reader.readI64() else 0;
128 entry.* = .{
129 .kind = entry_kind,
130 .relation = relation,
131 .rowid = rowid,
132 .hash = try reader.hash(),
133 };
134 }
135 try reader.finish();
136 try accept(context, .{
137 .key = .{
138 .kind = .conflict_root,
139 .bytes = version.ConflictRoot.init(entries).hash,
140 },
141 .location = location,
142 });
143 },
144 .tree_nodes => {
145 const count = try reader.readU32();
146 if (count > record_mod.node_batch_max) return error.InvalidHistory;
147 for (0..count) |position| {
148 const key = try reader.hash();
149 _ = try record_mod.nodeKind(try reader.readU8());
150 _ = try reader.readBytes();
151 _ = try reader.optionalBytes();
152 _ = try record_mod.readUsize(&reader);
153 _ = try record_mod.readSummary(&reader);
154 _ = try reader.hash();
155 const children = try reader.readU32();
156 const child_bytes = std.math.mul(usize, children, 32) catch
157 return error.InvalidHistory;
158 if (child_bytes > reader.remaining()) return error.InvalidHistory;
159 reader.cursor += child_bytes;
160 var node_location = location;
161 node_location.node_position = @intCast(position);
162 try accept(context, .{
163 .key = .{ .kind = .tree_node, .bytes = key },
164 .location = node_location,
165 });
166 }
167 try reader.finish();
168 },
169 .ref,
170 .ref_delete,
171 .fast_forward_prepare,
172 .fast_forward_commit,
173 .fast_forward_abort,
174 .fast_forward_complete,
175 => {},
176 }
177 }
178
179 fn relationLessThan(_: void, left: version.RelationEntry, right: version.RelationEntry) bool {
180 return std.mem.lessThan(u8, left.name, right.name);
181 }