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;
|
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