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 }