feat: extend main with runRemoteImport and JoinCliMode
This commit is contained in:
1 parent
7424bb6ed8
commit
51bad5d86e
1 file changed
+173
+173
@@ -2560,6 +2560,179 @@ fn runRemoteExport(allocator: std.mem.Allocator, stdout: anytype, stderr: anytyp
|
||||
/// 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
|
||||
/// a local script file), and its bytes are sent to the server in one
|
||||
/// frame. `--user`/`--pass`/`--token` authenticate first, needed only
|
||||
/// against a server started with `serve --auth`.
|
||||
fn runRemoteImport(allocator: std.mem.Allocator, stdout: anytype, stderr: anytype, addressSpec: []const u8, rest: []const []const u8) !void {
|
||||
if (rest.len < 1) return fail(stderr, "remote-import 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-import needs <table> csv|json <path>");
|
||||
const format = try parseDumpFormat(stderr, args[0]);
|
||||
|
||||
const content = std.fs.cwd().readFileAlloc(allocator, args[1], 1 << 30) catch |err| {
|
||||
try stderr.print("failed to read '{s}': {s}\n", .{ args[1], @errorName(err) });
|
||||
std.process.exit(1);
|
||||
};
|
||||
defer allocator.free(content);
|
||||
|
||||
const imported = client.tableImport(rest[0], format, content) catch |err| {
|
||||
try stderr.print("error: {s}\n", .{@errorName(err)});
|
||||
std.process.exit(1);
|
||||
};
|
||||
try stdout.print("{d}\n", .{imported});
|
||||
}
|
||||
|
||||
const JoinCliMode = enum { inner, left, right, full };
|
||||
|
||||
/// Pulls `--left`/`--right`/`--full` (mutually exclusive, last one wins,
|
||||
/// same as local `table join`'s parsing loop) and `--left-expr E`/
|
||||
/// `--right-expr E` out of a `remote-join` argument list, same "strip
|
||||
/// recognized flags, keep the rest" shape as `extractPageFlags`/
|
||||
/// `extractAuthFlags`. An expr not given defaults to the empty string,
|
||||
/// meaning "select every row on that side" - the same convention
|
||||
/// `tableSelect`'s empty `exprText` already uses. Caller owns and frees
|
||||
/// `rest`.
|
||||
fn extractJoinFlags(allocator: std.mem.Allocator, args: []const []const u8) !struct {
|
||||
mode: JoinCliMode,
|
||||
leftExpr: []const u8,
|
||||
rightExpr: []const u8,
|
||||
rest: [][]const u8,
|
||||
} {
|
||||
var mode: JoinCliMode = .inner;
|
||||
var leftExpr: []const u8 = "";
|
||||
var rightExpr: []const u8 = "";
|
||||
var rest = std.ArrayList([]const u8).init(allocator);
|
||||
errdefer rest.deinit();
|
||||
|
||||
var i: usize = 0;
|
||||
while (i < args.len) : (i += 1) {
|
||||
if (std.mem.eql(u8, args[i], "--left")) {
|
||||
mode = .left;
|
||||
} else if (std.mem.eql(u8, args[i], "--right")) {
|
||||
mode = .right;
|
||||
} else if (std.mem.eql(u8, args[i], "--full")) {
|
||||
mode = .full;
|
||||
} else if (std.mem.eql(u8, args[i], "--left-expr")) {
|
||||
i += 1;
|
||||
if (i >= args.len) return error.InvalidJoinFlag;
|
||||
leftExpr = args[i];
|
||||
} else if (std.mem.eql(u8, args[i], "--right-expr")) {
|
||||
i += 1;
|
||||
if (i >= args.len) return error.InvalidJoinFlag;
|
||||
rightExpr = args[i];
|
||||
} else {
|
||||
try rest.append(args[i]);
|
||||
}
|
||||
}
|
||||
return .{ .mode = mode, .leftExpr = leftExpr, .rightExpr = rightExpr, .rest = try rest.toOwnedSlice() };
|
||||
}
|
||||
|
||||
/// Connects to a remote `cobalt serve` instance and runs `table join`
|
||||
/// there instead of against a local Db - the client-side counterpart of
|
||||
/// `server.zig`'s `table_join` opcode, mirroring `runRemoteSelect`'s shape
|
||||
/// but for all four of `join.zig`'s equi-join variants instead of a plain
|
||||
/// select. `--user`/`--pass`/`--token` authenticate first, needed only
|
||||
/// against a server started with `serve --auth`; printing is identical to
|
||||
/// the local `table join` command (`L <row>` / `R <row>`, or `L (nil)` /
|
||||
/// `R (nil)` for the unmatched side of an outer join).
|
||||
fn runRemoteJoin(allocator: std.mem.Allocator, stdout: anytype, stderr: anytype, addressSpec: []const u8, rest: []const []const u8) !void {
|
||||
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) catch return fail(stderr, "invalid --user/--pass/--token value");
|
||||
defer allocator.free(authFlags.rest);
|
||||
|
||||
try authenticateIfRequested(&client, stderr, authFlags.auth);
|
||||
|
||||
const jf = extractJoinFlags(allocator, authFlags.rest) catch return fail(stderr, "invalid --left-expr/--right-expr value");
|
||||
defer allocator.free(jf.rest);
|
||||
|
||||
const pf = extractPageFlags(allocator, jf.rest) catch return fail(stderr, "invalid --limit/--offset value");
|
||||
defer allocator.free(pf.rest);
|
||||
|
||||
if (pf.rest.len < 4) return fail(stderr, "remote-join needs [--left|--right|--full] <leftTable> <leftField> <rightTable> <rightField> [--left-expr E] [--right-expr E] [--limit N] [--offset N]");
|
||||
|
||||
const mode: cobalt.protocol.JoinMode = switch (jf.mode) {
|
||||
.inner => .inner,
|
||||
.left => .left,
|
||||
.right => .right,
|
||||
.full => .full,
|
||||
};
|
||||
|
||||
const pairs = client.tableJoin(allocator, mode, pf.rest[0], pf.rest[1], pf.rest[2], pf.rest[3], pf.page.offset, pf.page.limit, jf.leftExpr, jf.rightExpr) catch |err| {
|
||||
try stderr.print("error: {s}\n", .{@errorName(err)});
|
||||
std.process.exit(1);
|
||||
};
|
||||
defer cobalt.protocol.freeJoinPairs(allocator, pairs);
|
||||
|
||||
for (pairs) |p| {
|
||||
if (p.left) |l| {
|
||||
try stdout.writeAll("L ");
|
||||
try printRow(stdout, l);
|
||||
} else {
|
||||
try stdout.writeAll("L (nil)\n");
|
||||
}
|
||||
if (p.right) |r| {
|
||||
try stdout.writeAll("R ");
|
||||
try printRow(stdout, r);
|
||||
} else {
|
||||
try stdout.writeAll("R (nil)\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Connects to a remote `cobalt serve` instance and runs `table aggregate`
|
||||
/// there instead of against a local Db - the client-side counterpart of
|
||||
/// `server.zig`'s `table_aggregate` opcode, mirroring `runRemoteSelect`'s
|
||||
/// shape. Same `*` sentinel as local `table aggregate` for "no GROUP BY"
|
||||
/// (dispatches to `client.tableAggregateGlobal` instead of
|
||||
/// `tableAggregateGroups`), same comma-separated spec grammar
|
||||
/// (`parseAggSpecs`), and the same output format as the local command
|
||||
/// (`printAggLabel`/`printValue`). `--limit`/`--offset` window the result
|
||||
/// groups server-side (ignored for the `*` global path, same as local
|
||||
/// `table aggregate`), same convention as `remote-join`.
|
||||
/// `--user`/`--pass`/`--token` authenticate first, needed only against a
|
||||
/// server started with `serve --auth`. An optional literal `having <expr>`
|
||||
/// after the WHERE-style expr (split out by `splitAggFilterArgs`, the same
|
||||
/// helper the local `table aggregate` handler uses) filters the result
|
||||
/// groups server-side after aggregation, before `--limit`/`--offset` window
|
||||
/// them. An optional literal `order <field[:desc]>[,field...]` after that
|
||||
/// sorts the (HAVING-narrowed) result groups server-side before that same
|
||||
/// window, ignored for the `*` global path exactly like `--limit`/`--offset`
|
||||
/// already are - see `Client.tableAggregateGroups`/`tableAggregateGlobal`.
|
||||
fn runRemoteAggregate(allocator: std.mem.Allocator, stdout: anytype, stderr: anytype, addressSpec: []const u8, rest: []const []const u8) !void {
|
||||
if (rest.len < 3) return fail(stderr, "remote-aggregate needs <table> <groupField|*> <spec>[,<spec>...] [expr...] [having <expr>] [order <field[:desc]>[,field...]] [--limit N] [--offset N]");
|
||||
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 table = rest[0];
|
||||
const groupField = rest[1];
|
||||
const specs = parseAggSpecs(allocator, rest[2]) catch return fail(stderr, "invalid aggregate spec (expected count | count:field | sum:field | avg:field | min:field | max:field | distinct:field, comma-separated)");
|
||||
defer allocator.free(specs);
|
||||
|
||||
}
|
||||
|
||||
fn runVerify(allocator: std.mem.Allocator, stdout: anytype, dir: []const u8, count: usize) !void {
|
||||
|
||||
Reference in new issue
Block a user