diff --git a/src/index.zig b/src/index.zig index 4790266..ff26b2a 100644 --- a/src/index.zig +++ b/src/index.zig @@ -473,3 +473,164 @@ pub const ForeignKeySet = struct { return set; } + pub fn save(self: *const ForeignKeySet, db: *Db, table: []const u8) !void { + const key = try foreignKeyMetaKey(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.field); + try appendLenPrefixed(&out, spec.refTable); + try appendLenPrefixed(&out, spec.refField); + try out.append(@intFromEnum(spec.onDelete)); + } + try db.put(key, out.items); + } + + pub fn has(self: *const ForeignKeySet, field: []const u8) bool { + return self.find(field) != null; + } + + pub fn find(self: *const ForeignKeySet, field: []const u8) ?ForeignKeySpec { + for (self.specs.items) |spec| { + if (std.mem.eql(u8, spec.field, field)) return spec; + } + return null; + } + + pub fn add(self: *ForeignKeySet, field: []const u8, refTable: []const u8, refField: []const u8, onDelete: OnDelete) !void { + if (self.has(field)) return; + const spec = ForeignKeySpec{ + .field = try self.allocator.dupe(u8, field), + .refTable = try self.allocator.dupe(u8, refTable), + .refField = try self.allocator.dupe(u8, refField), + .onDelete = onDelete, + }; + try self.specs.append(spec); + } + + /// Returns true if a constraint on `field` was present and has been + /// removed. + pub fn remove(self: *ForeignKeySet, field: []const u8) bool { + for (self.specs.items, 0..) |spec, i| { + if (std.mem.eql(u8, spec.field, field)) { + self.allocator.free(spec.field); + self.allocator.free(spec.refTable); + self.allocator.free(spec.refField); + _ = self.specs.orderedRemove(i); + return true; + } + } + return false; + } + + pub fn deinit(self: *ForeignKeySet) void { + for (self.specs.items) |spec| { + self.allocator.free(spec.field); + self.allocator.free(spec.refTable); + self.allocator.free(spec.refField); + } + self.specs.deinit(); + } +}; + +/// One named CHECK constraint: a caller-chosen `name` (used only as the key +/// under which the constraint can be dropped again, not part of the +/// keyspace layout - unlike `CompositeIndexSpec.name`, no index entries are +/// ever written for a CHECK) paired with the raw `expr.zig` predicate source +/// text it must evaluate true against. Stored as source rather than a parsed +/// tree because `index.zig` sits below `query.zig`/`expr.zig` in the +/// dependency graph and knows nothing about `Predicate` - parsing happens +/// fresh in whichever layer enforces it (`query.zig`'s `Table.put`, +/// `txn_table.zig`'s `TableTxn.put`), the same "reparse on every use" cost +/// `table select`/`table join` already pay for their own expr text. +pub const CheckConstraintSpec = struct { + name: []u8, + source: []u8, +}; + +/// Persisted set of CHECK constraints on a table, at most one per name - +/// same on-disk shape family as `ForeignKeySet` (own meta key, own +/// namespace, two length-prefixed strings per entry). +pub const CheckConstraintSet = struct { + allocator: Allocator, + specs: std.ArrayList(CheckConstraintSpec), + + pub fn load(allocator: Allocator, db: *Db, table: []const u8) !CheckConstraintSet { + const key = try checkMetaKey(allocator, table); + defer allocator.free(key); + + var set = CheckConstraintSet{ .allocator = allocator, .specs = std.ArrayList(CheckConstraintSpec).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 name = try readLenPrefixed(allocator, raw, &pos); + errdefer allocator.free(name); + const source = try readLenPrefixed(allocator, raw, &pos); + try set.specs.append(.{ .name = name, .source = source }); + } + return set; + } + + pub fn save(self: *const CheckConstraintSet, db: *Db, table: []const u8) !void { + const key = try checkMetaKey(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.name); + try appendLenPrefixed(&out, spec.source); + } + try db.put(key, out.items); + } + + pub fn has(self: *const CheckConstraintSet, name: []const u8) bool { + return self.find(name) != null; + } + + pub fn find(self: *const CheckConstraintSet, name: []const u8) ?CheckConstraintSpec { + for (self.specs.items) |spec| { + if (std.mem.eql(u8, spec.name, name)) return spec; + } + return null; + } + + pub fn add(self: *CheckConstraintSet, name: []const u8, source: []const u8) !void { + if (self.has(name)) return; + const spec = CheckConstraintSpec{ + .name = try self.allocator.dupe(u8, name), + .source = try self.allocator.dupe(u8, source), + }; + try self.specs.append(spec); + } + + /// Returns true if a constraint named `name` was present and has been + /// removed. + pub fn remove(self: *CheckConstraintSet, name: []const u8) bool { + for (self.specs.items, 0..) |spec, i| { + if (std.mem.eql(u8, spec.name, name)) { + self.allocator.free(spec.name); + self.allocator.free(spec.source); + _ = self.specs.orderedRemove(i); + return true; + } + } + return false; + } +