feat: extend main with runRemoteExplain and runRemoteUpdate

This commit is contained in:
dualfroz committed 2026-09-07 15:30:22 +02:00
1 parent f9b2e7df9c
commit 7424bb6ed8
1 file changed
+173
+173
View File
@@ -2387,6 +2387,179 @@ fn runRemoteSelect(allocator: std.mem.Allocator, stdout: anytype, stderr: anytyp
try authenticateIfRequested(&client, stderr, authFlags.auth); try authenticateIfRequested(&client, stderr, authFlags.auth);
const sel = extractSelectFlags(allocator, authFlags.rest) catch return fail(stderr, "invalid --limit/--offset/--order-by value"); const sel = extractSelectFlags(allocator, authFlags.rest) catch return fail(stderr, "invalid --limit/--offset/--order-by value");
defer allocator.free(sel.rest);
defer allocator.free(sel.flags.orderBy);
const exprText = try std.mem.join(allocator, " ", sel.rest);
defer allocator.free(exprText);
const rows = client.tableSelect(allocator, rest[0], exprText, sel.flags.page.offset, sel.flags.page.limit, sel.flags.orderBy) catch |err| {
try stderr.print("error: {s}\n", .{@errorName(err)});
std.process.exit(1);
};
defer cobalt.protocol.freeRows(allocator, rows);
for (rows) |r| try printRow(stdout, r);
}
/// Connects to a remote `cobalt serve` instance and runs `table explain`
/// there instead of against a local Db - the client-side counterpart of
/// `server.zig`'s `table_explain` opcode, same shape as `runRemoteSelect`
/// but reporting a `query.Plan` instead of rows. `--user`/`--pass`/
/// `--token` authenticate first, needed only against a server started with
/// `serve --auth`.
fn runRemoteExplain(allocator: std.mem.Allocator, stdout: anytype, stderr: anytype, addressSpec: []const u8, rest: []const []const u8) !void {
if (rest.len < 1) return fail(stderr, "remote-explain needs <table> [expr...]");
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[1..]) catch return fail(stderr, "invalid --user/--pass/--token value");
defer allocator.free(authFlags.rest);
try authenticateIfRequested(&client, stderr, authFlags.auth);
const exprText = try std.mem.join(allocator, " ", authFlags.rest);
defer allocator.free(exprText);
var plan = client.tableExplain(allocator, rest[0], exprText) catch |err| {
try stderr.print("error: {s}\n", .{@errorName(err)});
std.process.exit(1);
};
defer plan.deinit(allocator);
try printPlan(stdout, plan);
}
/// Connects to a remote `cobalt serve` instance and runs `table update`
/// there instead of against a local Db - the client-side counterpart of
/// `server.zig`'s `table_update` opcode, same shape as `runRemoteExplain`
/// but taking a `--set` field list and printing the number of rows updated
/// instead of a plan. `--user`/`--pass`/`--token` authenticate first,
/// needed only against a server started with `serve --auth`.
fn runRemoteUpdate(allocator: std.mem.Allocator, stdout: anytype, stderr: anytype, addressSpec: []const u8, rest: []const []const u8) !void {
if (rest.len < 1) return fail(stderr, "remote-update needs <table> --set field=value[,field=value...] [expr...]");
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[1..]) catch return fail(stderr, "invalid --user/--pass/--token value");
defer allocator.free(authFlags.rest);
try authenticateIfRequested(&client, stderr, authFlags.auth);
const setf = extractSetFlag(allocator, authFlags.rest) catch |err| return fail(stderr, if (err == error.MissingSetFlag)
"remote-update requires --set field=value[,field=value...]"
else
"invalid --set value");
defer allocator.free(setf.sets);
defer allocator.free(setf.rest);
const exprText = try std.mem.join(allocator, " ", setf.rest);
defer allocator.free(exprText);
const updated = client.tableUpdate(rest[0], setf.sets, exprText) catch |err| {
try stderr.print("error: {s}\n", .{@errorName(err)});
std.process.exit(1);
};
try stdout.print("{d}\n", .{updated});
}
/// Connects to a remote `cobalt serve` instance and runs `table delete`
/// there instead of against a local Db - the client-side counterpart of
/// `server.zig`'s `table_delete` opcode, same shape as `runRemoteExplain`
/// (no `--set` flag, since a delete has no `SET` clause) but printing the
/// number of rows deleted instead of a plan. `--user`/`--pass`/`--token`
/// authenticate first, needed only against a server started with
/// `serve --auth`.
fn runRemoteDelete(allocator: std.mem.Allocator, stdout: anytype, stderr: anytype, addressSpec: []const u8, rest: []const []const u8) !void {
if (rest.len < 1) return fail(stderr, "remote-delete needs <table> [expr...]");
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[1..]) catch return fail(stderr, "invalid --user/--pass/--token value");
defer allocator.free(authFlags.rest);
try authenticateIfRequested(&client, stderr, authFlags.auth);
const exprText = try std.mem.join(allocator, " ", authFlags.rest);
defer allocator.free(exprText);
const deleted = client.tableDelete(rest[0], exprText) catch |err| {
try stderr.print("error: {s}\n", .{@errorName(err)});
std.process.exit(1);
};
try stdout.print("{d}\n", .{deleted});
}
fn parseDumpFormat(stderr: anytype, text: []const u8) !cobalt.protocol.DumpFormat {
if (std.mem.eql(u8, text, "csv")) return .csv;
if (std.mem.eql(u8, text, "json")) return .json;
try fail(stderr, "format must be csv or json");
unreachable;
}
/// Connects to a remote `cobalt serve` instance and runs `table export`
/// there instead of against a local Db - the client-side counterpart of
/// `server.zig`'s `table_export` opcode. Unlike every other remote-* op,
/// the destination `<path>` names a file on THIS machine, not the server:
/// the server renders the whole table into memory and sends the bytes
/// back in one frame, and this function is the one that actually creates
/// `<path>` and writes them - the local `table export` writes straight to
/// disk itself since it already has a `*Db` handle in-process, but a
/// remote client only ever gets bytes over the wire. `--user`/`--pass`/
/// `--token` authenticate first, needed only against a server started
/// with `serve --auth`.
fn runRemoteExport(allocator: std.mem.Allocator, stdout: anytype, stderr: anytype, addressSpec: []const u8, rest: []const []const u8) !void {
if (rest.len < 1) return fail(stderr, "remote-export needs <table> csv|json <path>");
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[1..]) catch return fail(stderr, "invalid --user/--pass/--token value");
defer allocator.free(authFlags.rest);
try authenticateIfRequested(&client, stderr, authFlags.auth);
const args = authFlags.rest;
if (args.len < 2) return fail(stderr, "remote-export needs <table> csv|json <path>");
const format = try parseDumpFormat(stderr, args[0]);
var result = client.tableExport(allocator, rest[0], format) catch |err| {
try stderr.print("error: {s}\n", .{@errorName(err)});
std.process.exit(1);
};
defer result.deinit(allocator);
const file = std.fs.cwd().createFile(args[1], .{ .truncate = true }) catch |err| {
try stderr.print("failed to write '{s}': {s}\n", .{ args[1], @errorName(err) });
std.process.exit(1);
};
defer file.close();
try file.writeAll(result.content);
try stdout.print("{d}\n", .{result.rowCount});
}
/// Connects to a remote `cobalt serve` instance and runs `table import`
/// there instead of against a local Db - the client-side counterpart of
/// `server.zig`'s `table_import` opcode. Mirrors `runRemoteExport`: the
/// source `<path>` is read from THIS machine (the same way `runTxn` reads
} }
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 {