feat: extend main with boolFlag and runColl

This commit is contained in:
dualfroz committed 2026-09-06 20:33:04 +02:00
1 parent 02c5e6da3a
commit bdeec4bfdf
1 file changed
+173
+173
View File
@@ -832,6 +832,179 @@ fn fail(stderr: anytype, message: []const u8) !void {
std.process.exit(2); std.process.exit(2);
} }
fn boolFlag(v: bool) u8 {
return if (v) 1 else 0;
}
/// Dispatches `coll <kind> <op> [args...]` onto the Redis-like typed
/// collections in collections.zig (Counter/Hash/Set/List/ZSet), which until
/// now were only reachable through the library API, never a CLI.
fn runColl(allocator: std.mem.Allocator, stdout: anytype, stderr: anytype, db: *cobalt.Db, rest: []const []const u8) !void {
if (rest.len >= 1 and std.mem.eql(u8, rest[0], "expire-sweep")) {
const reaped = try cobalt.collttl.sweepExpired(db, allocator, std.time.nanoTimestamp());
try stdout.print("reaped: {d}\n", .{reaped});
return;
}
if (rest.len < 2) return fail(stderr, "coll needs <kind> <op> ...");
const kind = rest[0];
const op = rest[1];
const args = rest[2..];
if (std.mem.eql(u8, op, "expire") or std.mem.eql(u8, op, "ttl") or std.mem.eql(u8, op, "persist")) {
const k = cobalt.CollectionKind.parse(kind) orelse return fail(stderr, "unknown collection kind (expected counter/hash/set/list/zset)");
return runCollTtl(allocator, stdout, stderr, db, k, op, args);
}
// Lazy expiry on the read/write path: every remaining op below takes the
// collection name as args[0], so this is what keeps a bare
// `coll hash get <name> ...` from ever reading a collection that is
// logically already gone. `set inter|union|diff` accept further names
// past args[0] - those get their own expireIfDueMany call right before
// dispatch, so this single-name check only needs to cover args[0].
if (args.len >= 1) {
if (cobalt.CollectionKind.parse(kind)) |k| {
_ = try cobalt.collttl.expireIfDue(db, allocator, k, args[0], std.time.nanoTimestamp());
}
}
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>");
const v = try cobalt.Counter.get(db, allocator, args[0]);
try stdout.print("{d}\n", .{v});
} else if (std.mem.eql(u8, op, "set")) {
if (args.len < 2) return fail(stderr, "counter set needs <name> <value>");
const v = std.fmt.parseInt(i64, args[1], 10) catch return fail(stderr, "invalid integer value");
try cobalt.Counter.set(db, allocator, args[0], v);
try stdout.writeAll("OK\n");
} else if (std.mem.eql(u8, op, "incrby")) {
if (args.len < 2) return fail(stderr, "counter incrby needs <name> <delta>");
const delta = std.fmt.parseInt(i64, args[1], 10) catch return fail(stderr, "invalid integer delta");
const v = try cobalt.Counter.incrBy(db, allocator, args[0], delta);
try stdout.print("{d}\n", .{v});
} else {
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>");
const created = try cobalt.Hash.set(db, allocator, args[0], args[1], args[2]);
try stdout.print("{s}\n", .{if (created) "created" else "updated"});
} else if (std.mem.eql(u8, op, "get")) {
if (args.len < 2) return fail(stderr, "hash get needs <name> <field>");
if (try cobalt.Hash.get(db, allocator, args[0], args[1])) |v| {
defer allocator.free(v);
try stdout.print("{s}\n", .{v});
} else {
try stdout.writeAll("(nil)\n");
}
} else if (std.mem.eql(u8, op, "del")) {
if (args.len < 2) return fail(stderr, "hash del needs <name> <field>");
const existed = try cobalt.Hash.del(db, allocator, args[0], args[1]);
try stdout.print("{d}\n", .{boolFlag(existed)});
} else if (std.mem.eql(u8, op, "len")) {
if (args.len < 1) return fail(stderr, "hash len needs <name>");
const n = try cobalt.Hash.len(db, allocator, args[0]);
try stdout.print("{d}\n", .{n});
} else if (std.mem.eql(u8, op, "getall")) {
if (args.len < 1) return fail(stderr, "hash getall needs <name>");
const fields = try cobalt.Hash.getAll(db, allocator, args[0]);
defer {
for (fields) |f| f.free(allocator);
allocator.free(fields);
}
for (fields) |f| try stdout.print("{s}\t{s}\n", .{ f.field, f.value });
} else {
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>");
const added = try cobalt.Set.add(db, allocator, args[0], args[1]);
try stdout.print("{d}\n", .{boolFlag(added)});
} else if (std.mem.eql(u8, op, "rem")) {
if (args.len < 2) return fail(stderr, "set rem needs <name> <member>");
const removed = try cobalt.Set.remove(db, allocator, args[0], args[1]);
try stdout.print("{d}\n", .{boolFlag(removed)});
} else if (std.mem.eql(u8, op, "ismember")) {
if (args.len < 2) return fail(stderr, "set ismember needs <name> <member>");
const is = try cobalt.Set.isMember(db, allocator, args[0], args[1]);
try stdout.print("{d}\n", .{boolFlag(is)});
} else if (std.mem.eql(u8, op, "card")) {
if (args.len < 1) return fail(stderr, "set card needs <name>");
const n = try cobalt.Set.card(db, allocator, args[0]);
try stdout.print("{d}\n", .{n});
} else if (std.mem.eql(u8, op, "members")) {
if (args.len < 1) return fail(stderr, "set members needs <name>");
const ms = try cobalt.Set.members(db, allocator, args[0]);
defer {
for (ms) |m| allocator.free(m);
allocator.free(ms);
}
for (ms) |m| try stdout.print("{s}\n", .{m});
} else if (std.mem.eql(u8, op, "inter")) {
if (args.len < 1) return fail(stderr, "set inter needs at least one <name>");
// The blanket lazy check above only covered args[0]; args[1..]
// are additional set names this multi-set op reads directly.
_ = try cobalt.collttl.expireIfDueMany(db, allocator, .set, args[1..], std.time.nanoTimestamp());
const ms = try cobalt.Set.intersect(db, allocator, args);
defer {
for (ms) |m| allocator.free(m);
allocator.free(ms);
}
for (ms) |m| try stdout.print("{s}\n", .{m});
} else if (std.mem.eql(u8, op, "union")) {
if (args.len < 1) return fail(stderr, "set union needs at least one <name>");
_ = try cobalt.collttl.expireIfDueMany(db, allocator, .set, args[1..], std.time.nanoTimestamp());
const ms = try cobalt.Set.unionOf(db, allocator, args);
defer {
for (ms) |m| allocator.free(m);
allocator.free(ms);
}
for (ms) |m| try stdout.print("{s}\n", .{m});
} else if (std.mem.eql(u8, op, "diff")) {
if (args.len < 2) return fail(stderr, "set diff needs <name> <other...>");
_ = try cobalt.collttl.expireIfDueMany(db, allocator, .set, args[1..], std.time.nanoTimestamp());
const ms = try cobalt.Set.difference(db, allocator, args[0], args[1..]);
defer {
for (ms) |m| allocator.free(m);
allocator.free(ms);
}
for (ms) |m| try stdout.print("{s}\n", .{m});
} else {
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>");
try cobalt.List.pushLeft(db, allocator, args[0], args[1]);
try stdout.writeAll("OK\n");
} else if (std.mem.eql(u8, op, "rpush")) {
if (args.len < 2) return fail(stderr, "list rpush needs <name> <value>");
try cobalt.List.pushRight(db, allocator, args[0], args[1]);
try stdout.writeAll("OK\n");
} else if (std.mem.eql(u8, op, "lpop")) {
if (args.len < 1) return fail(stderr, "list lpop needs <name>");
if (try cobalt.List.popLeft(db, allocator, args[0])) |v| {
defer allocator.free(v);
try stdout.print("{s}\n", .{v});
} else {
try stdout.writeAll("(nil)\n");
}
} else if (std.mem.eql(u8, op, "rpop")) {
if (args.len < 1) return fail(stderr, "list rpop needs <name>");
if (try cobalt.List.popRight(db, allocator, args[0])) |v| {
defer allocator.free(v);
try stdout.print("{s}\n", .{v});
} else {
try stdout.writeAll("(nil)\n");
}
} else if (std.mem.eql(u8, op, "len")) {
if (args.len < 1) return fail(stderr, "list len needs <name>");
const n = try cobalt.List.len(db, allocator, args[0]);
try stdout.print("{d}\n", .{n});
}
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 {
std.fs.cwd().deleteTree(dir) catch {}; std.fs.cwd().deleteTree(dir) catch {};