feat: extend main with runCollTtl and parseValue
This commit is contained in:
1 parent
bdeec4bfdf
commit
933ba33298
1 file changed
+173
+173
@@ -1003,6 +1003,179 @@ fn runColl(allocator: std.mem.Allocator, stdout: anytype, stderr: anytype, db: *
|
||||
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});
|
||||
} else if (std.mem.eql(u8, op, "range")) {
|
||||
if (args.len < 3) return fail(stderr, "list range needs <name> <start> <stop>");
|
||||
const start = std.fmt.parseInt(i64, args[1], 10) catch return fail(stderr, "invalid start index");
|
||||
const stop = std.fmt.parseInt(i64, args[2], 10) catch return fail(stderr, "invalid stop index");
|
||||
const vals = try cobalt.List.range(db, allocator, args[0], start, stop);
|
||||
defer {
|
||||
for (vals) |v| allocator.free(v);
|
||||
allocator.free(vals);
|
||||
}
|
||||
for (vals) |v| try stdout.print("{s}\n", .{v});
|
||||
} else {
|
||||
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>");
|
||||
const score = std.fmt.parseFloat(f64, args[2]) catch return fail(stderr, "invalid score");
|
||||
try cobalt.ZSet.add(db, allocator, args[0], args[1], score);
|
||||
try stdout.writeAll("OK\n");
|
||||
} else if (std.mem.eql(u8, op, "score")) {
|
||||
if (args.len < 2) return fail(stderr, "zset score needs <name> <member>");
|
||||
if (try cobalt.ZSet.score(db, allocator, args[0], args[1])) |s| {
|
||||
try stdout.print("{d}\n", .{s});
|
||||
} else {
|
||||
try stdout.writeAll("(nil)\n");
|
||||
}
|
||||
} else if (std.mem.eql(u8, op, "incrby")) {
|
||||
if (args.len < 3) return fail(stderr, "zset incrby needs <name> <member> <delta>");
|
||||
const delta = std.fmt.parseFloat(f64, args[2]) catch return fail(stderr, "invalid delta");
|
||||
const s = try cobalt.ZSet.incrBy(db, allocator, args[0], args[1], delta);
|
||||
try stdout.print("{d}\n", .{s});
|
||||
} else if (std.mem.eql(u8, op, "rem")) {
|
||||
if (args.len < 2) return fail(stderr, "zset rem needs <name> <member>");
|
||||
const removed = try cobalt.ZSet.remove(db, allocator, args[0], args[1]);
|
||||
try stdout.print("{d}\n", .{boolFlag(removed)});
|
||||
} else if (std.mem.eql(u8, op, "card")) {
|
||||
if (args.len < 1) return fail(stderr, "zset card needs <name>");
|
||||
const n = try cobalt.ZSet.card(db, allocator, args[0]);
|
||||
try stdout.print("{d}\n", .{n});
|
||||
} else if (std.mem.eql(u8, op, "rangebyrank")) {
|
||||
if (args.len < 3) return fail(stderr, "zset rangebyrank needs <name> <start> <stop>");
|
||||
const start = std.fmt.parseInt(i64, args[1], 10) catch return fail(stderr, "invalid start rank");
|
||||
const stop = std.fmt.parseInt(i64, args[2], 10) catch return fail(stderr, "invalid stop rank");
|
||||
const zms = try cobalt.ZSet.rangeByRank(db, allocator, args[0], start, stop);
|
||||
defer {
|
||||
for (zms) |zm| zm.free(allocator);
|
||||
allocator.free(zms);
|
||||
}
|
||||
for (zms) |zm| try stdout.print("{s}\t{d}\n", .{ zm.member, zm.score });
|
||||
} else if (std.mem.eql(u8, op, "rangebyscore")) {
|
||||
if (args.len < 3) return fail(stderr, "zset rangebyscore needs <name> <min> <max>");
|
||||
const min = std.fmt.parseFloat(f64, args[1]) catch return fail(stderr, "invalid min score");
|
||||
const max = std.fmt.parseFloat(f64, args[2]) catch return fail(stderr, "invalid max score");
|
||||
const zms = try cobalt.ZSet.rangeByScore(db, allocator, args[0], min, max);
|
||||
defer {
|
||||
for (zms) |zm| zm.free(allocator);
|
||||
allocator.free(zms);
|
||||
}
|
||||
for (zms) |zm| try stdout.print("{s}\t{d}\n", .{ zm.member, zm.score });
|
||||
} else if (std.mem.eql(u8, op, "rank")) {
|
||||
if (args.len < 2) return fail(stderr, "zset rank needs <name> <member>");
|
||||
if (try cobalt.ZSet.rank(db, allocator, args[0], args[1])) |r| {
|
||||
try stdout.print("{d}\n", .{r});
|
||||
} else {
|
||||
try stdout.writeAll("(nil)\n");
|
||||
}
|
||||
} else {
|
||||
return fail(stderr, "unknown zset operation");
|
||||
}
|
||||
} else {
|
||||
return fail(stderr, "unknown collection kind (expected counter/hash/set/list/zset)");
|
||||
}
|
||||
}
|
||||
|
||||
/// Dispatches `coll <kind> expire|ttl|persist <name> [seconds]` onto
|
||||
/// collttl.zig - the whole-collection counterpart of the flat-key
|
||||
/// `expire`/`ttl`/`persist` commands above. `expire` mirrors Redis-strict
|
||||
/// EXPIRE by checking the collection currently exists first (same shape as
|
||||
/// the flat-key `expire` handler checking db.get first).
|
||||
fn runCollTtl(allocator: std.mem.Allocator, stdout: anytype, stderr: anytype, db: *cobalt.Db, k: cobalt.CollectionKind, op: []const u8, args: []const []const u8) !void {
|
||||
if (std.mem.eql(u8, op, "expire")) {
|
||||
if (args.len < 2) return fail(stderr, "coll <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");
|
||||
if (!try cobalt.collections.collectionExists(db, allocator, k, args[0])) return fail(stderr, "no such collection");
|
||||
try cobalt.collttl.setExpireSeconds(db, allocator, k, args[0], std.time.nanoTimestamp(), seconds);
|
||||
try stdout.writeAll("OK\n");
|
||||
} else if (std.mem.eql(u8, op, "persist")) {
|
||||
if (args.len < 1) return fail(stderr, "coll <kind> persist needs <name>");
|
||||
try cobalt.collttl.clearExpire(db, allocator, k, args[0]);
|
||||
try stdout.writeAll("OK\n");
|
||||
} else if (std.mem.eql(u8, op, "ttl")) {
|
||||
if (args.len < 1) return fail(stderr, "coll <kind> ttl needs <name>");
|
||||
switch (try cobalt.collttl.remaining(db, allocator, k, args[0], std.time.nanoTimestamp())) {
|
||||
.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 coll ttl operation");
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a CLI-supplied scalar into a typed row Value. `i:`/`f:`/`b:`/`s:`
|
||||
/// prefixes pick int/float/bool/text explicitly; unprefixed text defaults to
|
||||
/// `.text`. The returned Value borrows `text` directly (no allocation), so it
|
||||
/// is only valid for as long as the argv slice (or script line) it came from.
|
||||
fn parseValue(text: []const u8) !Value {
|
||||
return cobalt.row.Value.parseCli(text);
|
||||
}
|
||||
|
||||
/// Splits `name=value` into a Field, validating the name with the same
|
||||
/// identifier rule tables and indexes already enforce.
|
||||
fn parseField(text: []const u8) !Field {
|
||||
const eq = std.mem.indexOfScalar(u8, text, '=') orelse return error.InvalidField;
|
||||
const name = text[0..eq];
|
||||
if (!cobalt.index.validIdentifier(name)) return error.InvalidField;
|
||||
const value = try parseValue(text[eq + 1 ..]);
|
||||
return .{ .name = name, .value = value };
|
||||
}
|
||||
|
||||
const PageFlags = struct { limit: u32 = 0, offset: u32 = 0 };
|
||||
|
||||
/// Pulls `--limit N` / `--offset N` out of a `table select`/`remote-select`
|
||||
/// argument list wherever they appear, returning the remaining tokens
|
||||
/// (destined to be joined into the expr text) alongside the parsed page
|
||||
/// window. Caller owns and frees `rest`.
|
||||
fn extractPageFlags(allocator: std.mem.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/
|
||||
/// `remote-window`'s trailing args, requesting `window.FrameMode.range`
|
||||
/// (SQL's actual `RANGE` frame default for running aggregates) instead of
|
||||
/// this CLI's `.rows` default - see `window.zig`'s `compute` doc comment
|
||||
/// for the semantic difference. Kept separate from `extractPageFlags`
|
||||
/// (same reason `extractSelectFlags`'s `--order-by` is kept separate:
|
||||
/// `extractPageFlags` is also shared by `table join`/`table aggregate`,
|
||||
/// neither of which has a frame concept to silently swallow the flag into).
|
||||
/// Caller owns and frees `rest`.
|
||||
fn extractRangeFlag(allocator: std.mem.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 cobalt.query.OrderBy = &.{} };
|
||||
|
||||
/// `table select`/`remote-select`-only counterpart of `extractPageFlags`:
|
||||
}
|
||||
|
||||
fn runVerify(allocator: std.mem.Allocator, stdout: anytype, dir: []const u8, count: usize) !void {
|
||||
|
||||
Reference in new issue
Block a user