diff --git a/src/collections.zig b/src/collections.zig index 500d83f..1aa13e1 100644 --- a/src/collections.zig +++ b/src/collections.zig @@ -322,3 +322,155 @@ pub const Hash = struct { return true; } + pub fn len(db: *Db, allocator: Allocator, name: []const u8) !usize { + const p = try prefix(allocator, name); + defer allocator.free(p); + var it = try db.scanPrefix(allocator, p); + defer it.deinit(); + var count: usize = 0; + while (try it.next()) |_| count += 1; + return count; + } + + /// Returns every field/value pair currently stored under `name`. Caller + /// owns the returned slice and each entry's `field`/`value` buffers. + pub fn getAll(db: *Db, allocator: Allocator, name: []const u8) ![]HashField { + const p = try prefix(allocator, name); + defer allocator.free(p); + + var results = std.ArrayList(HashField).init(allocator); + errdefer { + for (results.items) |f| f.free(allocator); + results.deinit(); + } + + var it = try db.scanPrefix(allocator, p); + defer it.deinit(); + while (try it.next()) |entry| { + const field = try allocator.dupe(u8, entry.key[p.len..]); + errdefer allocator.free(field); + const value = try allocator.dupe(u8, entry.value); + try results.append(.{ .field = field, .value = value }); + } + return results.toOwnedSlice(); + } +}; + +// --------------------------------------------------------------------- +// Set +// --------------------------------------------------------------------- + +pub const Set = struct { + fn prefix(allocator: Allocator, name: []const u8) ![]u8 { + var out = std.ArrayList(u8).init(allocator); + errdefer out.deinit(); + try out.append(nsTag); + try appendSegment(&out, "S"); + try appendSegment(&out, name); + return out.toOwnedSlice(); + } + + fn memberKey(allocator: Allocator, name: []const u8, member: []const u8) ![]u8 { + var out = std.ArrayList(u8).init(allocator); + errdefer out.deinit(); + try out.append(nsTag); + try appendSegment(&out, "S"); + try appendSegment(&out, name); + try out.appendSlice(member); + return out.toOwnedSlice(); + } + + /// Returns true if the member was newly added (false if already present). + pub fn add(db: *Db, allocator: Allocator, name: []const u8, member: []const u8) !bool { + if (try isMember(db, allocator, name, member)) return false; + const k = try memberKey(allocator, name, member); + defer allocator.free(k); + try db.put(k, &.{}); + return true; + } + + /// Returns true if the member was present and has been removed. + pub fn remove(db: *Db, allocator: Allocator, name: []const u8, member: []const u8) !bool { + if (!try isMember(db, allocator, name, member)) return false; + const k = try memberKey(allocator, name, member); + defer allocator.free(k); + try db.delete(k); + return true; + } + + pub fn isMember(db: *Db, allocator: Allocator, name: []const u8, member: []const u8) !bool { + const k = try memberKey(allocator, name, member); + defer allocator.free(k); + const raw = try db.get(allocator, k) orelse return false; + allocator.free(raw); + return true; + } + + pub fn card(db: *Db, allocator: Allocator, name: []const u8) !usize { + const members_ = try members(db, allocator, name); + defer { + for (members_) |m| allocator.free(m); + allocator.free(members_); + } + return members_.len; + } + + /// Returns every member currently in the set. Caller owns the slice and + /// each element. + pub fn members(db: *Db, allocator: Allocator, name: []const u8) ![][]u8 { + const p = try prefix(allocator, name); + defer allocator.free(p); + + var results = std.ArrayList([]u8).init(allocator); + errdefer { + for (results.items) |m| allocator.free(m); + results.deinit(); + } + + var it = try db.scanPrefix(allocator, p); + defer it.deinit(); + while (try it.next()) |entry| { + try results.append(try allocator.dupe(u8, entry.key[p.len..])); + } + return results.toOwnedSlice(); + } + + fn containsSorted(haystack: [][]const u8, needle: []const u8) bool { + for (haystack) |h| { + if (std.mem.eql(u8, h, needle)) return true; + } + return false; + } + + /// Members present in every one of `names`. Caller owns the result. + pub fn intersect(db: *Db, allocator: Allocator, names: []const []const u8) ![][]u8 { + var results = std.ArrayList([]u8).init(allocator); + errdefer { + for (results.items) |m| allocator.free(m); + results.deinit(); + } + if (names.len == 0) return results.toOwnedSlice(); + + const first = try members(db, allocator, names[0]); + defer { + for (first) |m| allocator.free(m); + allocator.free(first); + } + + outer: for (first) |candidate| { + for (names[1..]) |other| { + if (!try isMember(db, allocator, other, candidate)) continue :outer; + } + try results.append(try allocator.dupe(u8, candidate)); + } + return results.toOwnedSlice(); + } + + /// Members present in any of `names`, deduplicated. Caller owns the result. + pub fn unionOf(db: *Db, allocator: Allocator, names: []const []const u8) ![][]u8 { + var results = std.ArrayList([]u8).init(allocator); + errdefer { + for (results.items) |m| allocator.free(m); + results.deinit(); + } +