diff --git a/src/main.zig b/src/main.zig
index 7ab545c..6e5dfa2 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -585,16 +585,150 @@ pub fn main() !void {
return;
}
+ if (std.mem.eql(u8, command, "remote-select")) {
+ // Reuses the
slot as a host:port to connect to, exactly like
+ // `verify` reuses it as the directory under test - `remote-select`
+ // never opens a local Db at all.
+ try runRemoteSelect(allocator, stdout, stderr, dir, rest);
+ return;
+ }
+
+ if (std.mem.eql(u8, command, "remote-join")) {
+ // Same " slot is really host:port" convention as
+ // `remote-select` - never opens a local Db.
+ try runRemoteJoin(allocator, stdout, stderr, dir, rest);
+ return;
+ }
+
+ if (std.mem.eql(u8, command, "remote-aggregate")) {
+ // Same " slot is really host:port" convention as
+ // `remote-select` - never opens a local Db.
+ try runRemoteAggregate(allocator, stdout, stderr, dir, rest);
+ return;
+ }
+
+ if (std.mem.eql(u8, command, "remote-window")) {
+ // Same " slot is really host:port" convention as
+ // `remote-select` - never opens a local Db.
+ try runRemoteWindow(allocator, stdout, stderr, dir, rest);
+ return;
+ }
+
+ if (std.mem.eql(u8, command, "remote-publish")) {
+ // Same " slot is really host:port" convention as
+ // `remote-select` - never opens a local Db.
+ try runRemotePublish(allocator, stdout, stderr, dir, rest);
+ return;
+ }
+
+ if (std.mem.eql(u8, command, "remote-subscribe")) {
+ // Same " slot is really host:port" convention as
+ // `remote-select` - never opens a local Db.
+ try runRemoteSubscribe(allocator, stdout, stderr, dir, rest);
+ return;
+ }
+
+ if (std.mem.eql(u8, command, "remote-unsubscribe")) {
+ // Same " slot is really host:port" convention as
+ // `remote-select` - never opens a local Db.
+ try runRemoteUnsubscribe(allocator, stdout, stderr, dir, rest);
+ return;
+ }
+
+ if (std.mem.eql(u8, command, "remote-explain")) {
+ // Same " slot is really host:port" convention as
+ // `remote-select` - never opens a local Db.
+ try runRemoteExplain(allocator, stdout, stderr, dir, rest);
+ return;
+ }
+
+ if (std.mem.eql(u8, command, "remote-update")) {
+ // Same " slot is really host:port" convention as
+ // `remote-select` - never opens a local Db.
+ try runRemoteUpdate(allocator, stdout, stderr, dir, rest);
+ return;
+ }
+
+ if (std.mem.eql(u8, command, "remote-delete")) {
+ // Same " slot is really host:port" convention as
+ // `remote-select` - never opens a local Db.
+ try runRemoteDelete(allocator, stdout, stderr, dir, rest);
+ return;
+ }
+
+ if (std.mem.eql(u8, command, "remote-export")) {
+ // Same " slot is really host:port" convention as
+ // `remote-select` - never opens a local Db.
+ try runRemoteExport(allocator, stdout, stderr, dir, rest);
+ return;
+ }
+
+ if (std.mem.eql(u8, command, "remote-import")) {
+ // Same " slot is really host:port" convention as
+ // `remote-select` - never opens a local Db.
+ try runRemoteImport(allocator, stdout, stderr, dir, rest);
+ return;
+ }
+
+ if (std.mem.eql(u8, command, "remote-user")) {
+ // Same " slot is really host:port" convention as
+ // `remote-select` - never opens a local Db.
+ try runRemoteUser(allocator, stdout, stderr, dir, rest);
+ return;
+ }
+
+ if (std.mem.eql(u8, command, "remote-stats")) {
+ // Same " slot is really host:port" convention as
+ // `remote-select`/`remote-user` - never opens a local Db.
+ try runRemoteStats(allocator, stdout, stderr, dir, rest);
+ return;
+ }
+
+ if (std.mem.eql(u8, command, "remote-ttl")) {
+ // Same " slot is really host:port" convention as
+ // `remote-select`/`remote-user`/`remote-stats` - never opens a
+ // local Db.
+ try runRemoteTtl(allocator, stdout, stderr, dir, rest);
+ return;
+ }
+
+ if (std.mem.eql(u8, command, "remote-coll-ttl")) {
+ // Same " slot is really host:port" convention as
+ // `remote-ttl` - never opens a local Db.
+ try runRemoteCollTtl(allocator, stdout, stderr, dir, rest);
+ return;
+ }
+
+ if (std.mem.eql(u8, command, "remote-coll")) {
+ // Same " slot is really host:port" convention as
+ // `remote-coll-ttl` - never opens a local Db.
+ try runRemoteColl(allocator, stdout, stderr, dir, rest);
+ return;
+ }
+
+ if (std.mem.eql(u8, command, "remote-txn")) {
+ // Same " slot is really host:port" convention as every other
+ // remote-* command - never opens a local Db.
+ try runRemoteTxn(allocator, stdout, stderr, dir, rest);
+ return;
+ }
+
var db = try cobalt.Db.open(allocator, dir, .{});
defer db.close();
if (std.mem.eql(u8, command, "put")) {
if (rest.len < 2) return fail(stderr, "put needs ");
try db.put(rest[0], rest[1]);
+ if (rest.len >= 4 and std.mem.eql(u8, rest[2], "--ttl")) {
+ const seconds = std.fmt.parseInt(u64, rest[3], 10) catch return fail(stderr, "--ttl needs a whole number of seconds");
+ try cobalt.ttl.setExpireSeconds(&db, allocator, rest[0], std.time.nanoTimestamp(), seconds);
+ } else if (rest.len > 2) {
+ return fail(stderr, "put takes [--ttl ]");
+ }
try db.maybeCompact();
} else if (std.mem.eql(u8, command, "get")) {
if (rest.len < 1) return fail(stderr, "get needs ");
- if (try db.get(allocator, rest[0])) |value| {
+ if (try cobalt.ttl.getLive(&db, allocator, rest[0], std.time.nanoTimestamp())) |value| {
defer allocator.free(value);
try stdout.print("{s}\n", .{value});
} else {
@@ -603,7 +737,27 @@ pub fn main() !void {
} else if (std.mem.eql(u8, command, "del")) {
if (rest.len < 1) return fail(stderr, "del needs ");
try db.delete(rest[0]);
+ try cobalt.ttl.clearExpire(&db, allocator, rest[0]);
try db.maybeCompact();
+ } else if (std.mem.eql(u8, command, "expire")) {
+ if (rest.len < 2) return fail(stderr, "expire needs ");
+ const seconds = std.fmt.parseInt(u64, rest[1], 10) catch return fail(stderr, "expire needs a whole number of seconds");
+ const existing = try db.get(allocator, rest[0]) orelse return fail(stderr, "no such key");
+ allocator.free(existing);
+ try cobalt.ttl.setExpireSeconds(&db, allocator, rest[0], std.time.nanoTimestamp(), seconds);
+ } else if (std.mem.eql(u8, command, "persist")) {
+ if (rest.len < 1) return fail(stderr, "persist needs ");
+ try cobalt.ttl.clearExpire(&db, allocator, rest[0]);
+ } else if (std.mem.eql(u8, command, "ttl")) {
+ if (rest.len < 1) return fail(stderr, "ttl needs ");
+ switch (try cobalt.ttl.remaining(&db, allocator, rest[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 if (std.mem.eql(u8, command, "expire-sweep")) {
+ const reaped = try cobalt.ttl.sweepExpired(&db, allocator, std.time.nanoTimestamp());
+ try stdout.print("reaped: {d}\n", .{reaped});
} else if (std.mem.eql(u8, command, "scan")) {
const start: ?[]const u8 = if (rest.len >= 1) rest[0] else null;
const end: ?[]const u8 = if (rest.len >= 2) rest[1] else null;
@@ -625,7 +779,47 @@ pub fn main() !void {
try db.compact();
try stdout.print("sstables after compaction: {d}\n", .{db.sstables.items.len});
} else if (std.mem.eql(u8, command, "stats")) {
- try db.stats().print(stdout);
+ if (rest.len >= 1 and std.mem.eql(u8, rest[0], "--prom")) {
+ try cobalt.metrics.formatDbStats(stdout, "cobalt", db.stats());
+ } else {
+ try db.stats().print(stdout);
+ }
+ } else if (std.mem.eql(u8, command, "serve")) {
+ const bind = if (rest.len >= 1) rest[0] else "127.0.0.1:6380";
+ const serveRest = if (rest.len >= 1) rest[1..] else rest;
+ try runServe(allocator, stdout, &db, bind, serveRest);
+ } else if (std.mem.eql(u8, command, "backup")) {
+ if (rest.len < 1) return fail(stderr, "backup needs ");
+ const snap = try db.snapshot();
+ const count = try cobalt.backup.dumpToFile(allocator, snap, rest[0]);
+ try stdout.print("backup written: {d} entries, seq={d}, file={s}\n", .{ count, snap.seq, rest[0] });
+ } else if (std.mem.eql(u8, command, "restore")) {
+ if (rest.len < 1) return fail(stderr, "restore needs ");
+ const info = cobalt.backup.restoreFromFile(allocator, &db, rest[0], 500) catch |err| {
+ try stderr.print("restore failed: {s}\n", .{@errorName(err)});
+ std.process.exit(1);
+ };
+ try stdout.print("restore applied: {d} entries from seq={d}\n", .{ info.entryCount, info.seq });
+ } else if (std.mem.eql(u8, command, "coll")) {
+ try runColl(allocator, stdout, stderr, &db, rest);
+ } else if (std.mem.eql(u8, command, "table")) {
+ try runTable(allocator, stdout, stderr, &db, rest);
+ } else if (std.mem.eql(u8, command, "user")) {
+ try runUser(allocator, stdout, stderr, &db, rest);
+ } else if (std.mem.eql(u8, command, "txn")) {
+ try runTxn(allocator, stdout, stderr, &db, rest);
+ } else if (std.mem.eql(u8, command, "replicate")) {
+ if (rest.len < 1) return fail(stderr, "replicate needs ");
+ try runReplicate(allocator, stdout, &db, rest[0], rest[1..]);
+ } else if (std.mem.eql(u8, command, "repl")) {
+ try runRepl(allocator, stdout, stderr, &db, rest);
+ } else if (std.mem.eql(u8, command, "backup-info")) {
+ if (rest.len < 1) return fail(stderr, "backup-info needs ");
+ const info = cobalt.backup.inspectFile(allocator, rest[0]) catch |err| {
+ try stderr.print("invalid backup: {s}\n", .{@errorName(err)});
+ std.process.exit(1);
+ };
+ try stdout.print("entries={d} seq={d}\n", .{ info.entryCount, info.seq });
} else {
try stderr.print("unknown command: {s}\n", .{command});
try stderr.writeAll(usage);