From 0b6c99591eae7377e7dae021588f821ae30f6008 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Mon, 7 Sep 2026 17:06:33 +0200 Subject: [PATCH] feat: extend main with printCollReply and runRemoteColl --- src/main.zig | 173 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) diff --git a/src/main.zig b/src/main.zig index 3c36747..1d3204f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3252,6 +3252,179 @@ fn runRemoteCollTtl(allocator: std.mem.Allocator, stdout: anytype, stderr: anyty defer allocator.free(authFlags.rest); 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 expire needs "); + 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 persist needs "); + 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 ttl needs "); + 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 ...` +/// (backed by `collections.zig`): parses the exact same +/// ` [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 +/// 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 ..."); + 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 "); + 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 "); + 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 "); + 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 "); + 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 "); + 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 "); + 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 "); + 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 "); + 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 "); + 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 "); + 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 "); + 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 "); + 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 "); + 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 "); + 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 "); + 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 "); + 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 "); + 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 "); + 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 "); + 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 "); + 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 "); + 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 "); + 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 "); + 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 "); + 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 "); + 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 {