feat: add deinit and ReferencedBySpec to index
This commit is contained in:
1 parent
43b27efecc
commit
fff7ffeb78
1 file changed
+156
+156
@@ -634,3 +634,159 @@ pub const CheckConstraintSet = struct {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *CheckConstraintSet) void {
|
||||||
|
for (self.specs.items) |spec| {
|
||||||
|
self.allocator.free(spec.name);
|
||||||
|
self.allocator.free(spec.source);
|
||||||
|
}
|
||||||
|
self.specs.deinit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// One reverse FOREIGN KEY entry, recorded on the REFERENCED table rather
|
||||||
|
/// than the referencing one: some other table `childTable` has a
|
||||||
|
/// `FOREIGN KEY (childField) REFERENCES <this table>(refField)`. This is
|
||||||
|
/// the registry `Table.createForeignKey`'s doc comment calls out as
|
||||||
|
/// missing ("tables are opened ad hoc by name and never catalogued
|
||||||
|
/// anywhere") - without it, deleting a parent row has no way to discover
|
||||||
|
/// which other tables might still be pointing at it, since `ForeignKeySet`
|
||||||
|
/// is only ever stored on the referencing (child) side.
|
||||||
|
pub const ReferencedBySpec = struct {
|
||||||
|
childTable: []u8,
|
||||||
|
childField: []u8,
|
||||||
|
refField: []u8,
|
||||||
|
onDelete: OnDelete,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Persisted set of reverse FK entries on a table - same on-disk shape
|
||||||
|
/// family as `ForeignKeySet` (own meta key, own namespace, three
|
||||||
|
/// length-prefixed strings plus an `onDelete` mode byte per entry), but
|
||||||
|
/// populated by a DIFFERENT table's `createForeignKey`/`dropForeignKey`
|
||||||
|
/// call rather than by mutations against this table directly. `onDelete`
|
||||||
|
/// is duplicated here rather than looked up from the child's own
|
||||||
|
/// `ForeignKeySet` on every delete, so `Table.delete`'s cascade planner
|
||||||
|
/// (see `query.zig`'s `planCascade`) can decide restrict-vs-cascade
|
||||||
|
/// without opening and loading the child a second time just to answer
|
||||||
|
/// that. `add` is keyed by the (childTable, childField) pair - a child
|
||||||
|
/// field can only ever carry one FOREIGN KEY (enforced by
|
||||||
|
/// `ForeignKeySet.has` at the child), so at most one reverse entry per
|
||||||
|
/// pair can ever be correct; re-adding the same pair (only reachable by
|
||||||
|
/// first dropping and recreating the child's constraint, possibly with a
|
||||||
|
/// different `refField`/`onDelete`) replaces it in place instead of
|
||||||
|
/// accumulating a stale duplicate.
|
||||||
|
pub const ReferencedBySet = struct {
|
||||||
|
allocator: Allocator,
|
||||||
|
specs: std.ArrayList(ReferencedBySpec),
|
||||||
|
|
||||||
|
pub fn load(allocator: Allocator, db: *Db, table: []const u8) !ReferencedBySet {
|
||||||
|
const key = try referencedByMetaKey(allocator, table);
|
||||||
|
defer allocator.free(key);
|
||||||
|
|
||||||
|
var set = ReferencedBySet{ .allocator = allocator, .specs = std.ArrayList(ReferencedBySpec).init(allocator) };
|
||||||
|
errdefer set.deinit();
|
||||||
|
|
||||||
|
const raw = try db.get(allocator, key) orelse return set;
|
||||||
|
defer allocator.free(raw);
|
||||||
|
|
||||||
|
var pos: usize = 0;
|
||||||
|
if (pos + 4 > raw.len) return error.Malformed;
|
||||||
|
const count = std.mem.readInt(u32, raw[pos..][0..4], .little);
|
||||||
|
pos += 4;
|
||||||
|
var i: u32 = 0;
|
||||||
|
while (i < count) : (i += 1) {
|
||||||
|
const childTable = try readLenPrefixed(allocator, raw, &pos);
|
||||||
|
errdefer allocator.free(childTable);
|
||||||
|
const childField = try readLenPrefixed(allocator, raw, &pos);
|
||||||
|
errdefer allocator.free(childField);
|
||||||
|
const refField = try readLenPrefixed(allocator, raw, &pos);
|
||||||
|
errdefer allocator.free(refField);
|
||||||
|
if (pos + 1 > raw.len) return error.Malformed;
|
||||||
|
const onDelete = try onDeleteFromByte(raw[pos]);
|
||||||
|
pos += 1;
|
||||||
|
try set.specs.append(.{ .childTable = childTable, .childField = childField, .refField = refField, .onDelete = onDelete });
|
||||||
|
}
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save(self: *const ReferencedBySet, db: *Db, table: []const u8) !void {
|
||||||
|
const key = try referencedByMetaKey(self.allocator, table);
|
||||||
|
defer self.allocator.free(key);
|
||||||
|
|
||||||
|
var out = std.ArrayList(u8).init(self.allocator);
|
||||||
|
defer out.deinit();
|
||||||
|
var countBuf: [4]u8 = undefined;
|
||||||
|
std.mem.writeInt(u32, &countBuf, @intCast(self.specs.items.len), .little);
|
||||||
|
try out.appendSlice(&countBuf);
|
||||||
|
for (self.specs.items) |spec| {
|
||||||
|
try appendLenPrefixed(&out, spec.childTable);
|
||||||
|
try appendLenPrefixed(&out, spec.childField);
|
||||||
|
try appendLenPrefixed(&out, spec.refField);
|
||||||
|
try out.append(@intFromEnum(spec.onDelete));
|
||||||
|
}
|
||||||
|
try db.put(key, out.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn indexOf(self: *const ReferencedBySet, childTable: []const u8, childField: []const u8) ?usize {
|
||||||
|
for (self.specs.items, 0..) |spec, i| {
|
||||||
|
if (std.mem.eql(u8, spec.childTable, childTable) and std.mem.eql(u8, spec.childField, childField)) return i;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(self: *ReferencedBySet, childTable: []const u8, childField: []const u8, refField: []const u8, onDelete: OnDelete) !void {
|
||||||
|
const refFieldCopy = try self.allocator.dupe(u8, refField);
|
||||||
|
errdefer self.allocator.free(refFieldCopy);
|
||||||
|
if (self.indexOf(childTable, childField)) |i| {
|
||||||
|
self.allocator.free(self.specs.items[i].refField);
|
||||||
|
self.specs.items[i].refField = refFieldCopy;
|
||||||
|
self.specs.items[i].onDelete = onDelete;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const spec = ReferencedBySpec{
|
||||||
|
.childTable = try self.allocator.dupe(u8, childTable),
|
||||||
|
.childField = try self.allocator.dupe(u8, childField),
|
||||||
|
.refField = refFieldCopy,
|
||||||
|
.onDelete = onDelete,
|
||||||
|
};
|
||||||
|
try self.specs.append(spec);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if a reverse entry for (childTable, childField) was
|
||||||
|
/// present and has been removed.
|
||||||
|
pub fn remove(self: *ReferencedBySet, childTable: []const u8, childField: []const u8) bool {
|
||||||
|
const i = self.indexOf(childTable, childField) orelse return false;
|
||||||
|
const spec = self.specs.items[i];
|
||||||
|
self.allocator.free(spec.childTable);
|
||||||
|
self.allocator.free(spec.childField);
|
||||||
|
self.allocator.free(spec.refField);
|
||||||
|
_ = self.specs.orderedRemove(i);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *ReferencedBySet) void {
|
||||||
|
for (self.specs.items) |spec| {
|
||||||
|
self.allocator.free(spec.childTable);
|
||||||
|
self.allocator.free(spec.childField);
|
||||||
|
self.allocator.free(spec.refField);
|
||||||
|
}
|
||||||
|
self.specs.deinit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fn readLenPrefixed(allocator: Allocator, raw: []const u8, pos: *usize) ![]u8 {
|
||||||
|
if (pos.* + 4 > raw.len) return error.Malformed;
|
||||||
|
const len = std.mem.readInt(u32, raw[pos.*..][0..4], .little);
|
||||||
|
pos.* += 4;
|
||||||
|
if (pos.* + len > raw.len) return error.Malformed;
|
||||||
|
const value = try allocator.dupe(u8, raw[pos.* .. pos.* + len]);
|
||||||
|
pos.* += len;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn appendLenPrefixed(out: *std.ArrayList(u8), value: []const u8) !void {
|
||||||
|
var lenBuf: [4]u8 = undefined;
|
||||||
|
std.mem.writeInt(u32, &lenBuf, @intCast(value.len), .little);
|
||||||
|
try out.appendSlice(&lenBuf);
|
||||||
|
try out.appendSlice(value);
|
||||||
|
}
|
||||||
|
|
||||||
Reference in new issue
Block a user