diff --git a/src/collections.zig b/src/collections.zig index 1aa13e1..6d816c4 100644 --- a/src/collections.zig +++ b/src/collections.zig @@ -474,3 +474,163 @@ pub const Set = struct { results.deinit(); } + for (names) |name| { + const ms = try members(db, allocator, name); + defer { + for (ms) |m| allocator.free(m); + allocator.free(ms); + } + for (ms) |m| { + var seen = false; + for (results.items) |existing| { + if (std.mem.eql(u8, existing, m)) { + seen = true; + break; + } + } + if (!seen) try results.append(try allocator.dupe(u8, m)); + } + } + return results.toOwnedSlice(); + } + + /// Members in `name` that are not in any set of `others`. Caller owns the result. + pub fn difference(db: *Db, allocator: Allocator, name: []const u8, others: []const []const u8) ![][]u8 { + var results = std.ArrayList([]u8).init(allocator); + errdefer { + for (results.items) |m| allocator.free(m); + results.deinit(); + } + + const base = try members(db, allocator, name); + defer { + for (base) |m| allocator.free(m); + allocator.free(base); + } + + outer: for (base) |candidate| { + for (others) |other| { + if (try isMember(db, allocator, other, candidate)) continue :outer; + } + try results.append(try allocator.dupe(u8, candidate)); + } + return results.toOwnedSlice(); + } +}; + +// --------------------------------------------------------------------- +// List (double-ended queue keyed by a monotonic sequence range) +// --------------------------------------------------------------------- + +const ListMeta = struct { + // Elements occupy sequence numbers (head+1 .. tail-1) inclusive; an + // empty list has tail == head + 1. Pushing left decrements head and + // writes at the new head; pushing right increments tail and writes at + // the slot just below the new tail. + head: i64, + tail: i64, +}; + +pub const List = struct { + fn metaKey(allocator: Allocator, name: []const u8) ![]u8 { + var out = std.ArrayList(u8).init(allocator); + errdefer out.deinit(); + try out.append(nsTag); + try appendSegment(&out, "L"); + try appendSegment(&out, name); + try out.append('M'); + return out.toOwnedSlice(); + } + + fn elementPrefix(allocator: Allocator, name: []const u8) ![]u8 { + var out = std.ArrayList(u8).init(allocator); + errdefer out.deinit(); + try out.append(nsTag); + try appendSegment(&out, "L"); + try appendSegment(&out, name); + try appendSegment(&out, "E"); + return out.toOwnedSlice(); + } + + fn elementKey(allocator: Allocator, name: []const u8, seq: i64) ![]u8 { + var out = std.ArrayList(u8).init(allocator); + errdefer out.deinit(); + try out.append(nsTag); + try appendSegment(&out, "L"); + try appendSegment(&out, name); + try appendSegment(&out, "E"); + try row.appendOrdered(&out, .{ .int = seq }); + return out.toOwnedSlice(); + } + + fn readMeta(txn: *Transaction, allocator: Allocator, k: []const u8) !ListMeta { + const raw = try txn.get(allocator, k) orelse return ListMeta{ .head = 0, .tail = 1 }; + defer allocator.free(raw); + if (raw.len != 16) return error.Malformed; + return .{ + .head = std.mem.readInt(i64, raw[0..8], .big), + .tail = std.mem.readInt(i64, raw[8..16], .big), + }; + } + + fn writeMeta(txn: *Transaction, k: []const u8, meta: ListMeta) !void { + var buf: [16]u8 = undefined; + std.mem.writeInt(i64, buf[0..8], meta.head, .big); + std.mem.writeInt(i64, buf[8..16], meta.tail, .big); + try txn.put(k, &buf); + } + + pub fn pushLeft(db: *Db, allocator: Allocator, name: []const u8, value: []const u8) !void { + const mk = try metaKey(allocator, name); + defer allocator.free(mk); + + var attempt: usize = 0; + while (attempt < maxConflictRetries) : (attempt += 1) { + var txn = Transaction.begin(db); + defer txn.deinit(); + + var meta = try readMeta(&txn, allocator, mk); + const seq = meta.head; + meta.head -= 1; + + const ek = try elementKey(allocator, name, seq); + defer allocator.free(ek); + try txn.put(ek, value); + try writeMeta(&txn, mk, meta); + + txn.commit() catch |err| { + if (err == error.Conflict) continue; + return err; + }; + return; + } + return error.TooManyConflicts; + } + + pub fn pushRight(db: *Db, allocator: Allocator, name: []const u8, value: []const u8) !void { + const mk = try metaKey(allocator, name); + defer allocator.free(mk); + + var attempt: usize = 0; + while (attempt < maxConflictRetries) : (attempt += 1) { + var txn = Transaction.begin(db); + defer txn.deinit(); + + var meta = try readMeta(&txn, allocator, mk); + const seq = meta.tail; + meta.tail += 1; + + const ek = try elementKey(allocator, name, seq); + defer allocator.free(ek); + try txn.put(ek, value); + try writeMeta(&txn, mk, meta); + + txn.commit() catch |err| { + if (err == error.Conflict) continue; + return err; + }; + return; + } + return error.TooManyConflicts; + } +