feat: extend main with printCollReply and runRemoteColl

This commit is contained in:
dualfroz committed 2026-09-07 17:06:33 +02:00
1 parent ef1d0f4c06
commit 0b6c99591e
1 file changed
+173
+173
View File
@@ -3252,6 +3252,179 @@ fn runRemoteCollTtl(allocator: std.mem.Allocator, stdout: anytype, stderr: anyty
defer allocator.free(authFlags.rest); defer allocator.free(authFlags.rest);
try authenticateIfRequested(&client, stderr, authFlags.auth); try authenticateIfRequested(&client, stderr, authFlags.auth);
const op = rest[1];
const args = authFlags.rest;
if (std.mem.eql(u8, op, "expire")) {
if (args.len < 2) return fail(stderr, "remote-coll-ttl <kind> expire needs <name> <seconds>");
const seconds = std.fmt.parseInt(u64, args[1], 10) catch return fail(stderr, "expire needs a whole number of seconds");
client.collTtlExpire(kind, args[0], seconds) catch |err| {
if (err == error.ServerCollectionNotFound) return fail(stderr, "no such collection");
try stderr.print("error: {s}\n", .{@errorName(err)});
std.process.exit(1);
};
} else if (std.mem.eql(u8, op, "persist")) {
if (args.len < 1) return fail(stderr, "remote-coll-ttl <kind> persist needs <name>");
client.collTtlPersist(kind, args[0]) catch |err| {
try stderr.print("error: {s}\n", .{@errorName(err)});
std.process.exit(1);
};
} else if (std.mem.eql(u8, op, "ttl")) {
if (args.len < 1) return fail(stderr, "remote-coll-ttl <kind> ttl needs <name>");
const result = client.collTtlQuery(kind, args[0]) catch |err| {
try stderr.print("error: {s}\n", .{@errorName(err)});
std.process.exit(1);
};
switch (result) {
.noKey => try stdout.writeAll("-2\n"),
.noExpiry => try stdout.writeAll("-1\n"),
.seconds => |s| try stdout.print("{d}\n", .{s}),
}
} else {
return fail(stderr, "unknown remote-coll-ttl operation");
}
}
/// Prints an `OwnedCollReply` the same way `runColl`'s local dispatch
/// prints each op's own return value - one shape per reply variant, not
/// one shape per op, since `client.collOp`'s reply already carries enough
/// structure to tell them apart (an int reply from `set_ismember` prints
/// exactly like one from `hash_del`, both booleans-as-0/1, matching
/// `runColl`'s own `boolFlag` convention).
fn printCollReply(stdout: anytype, reply: cobalt.protocol.OwnedCollReply) !void {
switch (reply) {
.nil => try stdout.writeAll("(nil)\n"),
.ok => try stdout.writeAll("OK\n"),
.int => |v| try stdout.print("{d}\n", .{v}),
.real => |v| try stdout.print("{d}\n", .{v}),
.bytes => |v| try stdout.print("{s}\n", .{v}),
.stringList => |items| for (items) |it| try stdout.print("{s}\n", .{it}),
.pairList => |items| for (items) |p| try stdout.print("{s}\t{s}\n", .{ p.field, p.value }),
.scorePairList => |items| for (items) |p| try stdout.print("{s}\t{d}\n", .{ p.member, p.score }),
}
}
/// The network counterpart of `runColl`'s local `coll <kind> <op> ...`
/// (backed by `collections.zig`): parses the exact same
/// `<kind> <op> [args...]` shape `runColl` does, translates it to a
/// `protocol.CollAction` plus a name and string-arg list, and sends it
/// through `client.collOp` instead of calling `collections.zig` directly -
/// the network counterpart `remote-coll-ttl` already is for `coll <kind>
/// expire|ttl|persist`. There is no `remote-coll expire-sweep` counterpart:
/// unlike TTL sweeping (a background maintenance action against the whole
/// store), the read/write collection operations here always target one
/// named collection, so there is nothing store-wide to bundle behind this
/// opcode the way `coll_ttl_op`'s `sweep` action does.
fn runRemoteColl(allocator: std.mem.Allocator, stdout: anytype, stderr: anytype, addressSpec: []const u8, rest: []const []const u8) !void {
if (rest.len < 2) return fail(stderr, "remote-coll needs <kind> <op> ...");
const address = try parseHostPort(stdout, addressSpec);
var client = cobalt.Client.connect(allocator, address) catch |err| {
try stderr.print("error: could not connect to {s}: {s}\n", .{ addressSpec, @errorName(err) });
std.process.exit(1);
};
defer client.close();
const authFlags = extractAuthFlags(allocator, rest[2..]) catch return fail(stderr, "invalid --user/--pass/--token value");
defer allocator.free(authFlags.rest);
try authenticateIfRequested(&client, stderr, authFlags.auth);
const kind = rest[0];
const op = rest[1];
const args = authFlags.rest;
const Call = struct { action: cobalt.protocol.CollAction, name: []const u8, callArgs: []const []const u8 };
const call: Call = blk: {
if (std.mem.eql(u8, kind, "counter")) {
if (std.mem.eql(u8, op, "get")) {
if (args.len < 1) return fail(stderr, "counter get needs <name>");
break :blk .{ .action = .counter_get, .name = args[0], .callArgs = &.{} };
} else if (std.mem.eql(u8, op, "set")) {
if (args.len < 2) return fail(stderr, "counter set needs <name> <value>");
break :blk .{ .action = .counter_set, .name = args[0], .callArgs = args[1..2] };
} else if (std.mem.eql(u8, op, "incrby")) {
if (args.len < 2) return fail(stderr, "counter incrby needs <name> <delta>");
break :blk .{ .action = .counter_incrby, .name = args[0], .callArgs = args[1..2] };
}
return fail(stderr, "unknown counter operation");
} else if (std.mem.eql(u8, kind, "hash")) {
if (std.mem.eql(u8, op, "set")) {
if (args.len < 3) return fail(stderr, "hash set needs <name> <field> <value>");
break :blk .{ .action = .hash_set, .name = args[0], .callArgs = args[1..3] };
} else if (std.mem.eql(u8, op, "get")) {
if (args.len < 2) return fail(stderr, "hash get needs <name> <field>");
break :blk .{ .action = .hash_get, .name = args[0], .callArgs = args[1..2] };
} else if (std.mem.eql(u8, op, "del")) {
if (args.len < 2) return fail(stderr, "hash del needs <name> <field>");
break :blk .{ .action = .hash_del, .name = args[0], .callArgs = args[1..2] };
} else if (std.mem.eql(u8, op, "len")) {
if (args.len < 1) return fail(stderr, "hash len needs <name>");
break :blk .{ .action = .hash_len, .name = args[0], .callArgs = &.{} };
} else if (std.mem.eql(u8, op, "getall")) {
if (args.len < 1) return fail(stderr, "hash getall needs <name>");
break :blk .{ .action = .hash_getall, .name = args[0], .callArgs = &.{} };
}
return fail(stderr, "unknown hash operation");
} else if (std.mem.eql(u8, kind, "set")) {
if (std.mem.eql(u8, op, "add")) {
if (args.len < 2) return fail(stderr, "set add needs <name> <member>");
break :blk .{ .action = .set_add, .name = args[0], .callArgs = args[1..2] };
} else if (std.mem.eql(u8, op, "rem")) {
if (args.len < 2) return fail(stderr, "set rem needs <name> <member>");
break :blk .{ .action = .set_rem, .name = args[0], .callArgs = args[1..2] };
} else if (std.mem.eql(u8, op, "ismember")) {
if (args.len < 2) return fail(stderr, "set ismember needs <name> <member>");
break :blk .{ .action = .set_ismember, .name = args[0], .callArgs = args[1..2] };
} else if (std.mem.eql(u8, op, "card")) {
if (args.len < 1) return fail(stderr, "set card needs <name>");
break :blk .{ .action = .set_card, .name = args[0], .callArgs = &.{} };
} else if (std.mem.eql(u8, op, "members")) {
if (args.len < 1) return fail(stderr, "set members needs <name>");
break :blk .{ .action = .set_members, .name = args[0], .callArgs = &.{} };
} else if (std.mem.eql(u8, op, "inter")) {
if (args.len < 1) return fail(stderr, "set inter needs at least one <name>");
break :blk .{ .action = .set_inter, .name = args[0], .callArgs = args[1..] };
} else if (std.mem.eql(u8, op, "union")) {
if (args.len < 1) return fail(stderr, "set union needs at least one <name>");
break :blk .{ .action = .set_union, .name = args[0], .callArgs = args[1..] };
} else if (std.mem.eql(u8, op, "diff")) {
if (args.len < 2) return fail(stderr, "set diff needs <name> <other...>");
break :blk .{ .action = .set_diff, .name = args[0], .callArgs = args[1..] };
}
return fail(stderr, "unknown set operation");
} else if (std.mem.eql(u8, kind, "list")) {
if (std.mem.eql(u8, op, "lpush")) {
if (args.len < 2) return fail(stderr, "list lpush needs <name> <value>");
break :blk .{ .action = .list_lpush, .name = args[0], .callArgs = args[1..2] };
} else if (std.mem.eql(u8, op, "rpush")) {
if (args.len < 2) return fail(stderr, "list rpush needs <name> <value>");
break :blk .{ .action = .list_rpush, .name = args[0], .callArgs = args[1..2] };
} else if (std.mem.eql(u8, op, "lpop")) {
if (args.len < 1) return fail(stderr, "list lpop needs <name>");
break :blk .{ .action = .list_lpop, .name = args[0], .callArgs = &.{} };
} else if (std.mem.eql(u8, op, "rpop")) {
if (args.len < 1) return fail(stderr, "list rpop needs <name>");
break :blk .{ .action = .list_rpop, .name = args[0], .callArgs = &.{} };
} else if (std.mem.eql(u8, op, "len")) {
if (args.len < 1) return fail(stderr, "list len needs <name>");
break :blk .{ .action = .list_len, .name = args[0], .callArgs = &.{} };
} else if (std.mem.eql(u8, op, "range")) {
if (args.len < 3) return fail(stderr, "list range needs <name> <start> <stop>");
break :blk .{ .action = .list_range, .name = args[0], .callArgs = args[1..3] };
}
return fail(stderr, "unknown list operation");
} else if (std.mem.eql(u8, kind, "zset")) {
if (std.mem.eql(u8, op, "add")) {
if (args.len < 3) return fail(stderr, "zset add needs <name> <member> <score>");
break :blk .{ .action = .zset_add, .name = args[0], .callArgs = args[1..3] };
} else if (std.mem.eql(u8, op, "score")) {
if (args.len < 2) return fail(stderr, "zset score needs <name> <member>");
break :blk .{ .action = .zset_score, .name = args[0], .callArgs = args[1..2] };
} else if (std.mem.eql(u8, op, "incrby")) {
if (args.len < 3) return fail(stderr, "zset incrby needs <name> <member> <delta>");
break :blk .{ .action = .zset_incrby, .name = args[0], .callArgs = args[1..3] };
} }
fn runVerify(allocator: std.mem.Allocator, stdout: anytype, dir: []const u8, count: usize) !void { fn runVerify(allocator: std.mem.Allocator, stdout: anytype, dir: []const u8, count: usize) !void {