feat: add popLeft and popRight to collections
This commit is contained in:
1 parent
40712f5cdd
commit
d8e8662d48
1 file changed
+157
@@ -634,3 +634,160 @@ pub const List = struct {
|
|||||||
return error.TooManyConflicts;
|
return error.TooManyConflicts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn popLeft(db: *Db, allocator: Allocator, name: []const u8) !?[]u8 {
|
||||||
|
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);
|
||||||
|
if (meta.tail - meta.head <= 1) return null;
|
||||||
|
const seq = meta.head + 1;
|
||||||
|
meta.head += 1;
|
||||||
|
|
||||||
|
const ek = try elementKey(allocator, name, seq);
|
||||||
|
defer allocator.free(ek);
|
||||||
|
const value = try txn.get(allocator, ek) orelse return error.Malformed;
|
||||||
|
errdefer allocator.free(value);
|
||||||
|
try txn.delete(ek);
|
||||||
|
try writeMeta(&txn, mk, meta);
|
||||||
|
|
||||||
|
txn.commit() catch |err| {
|
||||||
|
allocator.free(value);
|
||||||
|
if (err == error.Conflict) continue;
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return error.TooManyConflicts;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn popRight(db: *Db, allocator: Allocator, name: []const u8) !?[]u8 {
|
||||||
|
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);
|
||||||
|
if (meta.tail - meta.head <= 1) return null;
|
||||||
|
const seq = meta.tail - 1;
|
||||||
|
meta.tail -= 1;
|
||||||
|
|
||||||
|
const ek = try elementKey(allocator, name, seq);
|
||||||
|
defer allocator.free(ek);
|
||||||
|
const value = try txn.get(allocator, ek) orelse return error.Malformed;
|
||||||
|
errdefer allocator.free(value);
|
||||||
|
try txn.delete(ek);
|
||||||
|
try writeMeta(&txn, mk, meta);
|
||||||
|
|
||||||
|
txn.commit() catch |err| {
|
||||||
|
allocator.free(value);
|
||||||
|
if (err == error.Conflict) continue;
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return error.TooManyConflicts;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(db: *Db, allocator: Allocator, name: []const u8) !usize {
|
||||||
|
const mk = try metaKey(allocator, name);
|
||||||
|
defer allocator.free(mk);
|
||||||
|
const raw = try db.get(allocator, mk) orelse return 0;
|
||||||
|
defer allocator.free(raw);
|
||||||
|
if (raw.len != 16) return error.Malformed;
|
||||||
|
const head = std.mem.readInt(i64, raw[0..8], .big);
|
||||||
|
const tail = std.mem.readInt(i64, raw[8..16], .big);
|
||||||
|
return @intCast(tail - head - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Redis-style inclusive range with negative indices counting from the
|
||||||
|
/// end (-1 is the last element). Out-of-range indices are clamped;
|
||||||
|
/// start > stop after clamping yields an empty result. Caller owns the
|
||||||
|
/// returned slice and each element.
|
||||||
|
pub fn range(db: *Db, allocator: Allocator, name: []const u8, start: i64, stop: i64) ![][]u8 {
|
||||||
|
const total: i64 = @intCast(try len(db, allocator, name));
|
||||||
|
|
||||||
|
var results = std.ArrayList([]u8).init(allocator);
|
||||||
|
errdefer {
|
||||||
|
for (results.items) |m| allocator.free(m);
|
||||||
|
results.deinit();
|
||||||
|
}
|
||||||
|
if (total == 0) return results.toOwnedSlice();
|
||||||
|
|
||||||
|
var lo = if (start < 0) total + start else start;
|
||||||
|
var hi = if (stop < 0) total + stop else stop;
|
||||||
|
if (lo < 0) lo = 0;
|
||||||
|
if (hi > total - 1) hi = total - 1;
|
||||||
|
if (lo > hi) return results.toOwnedSlice();
|
||||||
|
|
||||||
|
const p = try elementPrefix(allocator, name);
|
||||||
|
defer allocator.free(p);
|
||||||
|
var it = try db.scanPrefix(allocator, p);
|
||||||
|
defer it.deinit();
|
||||||
|
|
||||||
|
var idx: i64 = 0;
|
||||||
|
while (try it.next()) |entry| {
|
||||||
|
if (idx >= lo and idx <= hi) {
|
||||||
|
try results.append(try allocator.dupe(u8, entry.value));
|
||||||
|
}
|
||||||
|
idx += 1;
|
||||||
|
}
|
||||||
|
return results.toOwnedSlice();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// ZSet (sorted set: member -> score, plus a score-ordered index for
|
||||||
|
// range/rank queries)
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
pub const ZMember = struct {
|
||||||
|
member: []u8,
|
||||||
|
score: f64,
|
||||||
|
|
||||||
|
pub fn free(self: ZMember, allocator: Allocator) void {
|
||||||
|
allocator.free(self.member);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ZSet = struct {
|
||||||
|
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, "Z");
|
||||||
|
try appendSegment(&out, name);
|
||||||
|
try appendSegment(&out, "M");
|
||||||
|
try out.appendSlice(member);
|
||||||
|
return out.toOwnedSlice();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scorePrefix(allocator: Allocator, name: []const u8) ![]u8 {
|
||||||
|
var out = std.ArrayList(u8).init(allocator);
|
||||||
|
errdefer out.deinit();
|
||||||
|
try out.append(nsTag);
|
||||||
|
try appendSegment(&out, "Z");
|
||||||
|
try appendSegment(&out, name);
|
||||||
|
try appendSegment(&out, "S");
|
||||||
|
return out.toOwnedSlice();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scoreEntryKey(allocator: Allocator, name: []const u8, scoreVal: f64, member: []const u8) ![]u8 {
|
||||||
|
var out = std.ArrayList(u8).init(allocator);
|
||||||
|
errdefer out.deinit();
|
||||||
|
try out.append(nsTag);
|
||||||
|
try appendSegment(&out, "Z");
|
||||||
|
try appendSegment(&out, name);
|
||||||
|
try appendSegment(&out, "S");
|
||||||
|
try row.appendOrdered(&out, .{ .float = scoreVal });
|
||||||
|
try row.appendOrdered(&out, .{ .text = member });
|
||||||
|
return out.toOwnedSlice();
|
||||||
|
}
|
||||||
|
|
||||||
Reference in new issue
Block a user