feat: add prefixUpperBound and Counter to collections
This commit is contained in:
1 parent
4c750209f1
commit
ca9299e7cb
1 file changed
+163
@@ -159,3 +159,166 @@ pub fn dropCollection(db: *Db, allocator: Allocator, k: Kind, name: []const u8)
|
||||
return 1;
|
||||
}
|
||||
|
||||
const p = try genericPrefix(allocator, k, name);
|
||||
defer allocator.free(p);
|
||||
|
||||
var keys = std.ArrayList([]u8).init(allocator);
|
||||
defer {
|
||||
for (keys.items) |kk| allocator.free(kk);
|
||||
keys.deinit();
|
||||
}
|
||||
{
|
||||
// Collect first, delete after: db.delete mid-scan would mutate the
|
||||
// memtable structure a live RangeIterator's cursors are threaded
|
||||
// through (same reasoning as ttl.zig's sweepExpired).
|
||||
var it = try db.scanPrefix(allocator, p);
|
||||
defer it.deinit();
|
||||
while (try it.next()) |entry| {
|
||||
try keys.append(try allocator.dupe(u8, entry.key));
|
||||
}
|
||||
}
|
||||
for (keys.items) |kk| try db.delete(kk);
|
||||
return keys.items.len;
|
||||
}
|
||||
|
||||
fn prefixUpperBound(allocator: Allocator, prefix: []const u8) !?[]u8 {
|
||||
var end = prefix.len;
|
||||
while (end > 0 and prefix[end - 1] == 0xff) end -= 1;
|
||||
if (end == 0) return null;
|
||||
const upper = try allocator.alloc(u8, end);
|
||||
@memcpy(upper, prefix[0..end]);
|
||||
upper[end - 1] += 1;
|
||||
return upper;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Counter
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
pub const Counter = struct {
|
||||
fn key(allocator: Allocator, name: []const u8) ![]u8 {
|
||||
var out = std.ArrayList(u8).init(allocator);
|
||||
errdefer out.deinit();
|
||||
try out.append(nsTag);
|
||||
try appendSegment(&out, "C");
|
||||
try out.appendSlice(name);
|
||||
return out.toOwnedSlice();
|
||||
}
|
||||
|
||||
pub fn get(db: *Db, allocator: Allocator, name: []const u8) !i64 {
|
||||
const k = try key(allocator, name);
|
||||
defer allocator.free(k);
|
||||
const raw = try db.get(allocator, k) orelse return 0;
|
||||
defer allocator.free(raw);
|
||||
return readI64(raw);
|
||||
}
|
||||
|
||||
pub fn set(db: *Db, allocator: Allocator, name: []const u8, value: i64) !void {
|
||||
const k = try key(allocator, name);
|
||||
defer allocator.free(k);
|
||||
var out = std.ArrayList(u8).init(allocator);
|
||||
defer out.deinit();
|
||||
try putI64(&out, value);
|
||||
try db.put(k, out.items);
|
||||
}
|
||||
|
||||
/// Atomically adds `delta` to the counter (creating it at 0 if absent)
|
||||
/// and returns the new value. Uses an OCC transaction so concurrent
|
||||
/// increments never lose an update to a lost read-modify-write race.
|
||||
pub fn incrBy(db: *Db, allocator: Allocator, name: []const u8, delta: i64) !i64 {
|
||||
const k = try key(allocator, name);
|
||||
defer allocator.free(k);
|
||||
|
||||
var attempt: usize = 0;
|
||||
while (attempt < maxConflictRetries) : (attempt += 1) {
|
||||
var txn = Transaction.begin(db);
|
||||
defer txn.deinit();
|
||||
|
||||
const current: i64 = blk: {
|
||||
const raw = try txn.get(allocator, k) orelse break :blk 0;
|
||||
defer allocator.free(raw);
|
||||
break :blk try readI64(raw);
|
||||
};
|
||||
const next = current + delta;
|
||||
|
||||
var out = std.ArrayList(u8).init(allocator);
|
||||
defer out.deinit();
|
||||
try putI64(&out, next);
|
||||
try txn.put(k, out.items);
|
||||
|
||||
txn.commit() catch |err| {
|
||||
if (err == error.Conflict) continue;
|
||||
return err;
|
||||
};
|
||||
return next;
|
||||
}
|
||||
return error.TooManyConflicts;
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Hash
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
pub const HashField = struct {
|
||||
field: []u8,
|
||||
value: []u8,
|
||||
|
||||
pub fn free(self: HashField, allocator: Allocator) void {
|
||||
allocator.free(self.field);
|
||||
allocator.free(self.value);
|
||||
}
|
||||
};
|
||||
|
||||
pub const Hash = 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, "H");
|
||||
try appendSegment(&out, name);
|
||||
return out.toOwnedSlice();
|
||||
}
|
||||
|
||||
fn fieldKey(allocator: Allocator, name: []const u8, field: []const u8) ![]u8 {
|
||||
var out = std.ArrayList(u8).init(allocator);
|
||||
errdefer out.deinit();
|
||||
try out.append(nsTag);
|
||||
try appendSegment(&out, "H");
|
||||
try appendSegment(&out, name);
|
||||
try out.appendSlice(field);
|
||||
return out.toOwnedSlice();
|
||||
}
|
||||
|
||||
/// Sets a field, returning true if the field was newly created.
|
||||
pub fn set(db: *Db, allocator: Allocator, name: []const u8, field: []const u8, value: []const u8) !bool {
|
||||
const k = try fieldKey(allocator, name, field);
|
||||
defer allocator.free(k);
|
||||
const existing = try db.get(allocator, k);
|
||||
if (existing) |raw| allocator.free(raw);
|
||||
try db.put(k, value);
|
||||
return existing == null;
|
||||
}
|
||||
|
||||
pub fn get(db: *Db, allocator: Allocator, name: []const u8, field: []const u8) !?[]u8 {
|
||||
const k = try fieldKey(allocator, name, field);
|
||||
defer allocator.free(k);
|
||||
return db.get(allocator, k);
|
||||
}
|
||||
|
||||
pub fn exists(db: *Db, allocator: Allocator, name: []const u8, field: []const u8) !bool {
|
||||
const raw = try get(db, allocator, name, field) orelse return false;
|
||||
allocator.free(raw);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Deletes a field, returning true if it existed.
|
||||
pub fn del(db: *Db, allocator: Allocator, name: []const u8, field: []const u8) !bool {
|
||||
const existed = try exists(db, allocator, name, field);
|
||||
if (!existed) return false;
|
||||
const k = try fieldKey(allocator, name, field);
|
||||
defer allocator.free(k);
|
||||
try db.delete(k);
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user