lib/sql/src/history/conflict.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const sql = @import("../root.zig");
3 const record_mod = @import("record.zig");
4 const store_mod = @import("store.zig");
5 const version = sql.version;
6
7 const Allocator = std.mem.Allocator;
8 const conflict_entry_bytes_min =
9 @sizeOf(u8) + @sizeOf(u32) + version.hash_bytes;
10
11 pub const ConflictEntries = struct {
12 allocator: Allocator,
13 root: version.ConflictRoot,
14 entries: []version.ConflictEntry,
15
16 pub fn deinit(self: *ConflictEntries) void {
17 deinitConflictEntries(self.allocator, self.entries);
18 self.* = undefined;
19 }
20 };
21
22 pub const ConflictArtifacts = struct {
23 allocator: Allocator,
24 root: version.ConflictRoot,
25 artifacts: []version.ConflictArtifact,
26
27 pub fn deinit(self: *ConflictArtifacts) void {
28 deinitConflictArtifacts(self.allocator, self.artifacts);
29 self.* = undefined;
30 }
31 };
32
33 pub fn appendConflictEntries(allocator: Allocator, target: *std.ArrayList(u8), entries: []const version.ConflictEntry) store_mod.Error!void {
34 if (entries.len > std.math.maxInt(u32)) return error.InvalidHistory;
35 try record_mod.appendU32(allocator, target, @intCast(entries.len));
36 for (entries) |entry| {
37 try record_mod.appendU8(allocator, target, @backingInt(entry.kind));
38 try record_mod.appendBytes(allocator, target, entry.relation);
39 if (entry.kind == .row) try record_mod.appendI64(allocator, target, entry.rowid);
40 try record_mod.appendHash(allocator, target, entry.hash);
41 }
42 }
43
44 pub fn readConflictEntries(allocator: Allocator, reader: *record_mod.PayloadReader) store_mod.Error![]version.ConflictEntry {
45 const count = try reader.readU32();
46 const count_usize: usize = count;
47 if (count_usize > reader.remaining() / conflict_entry_bytes_min) {
48 return error.InvalidHistory;
49 }
50 const entries = try allocator.alloc(version.ConflictEntry, count_usize);
51 var read_entries: usize = 0;
52 errdefer {
53 for (entries[0..read_entries]) |entry| allocator.free(entry.relation);
54 allocator.free(entries);
55 }
56 for (entries) |*entry| {
57 const kind = try conflictKind(try reader.readU8());
58 const relation = try allocator.dupe(u8, try reader.readBytes());
59 errdefer allocator.free(relation);
60 const rowid = if (kind == .row) try reader.readI64() else 0;
61 entry.* = .{
62 .kind = kind,
63 .relation = relation,
64 .rowid = rowid,
65 .hash = try reader.hash(),
66 };
67 read_entries += 1;
68 }
69 return entries;
70 }
71
72 pub fn conflictKind(value: u8) store_mod.Error!version.ConflictKind {
73 return switch (value) {
74 @backingInt(version.ConflictKind.row) => .row,
75 @backingInt(version.ConflictKind.relation) => .relation,
76 else => error.InvalidHistory,
77 };
78 }
79
80 pub fn copyOptionalRow(allocator: Allocator, bytes: ?[]const u8) Allocator.Error!?[]u8 {
81 return if (bytes) |value| try allocator.dupe(u8, value) else null;
82 }
83
84 pub fn canonicalConflictArtifact(artifact: version.ConflictArtifact) version.ConflictArtifact {
85 return switch (artifact.kind) {
86 .row => version.ConflictArtifact.init(artifact.relation, artifact.rowid, conflictRowValue(artifact.base), conflictRowValue(artifact.ours), conflictRowValue(artifact.theirs)),
87 .relation => version.ConflictArtifact.initRelation(artifact.relation, conflictRelationValue(artifact.base), conflictRelationValue(artifact.ours), conflictRelationValue(artifact.theirs)),
88 };
89 }
90
91 pub fn cloneConflictArtifact(allocator: Allocator, artifact: version.ConflictArtifact) store_mod.Error!version.ConflictArtifact {
92 const relation = try allocator.dupe(u8, artifact.relation);
93 errdefer allocator.free(relation);
94
95 return switch (artifact.kind) {
96 .row => blk: {
97 const base = try copyOptionalRow(allocator, conflictRowValue(artifact.base));
98 errdefer if (base) |bytes| allocator.free(bytes);
99 const ours = try copyOptionalRow(allocator, conflictRowValue(artifact.ours));
100 errdefer if (ours) |bytes| allocator.free(bytes);
101 const theirs = try copyOptionalRow(allocator, conflictRowValue(artifact.theirs));
102 errdefer if (theirs) |bytes| allocator.free(bytes);
103 break :blk version.ConflictArtifact.init(relation, artifact.rowid, base, ours, theirs);
104 },
105 .relation => version.ConflictArtifact.initRelation(relation, conflictRelationValue(artifact.base), conflictRelationValue(artifact.ours), conflictRelationValue(artifact.theirs)),
106 };
107 }
108
109 pub fn cloneConflictEntries(allocator: Allocator, entries: []const version.ConflictEntry) Allocator.Error![]version.ConflictEntry {
110 const cloned = try allocator.alloc(version.ConflictEntry, entries.len);
111 var count: usize = 0;
112 errdefer {
113 for (cloned[0..count]) |entry| allocator.free(entry.relation);
114 allocator.free(cloned);
115 }
116 for (entries, cloned) |entry, *target| {
117 target.* = .{
118 .kind = entry.kind,
119 .relation = try allocator.dupe(u8, entry.relation),
120 .rowid = entry.rowid,
121 .hash = entry.hash,
122 };
123 count += 1;
124 }
125 return cloned;
126 }
127
128 pub fn cloneConflictEntriesSorted(allocator: Allocator, entries: []const version.ConflictEntry) Allocator.Error![]version.ConflictEntry {
129 const cloned = try cloneConflictEntries(allocator, entries);
130 std.mem.sort(version.ConflictEntry, cloned, {}, version.ConflictEntry.lessThan);
131 return cloned;
132 }
133
134 pub fn deinitConflictEntries(allocator: Allocator, entries: []version.ConflictEntry) void {
135 for (entries) |entry| allocator.free(entry.relation);
136 if (entries.len != 0) allocator.free(entries);
137 }
138
139 pub fn deinitConflictArtifact(allocator: Allocator, artifact: *version.ConflictArtifact) void {
140 allocator.free(artifact.relation);
141 deinitConflictValue(allocator, artifact.base);
142 deinitConflictValue(allocator, artifact.ours);
143 deinitConflictValue(allocator, artifact.theirs);
144 artifact.* = undefined;
145 }
146
147 pub fn deinitConflictArtifacts(allocator: Allocator, artifacts: []version.ConflictArtifact) void {
148 for (artifacts) |*artifact| deinitConflictArtifact(allocator, artifact);
149 if (artifacts.len != 0) allocator.free(artifacts);
150 }
151
152 pub fn deinitConflictValue(allocator: Allocator, value: ?version.ConflictValue) void {
153 if (value) |conflict_value| switch (conflict_value) {
154 .row => |bytes| allocator.free(bytes),
155 .relation => {},
156 };
157 }
158
159 pub fn appendConflictArtifactRecordPayload(allocator: Allocator, target: *std.ArrayList(u8), artifact: version.ConflictArtifact) store_mod.Error!void {
160 try record_mod.appendU8(allocator, target, @backingInt(artifact.kind));
161 try record_mod.appendBytes(allocator, target, artifact.relation);
162 switch (artifact.kind) {
163 .row => {
164 try record_mod.appendI64(allocator, target, artifact.rowid);
165 try record_mod.appendOptionalBytes(allocator, target, conflictRowValue(artifact.base));
166 try record_mod.appendOptionalBytes(allocator, target, conflictRowValue(artifact.ours));
167 try record_mod.appendOptionalBytes(allocator, target, conflictRowValue(artifact.theirs));
168 },
169 .relation => {
170 try record_mod.appendOptionalHash(allocator, target, conflictRelationValue(artifact.base));
171 try record_mod.appendOptionalHash(allocator, target, conflictRelationValue(artifact.ours));
172 try record_mod.appendOptionalHash(allocator, target, conflictRelationValue(artifact.theirs));
173 },
174 }
175 }
176
177 pub fn decodeConflictArtifactPayload(reader: *record_mod.PayloadReader) store_mod.Error!version.ConflictArtifact {
178 const kind = try conflictKind(try reader.readU8());
179 const relation_bytes = try reader.readBytes();
180 return switch (kind) {
181 .row => blk: {
182 const rowid = try reader.readI64();
183 const base_bytes = try reader.optionalBytes();
184 const ours_bytes = try reader.optionalBytes();
185 const theirs_bytes = try reader.optionalBytes();
186 break :blk version.ConflictArtifact.init(relation_bytes, rowid, base_bytes, ours_bytes, theirs_bytes);
187 },
188 .relation => blk: {
189 const base_root = try reader.optionalHash();
190 const ours_root = try reader.optionalHash();
191 const theirs_root = try reader.optionalHash();
192 break :blk version.ConflictArtifact.initRelation(relation_bytes, base_root, ours_root, theirs_root);
193 },
194 };
195 }
196
197 pub fn conflictRowValue(value: ?version.ConflictValue) ?[]const u8 {
198 const conflict_value = value orelse return null;
199 return switch (conflict_value) {
200 .row => |bytes| bytes,
201 .relation => unreachable,
202 };
203 }
204
205 pub fn conflictRelationValue(value: ?version.ConflictValue) ?version.Hash {
206 const conflict_value = value orelse return null;
207 return switch (conflict_value) {
208 .row => unreachable,
209 .relation => |hash| hash,
210 };
211 }
212
213 test "conflict entry decoding bounds declared count by payload" {
214 const testing = std.testing;
215 const payloads = [_][4]u8{
216 .{ 0, 0, 0, 1 },
217 .{ 0xff, 0xff, 0xff, 0xff },
218 };
219 for (payloads) |payload| {
220 var reader = record_mod.PayloadReader.init(&payload);
221 try testing.expectError(
222 error.InvalidHistory,
223 readConflictEntries(testing.allocator, &reader),
224 );
225 }
226 }