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 }