feat: extend main with runRemoteExplain and runRemoteUpdate
This commit is contained in:
1 parent
f9b2e7df9c
commit
7424bb6ed8
1 file changed
+173
+173
@@ -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 {
|
||||||
|
|||||||
Reference in new issue
Block a user