feat: add TtlAction and fromByte to protocol
This commit is contained in:
1 parent
4c072af95d
commit
fc90b6c8a3
1 file changed
+176
@@ -313,3 +313,179 @@ pub fn decodeUserList(allocator: Allocator, payload: []const u8) ![]authmod.User
|
||||
|
||||
// --- ttl_op ---
|
||||
|
||||
/// Remote counterpart of the CLI's local `expire`/`persist`/`ttl`/
|
||||
/// `expire-sweep` (main.zig, backed by `ttl.zig`), carried as one opcode the
|
||||
/// same way `user_admin` bundles five user-management actions under one
|
||||
/// opcode - see server.zig's `requiredRole`, which (unlike `user_admin`'s
|
||||
/// blanket `admin` gate) inspects this action byte: `query` is read-only
|
||||
/// and needs only `readonly`; `expire`/`persist`/`sweep` all mutate (sweep
|
||||
/// deletes every due key) and need `readwrite`, matching every other write
|
||||
/// opcode.
|
||||
pub const TtlAction = enum(u8) {
|
||||
expire = 0,
|
||||
persist = 1,
|
||||
query = 2,
|
||||
sweep = 3,
|
||||
|
||||
pub fn fromByte(b: u8) !TtlAction {
|
||||
return std.meta.intToEnum(TtlAction, b) catch error.Malformed;
|
||||
}
|
||||
};
|
||||
|
||||
/// `seconds` is only meaningful for `expire` - callers send 0 for the other
|
||||
/// three actions, same "unused fields carry a zero" convention as
|
||||
/// `encodeUserAdmin`'s `role`/`rounds`. `key` is empty for `sweep`, which
|
||||
/// operates on every due key in the store rather than one specific key.
|
||||
pub fn encodeTtlOp(allocator: Allocator, action: TtlAction, key: []const u8, seconds: u64) ![]u8 {
|
||||
var buf = std.ArrayList(u8).init(allocator);
|
||||
errdefer buf.deinit();
|
||||
const w = encode.ByteWriter.init(&buf);
|
||||
try w.byte(@intFromEnum(action));
|
||||
try w.putU64(seconds);
|
||||
try w.slice(key);
|
||||
return buf.toOwnedSlice();
|
||||
}
|
||||
|
||||
pub const TtlOpArgs = struct { action: TtlAction, seconds: u64, key: []const u8 };
|
||||
|
||||
pub fn decodeTtlOp(payload: []const u8) !TtlOpArgs {
|
||||
var c = Cursor{ .data = payload };
|
||||
const action = try TtlAction.fromByte(try c.takeByte());
|
||||
const seconds = try c.takeU64();
|
||||
const key = c.data[c.pos..];
|
||||
return .{ .action = action, .seconds = seconds, .key = key };
|
||||
}
|
||||
|
||||
/// Encodes the response to a `query` ttl_op: reuses `ttlmod.TtlResult`
|
||||
/// rather than inventing a parallel type, the same way `encodeStats` reuses
|
||||
/// `dbmod.Stats` and `encodeUserList` reuses `authmod.UserInfo`.
|
||||
pub fn encodeTtlResult(allocator: Allocator, result: ttlmod.TtlResult) ![]u8 {
|
||||
var buf = std.ArrayList(u8).init(allocator);
|
||||
errdefer buf.deinit();
|
||||
const w = encode.ByteWriter.init(&buf);
|
||||
switch (result) {
|
||||
.noKey => try w.byte(0),
|
||||
.noExpiry => try w.byte(1),
|
||||
.seconds => |s| {
|
||||
try w.byte(2);
|
||||
try w.putU64(s);
|
||||
},
|
||||
}
|
||||
return buf.toOwnedSlice();
|
||||
}
|
||||
|
||||
pub fn decodeTtlResult(payload: []const u8) !ttlmod.TtlResult {
|
||||
var c = Cursor{ .data = payload };
|
||||
const kind = try c.takeByte();
|
||||
return switch (kind) {
|
||||
0 => .noKey,
|
||||
1 => .noExpiry,
|
||||
2 => .{ .seconds = try c.takeU64() },
|
||||
else => error.Malformed,
|
||||
};
|
||||
}
|
||||
|
||||
/// Encodes the response to a `sweep` ttl_op: how many due keys were reaped.
|
||||
pub fn encodeTtlSweepCount(allocator: Allocator, count: u64) ![]u8 {
|
||||
var buf = std.ArrayList(u8).init(allocator);
|
||||
errdefer buf.deinit();
|
||||
const w = encode.ByteWriter.init(&buf);
|
||||
try w.putU64(count);
|
||||
return buf.toOwnedSlice();
|
||||
}
|
||||
|
||||
pub fn decodeTtlSweepCount(payload: []const u8) !u64 {
|
||||
var c = Cursor{ .data = payload };
|
||||
return c.takeU64();
|
||||
}
|
||||
|
||||
// --- coll_ttl_op ---
|
||||
|
||||
/// Remote counterpart of the CLI's local `coll <kind> expire|ttl|persist
|
||||
/// <name>`/`coll expire-sweep` (main.zig, backed by `collttl.zig`), same
|
||||
/// bundling shape as `ttl_op` above but for a whole typed collection
|
||||
/// (Counter/Hash/Set/List/ZSet) instead of one flat key - so the payload
|
||||
/// carries an extra `Kind` tag byte the flat version does not need.
|
||||
/// Reuses `TtlAction` directly rather than a parallel enum: the four
|
||||
/// actions (expire/persist/query/sweep) mean exactly the same thing here,
|
||||
/// just against `collttl.zig` instead of `ttl.zig`.
|
||||
pub fn encodeCollTtlOp(allocator: Allocator, action: TtlAction, kind: collmod.Kind, name: []const u8, seconds: u64) ![]u8 {
|
||||
var buf = std.ArrayList(u8).init(allocator);
|
||||
errdefer buf.deinit();
|
||||
const w = encode.ByteWriter.init(&buf);
|
||||
try w.byte(@intFromEnum(action));
|
||||
// `sweep` operates across every kind at once (collttl.sweepExpired
|
||||
// scans the whole marker namespace, not one kind's slice of it), so
|
||||
// the kind byte is meaningless for that action - callers pass any
|
||||
// valid `Kind` and `decodeCollTtlOp` still decodes it, but
|
||||
// `handleCollTtlOp` never reads it for `.sweep`. Same "unused field
|
||||
// still round-trips" convention as `encodeTtlOp`'s zeroed `seconds`
|
||||
// on non-`expire` actions.
|
||||
try w.byte(kind.tagByte());
|
||||
try w.putU64(seconds);
|
||||
try w.slice(name);
|
||||
return buf.toOwnedSlice();
|
||||
}
|
||||
|
||||
pub const CollTtlOpArgs = struct { action: TtlAction, kind: collmod.Kind, seconds: u64, name: []const u8 };
|
||||
|
||||
pub fn decodeCollTtlOp(payload: []const u8) !CollTtlOpArgs {
|
||||
var c = Cursor{ .data = payload };
|
||||
const action = try TtlAction.fromByte(try c.takeByte());
|
||||
const kind = collmod.Kind.fromTagByte(try c.takeByte()) orelse return error.Malformed;
|
||||
const seconds = try c.takeU64();
|
||||
const name = c.data[c.pos..];
|
||||
return .{ .action = action, .kind = kind, .seconds = seconds, .name = name };
|
||||
}
|
||||
|
||||
// --- coll_op ---
|
||||
|
||||
/// Remote counterpart of the CLI's local `coll <kind> <op> ...` (main.zig's
|
||||
/// `runColl`, backed by `collections.zig`) - until now the Redis-like
|
||||
/// collections (Counter/Hash/Set/List/ZSet) were reachable only from a
|
||||
/// process that had the `Db` open directly, the same gap `ttl_op`/
|
||||
/// `coll_ttl_op` already closed for TTL management. One opcode bundles
|
||||
/// every collection operation (the same "one opcode, many actions" shape
|
||||
/// as `user_admin` and `ttl_op`) rather than one opcode per op, since the
|
||||
/// five kinds share almost nothing about their argument count or type.
|
||||
/// The payload carries only the action tag, the collection name, and a
|
||||
/// variable list of string arguments in the exact CLI text form `runColl`
|
||||
/// already parses locally (values/members/indices/scores) - so
|
||||
/// `handleCollOp` (server.zig) can reuse the very same
|
||||
/// `std.fmt.parseInt`/`parseFloat` calls instead of inventing a second
|
||||
/// binary number encoding just for this opcode.
|
||||
pub const CollAction = enum(u8) {
|
||||
counter_get = 0,
|
||||
counter_set = 1,
|
||||
counter_incrby = 2,
|
||||
hash_set = 3,
|
||||
hash_get = 4,
|
||||
hash_del = 5,
|
||||
hash_len = 6,
|
||||
hash_getall = 7,
|
||||
set_add = 8,
|
||||
set_rem = 9,
|
||||
set_ismember = 10,
|
||||
set_card = 11,
|
||||
set_members = 12,
|
||||
// `set_inter`/`set_union`/`set_diff`: `name` is the first set, `args`
|
||||
// are every remaining set name - the same split `runColl` already uses
|
||||
// (`args[0]` vs `args[1..]`) for these three multi-set operations.
|
||||
set_inter = 13,
|
||||
set_union = 14,
|
||||
set_diff = 15,
|
||||
list_lpush = 16,
|
||||
list_rpush = 17,
|
||||
list_lpop = 18,
|
||||
list_rpop = 19,
|
||||
list_len = 20,
|
||||
list_range = 21,
|
||||
zset_add = 22,
|
||||
zset_score = 23,
|
||||
zset_incrby = 24,
|
||||
zset_rem = 25,
|
||||
zset_card = 26,
|
||||
zset_rangebyrank = 27,
|
||||
zset_rangebyscore = 28,
|
||||
zset_rank = 29,
|
||||
|
||||
Reference in new issue
Block a user