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