feat: add cmdCollZSet and cmdHelp to repl

This commit is contained in:
dualfroz committed 2026-09-09 19:19:05 +02:00
1 parent 573d2c4860
commit 4b78a16183
1 file changed
+166
+166
View File
@@ -937,3 +937,169 @@ pub const Repl = struct {
}
}
fn cmdCollZSet(self: *Repl, op: []const u8, args: []const []const u8, writer: anytype) !void {
const ZSet = collectionsmod.ZSet;
if (std.mem.eql(u8, op, "add")) {
if (args.len < 3) return ReplError.WrongArgCount;
const scoreVal = std.fmt.parseFloat(f64, args[2]) catch return ReplError.InvalidArgument;
try ZSet.add(self.db, self.allocator, args[0], args[1], scoreVal);
try writer.writeAll("OK\n");
} else if (std.mem.eql(u8, op, "score")) {
if (args.len < 2) return ReplError.WrongArgCount;
if (try ZSet.score(self.db, self.allocator, args[0], args[1])) |s| {
try writer.print("{d}\n", .{s});
} else {
try writer.writeAll("(nil)\n");
}
} else if (std.mem.eql(u8, op, "incrby")) {
if (args.len < 3) return ReplError.WrongArgCount;
const delta = std.fmt.parseFloat(f64, args[2]) catch return ReplError.InvalidArgument;
const s = try ZSet.incrBy(self.db, self.allocator, args[0], args[1], delta);
try writer.print("{d}\n", .{s});
} else if (std.mem.eql(u8, op, "rem")) {
if (args.len < 2) return ReplError.WrongArgCount;
const removed = try ZSet.remove(self.db, self.allocator, args[0], args[1]);
try writer.print("{d}\n", .{@intFromBool(removed)});
} else if (std.mem.eql(u8, op, "card")) {
if (args.len < 1) return ReplError.WrongArgCount;
const n = try ZSet.card(self.db, self.allocator, args[0]);
try writer.print("{d}\n", .{n});
} else if (std.mem.eql(u8, op, "rangebyrank") or std.mem.eql(u8, op, "rangebyscore")) {
if (args.len < 3) return ReplError.WrongArgCount;
const zms = if (std.mem.eql(u8, op, "rangebyrank")) blk: {
const start = std.fmt.parseInt(i64, args[1], 10) catch return ReplError.InvalidArgument;
const stop = std.fmt.parseInt(i64, args[2], 10) catch return ReplError.InvalidArgument;
break :blk try ZSet.rangeByRank(self.db, self.allocator, args[0], start, stop);
} else blk: {
const min = std.fmt.parseFloat(f64, args[1]) catch return ReplError.InvalidArgument;
const max = std.fmt.parseFloat(f64, args[2]) catch return ReplError.InvalidArgument;
break :blk try ZSet.rangeByScore(self.db, self.allocator, args[0], min, max);
};
defer {
for (zms) |zm| zm.free(self.allocator);
self.allocator.free(zms);
}
for (zms) |zm| try writer.print("{s}\t{d}\n", .{ zm.member, zm.score });
} else if (std.mem.eql(u8, op, "rank")) {
if (args.len < 2) return ReplError.WrongArgCount;
if (try ZSet.rank(self.db, self.allocator, args[0], args[1])) |r| {
try writer.print("{d}\n", .{r});
} else {
try writer.writeAll("(nil)\n");
}
} else {
return ReplError.UnknownCollectionOp;
}
}
fn cmdHelp(self: *Repl, writer: anytype) !void {
_ = self;
try writer.writeAll(
\\commands:
\\ put <key> <value> table put <table> <pk> [field=value ...]
\\ get <key> table get <table> <pk>
\\ del <key> table del <table> <pk>
\\ scan [prefix] begin / commit / rollback
\\ stats / flush / compact exit / quit
\\ table select <table> [expr...] [--limit N] [--offset N]
\\ [--order-by f[:desc][,f[:desc]...]]
\\ table join [--left|--right|--full] <lt> <lf> <rt> <rf> [--limit N] [--offset N]
\\ table aggregate <table> <groupField|*> <spec>[,<spec>...] [expr...] [--limit N] [--offset N]
\\ table window <table> <kind[:field]>[,<kind[:field]>...] [expr...] [partition f[,f...]]
\\ [order f[:desc][,f...]] [--limit N] [--offset N] [--range]
\\ coll <kind> <op> [args...] kind: counter/hash/set/list/zset
\\ expire/persist/ttl <key> coll <kind> expire/persist/ttl <name>
\\ expire-sweep coll expire-sweep
\\
);
}
};
const PageFlags = struct { limit: u32 = 0, offset: u32 = 0 };
/// Pulls `--limit N` / `--offset N` out of a `table select` argument list
/// wherever they appear, returning the remaining tokens (destined to be
/// joined into the expr text) alongside the parsed page window. Same shape
/// as main.zig's identically-named helper for `table select`/`remote-select`
/// on the one-shot CLI - kept as its own small copy here rather than shared,
/// since main.zig is the binary entry point and not a module repl.zig can
/// import. Caller owns and frees `rest`.
fn extractPageFlags(allocator: Allocator, args: []const []const u8) !struct { page: PageFlags, rest: [][]const u8 } {
var page = PageFlags{};
var rest = std.ArrayList([]const u8).init(allocator);
errdefer rest.deinit();
var i: usize = 0;
while (i < args.len) : (i += 1) {
const isLimit = std.mem.eql(u8, args[i], "--limit");
const isOffset = std.mem.eql(u8, args[i], "--offset");
if (isLimit or isOffset) {
i += 1;
if (i >= args.len) return error.InvalidPageFlag;
const n = std.fmt.parseInt(u32, args[i], 10) catch return error.InvalidPageFlag;
if (isLimit) page.limit = n else page.offset = n;
} else {
try rest.append(args[i]);
}
}
return .{ .page = page, .rest = try rest.toOwnedSlice() };
}
/// Pulls a lone `--range` flag (no value) out of `table window`'s trailing
/// args, requesting `window.FrameMode.range` instead of the default
/// `.rows` running frame - see `window.zig`'s `compute` doc comment. Same
/// shape as main.zig's identically-named helper, kept as its own small copy
/// here for the same reason `extractPageFlags` above is. Caller owns and
/// frees `rest`.
fn extractRangeFlag(allocator: Allocator, args: []const []const u8) !struct { range: bool, rest: [][]const u8 } {
var range = false;
var rest = std.ArrayList([]const u8).init(allocator);
errdefer rest.deinit();
for (args) |a| {
if (std.mem.eql(u8, a, "--range")) {
range = true;
} else {
try rest.append(a);
}
}
return .{ .range = range, .rest = try rest.toOwnedSlice() };
}
const SelectFlags = struct { page: PageFlags = .{}, orderBy: []const querymod.OrderBy = &.{} };
/// `table select`-only counterpart of `extractPageFlags` above, mirroring
/// main.zig's identically-shaped `extractSelectFlags`/`SelectFlags`: same
/// `--limit`/`--offset` handling plus `--order-by
/// field[:desc][,field[:desc]...]` (comma-separated, applied left to
/// right), kept SEPARATE from `extractPageFlags` rather than folded into
/// it because `extractPageFlags` is also used by `table join`/`table
/// aggregate` here, neither of which sorts its result - recognizing
/// `--order-by` there too would silently consume the flag without
/// honoring it. Caller owns and frees `rest` AND `flags.orderBy`.
fn extractSelectFlags(allocator: Allocator, args: []const []const u8) !struct { flags: SelectFlags, rest: [][]const u8 } {
var flags = SelectFlags{};
var rest = std.ArrayList([]const u8).init(allocator);
errdefer rest.deinit();
var i: usize = 0;
while (i < args.len) : (i += 1) {
const isLimit = std.mem.eql(u8, args[i], "--limit");
const isOffset = std.mem.eql(u8, args[i], "--offset");
const isOrderBy = std.mem.eql(u8, args[i], "--order-by");
if (isLimit or isOffset) {
i += 1;
if (i >= args.len) return error.InvalidPageFlag;
const n = std.fmt.parseInt(u32, args[i], 10) catch return error.InvalidPageFlag;
if (isLimit) flags.page.limit = n else flags.page.offset = n;
} else if (isOrderBy) {
i += 1;
if (i >= args.len) return error.InvalidPageFlag;
allocator.free(flags.orderBy);
flags.orderBy = try parseOrderByList(allocator, args[i]);
} else {
try rest.append(args[i]);
}
}
return .{ .flags = flags, .rest = try rest.toOwnedSlice() };
}