feat: extend main with splitWindowArgs and parsePartitionList
This commit is contained in:
1 parent
6719fac786
commit
431669c07a
1 file changed
+173
+173
@@ -1522,6 +1522,179 @@ fn printWindowLabel(stdout: anytype, spec: cobalt.WindowSpec) !void {
|
||||
/// Splits the trailing free-text args of `table window` (after the spec
|
||||
/// token and any `--limit`/`--offset` flags have already been pulled out)
|
||||
/// on literal `partition` and `order` tokens, in that clause order
|
||||
/// (mirroring SQL's `OVER (PARTITION BY ... ORDER BY ...)`) - the `table
|
||||
/// window` counterpart of `splitAggFilterArgs`. Everything before
|
||||
/// `partition` (or `order`, if `partition` is absent) is the WHERE-style
|
||||
/// expr filtering rows before annotation; everything between `partition`
|
||||
/// and `order` (or to the end) is the partition field list (a single
|
||||
/// comma-separated token, parsed by the caller); everything after `order`
|
||||
/// is the ORDER BY field list, same shape as `splitAggFilterArgs`'s
|
||||
/// `orderArgs`. Either or both tokens absent means "one implicit partition"
|
||||
/// / "no ORDER BY".
|
||||
fn splitWindowArgs(args: []const []const u8) struct { whereArgs: []const []const u8, partitionArgs: ?[]const []const u8, orderArgs: ?[]const []const u8 } {
|
||||
if (findLiteralToken(args, "partition")) |pi| {
|
||||
const whereArgs = args[0..pi];
|
||||
const afterPartition = args[pi + 1 ..];
|
||||
if (findLiteralToken(afterPartition, "order")) |oi| {
|
||||
return .{ .whereArgs = whereArgs, .partitionArgs = afterPartition[0..oi], .orderArgs = afterPartition[oi + 1 ..] };
|
||||
}
|
||||
return .{ .whereArgs = whereArgs, .partitionArgs = afterPartition, .orderArgs = null };
|
||||
}
|
||||
if (findLiteralToken(args, "order")) |oi| {
|
||||
return .{ .whereArgs = args[0..oi], .partitionArgs = null, .orderArgs = args[oi + 1 ..] };
|
||||
}
|
||||
return .{ .whereArgs = args, .partitionArgs = null, .orderArgs = null };
|
||||
}
|
||||
|
||||
/// Parses `table window`'s `partition <field>[,field...]` value into a
|
||||
/// borrowed field-name list (same non-owning convention `parseWindowSpec`
|
||||
/// uses) - a leading/trailing/doubled comma yields an empty segment,
|
||||
/// rejected by `index.validIdentifier` the same way a malformed order-by
|
||||
/// suffix is elsewhere, not silently skipped. Caller owns and frees the
|
||||
/// returned slice (not each field - they borrow from `text`).
|
||||
fn parsePartitionList(allocator: std.mem.Allocator, text: []const u8) ![][]const u8 {
|
||||
var list = std.ArrayList([]const u8).init(allocator);
|
||||
errdefer list.deinit();
|
||||
var it = std.mem.splitScalar(u8, text, ',');
|
||||
while (it.next()) |field| {
|
||||
if (!cobalt.index.validIdentifier(field)) return error.InvalidPartitionField;
|
||||
try list.append(field);
|
||||
}
|
||||
return list.toOwnedSlice();
|
||||
}
|
||||
|
||||
/// Dispatches `user <op> ...` onto auth.zig's Store: username/password/role
|
||||
/// management for `serve --auth`, always operating on the local Db file
|
||||
/// directly (never over the wire - creating the first admin has to work
|
||||
/// even before any server is running, and there is no bootstrap way to
|
||||
/// AUTH your way into creating the first account).
|
||||
fn runUser(allocator: std.mem.Allocator, stdout: anytype, stderr: anytype, db: *cobalt.Db, rest: []const []const u8) !void {
|
||||
if (rest.len < 1) return fail(stderr, "user needs a subcommand (add/passwd/role/del/list)");
|
||||
const op = rest[0];
|
||||
const args = rest[1..];
|
||||
const store = cobalt.AuthStore.init(db);
|
||||
|
||||
if (std.mem.eql(u8, op, "add")) {
|
||||
if (args.len < 3) return fail(stderr, "user add needs <username> <password> readonly|readwrite|admin");
|
||||
const role = cobalt.AuthRole.parse(args[2]) catch return fail(stderr, "role must be readonly, readwrite, or admin");
|
||||
store.createUser(allocator, args[0], args[1], role, cobalt.auth.recommendedRounds) catch |err| {
|
||||
return fail(stderr, @errorName(err));
|
||||
};
|
||||
try stdout.print("user '{s}' created with role {s}\n", .{ args[0], role.name() });
|
||||
} else if (std.mem.eql(u8, op, "passwd")) {
|
||||
if (args.len < 2) return fail(stderr, "user passwd needs <username> <newpassword>");
|
||||
store.setPassword(allocator, args[0], args[1], cobalt.auth.recommendedRounds) catch |err| {
|
||||
return fail(stderr, @errorName(err));
|
||||
};
|
||||
try stdout.print("password updated for '{s}'\n", .{args[0]});
|
||||
} else if (std.mem.eql(u8, op, "role")) {
|
||||
if (args.len < 2) return fail(stderr, "user role needs <username> readonly|readwrite|admin");
|
||||
const role = cobalt.AuthRole.parse(args[1]) catch return fail(stderr, "role must be readonly, readwrite, or admin");
|
||||
store.setRole(allocator, args[0], role) catch |err| {
|
||||
return fail(stderr, @errorName(err));
|
||||
};
|
||||
try stdout.print("role for '{s}' set to {s}\n", .{ args[0], role.name() });
|
||||
} else if (std.mem.eql(u8, op, "del")) {
|
||||
if (args.len < 1) return fail(stderr, "user del needs <username>");
|
||||
store.deleteUser(allocator, args[0]) catch |err| {
|
||||
return fail(stderr, @errorName(err));
|
||||
};
|
||||
try stdout.print("user '{s}' deleted\n", .{args[0]});
|
||||
} else if (std.mem.eql(u8, op, "list")) {
|
||||
const users = try store.listUsers(allocator);
|
||||
defer cobalt.auth.freeUserList(allocator, users);
|
||||
for (users) |u| try stdout.print("{s}\t{s}\n", .{ u.username, u.role.name() });
|
||||
} else {
|
||||
return fail(stderr, "unknown user operation");
|
||||
}
|
||||
}
|
||||
|
||||
/// Dispatches `table <op> <table> ...` onto query.zig's Table: schemaless
|
||||
/// rows with secondary indexes, until now only reachable through the library
|
||||
/// API. Opens a fresh Table view per invocation (indexes are loaded from
|
||||
/// their persisted metadata, so this is consistent across process restarts).
|
||||
fn runTable(allocator: std.mem.Allocator, stdout: anytype, stderr: anytype, db: *cobalt.Db, rest: []const []const u8) !void {
|
||||
if (rest.len < 1) return fail(stderr, "table needs a subcommand (put/get/del/delete/scan/select/index/join/aggregate/export/import)");
|
||||
const sub = rest[0];
|
||||
const args = rest[1..];
|
||||
|
||||
if (std.mem.eql(u8, sub, "put")) {
|
||||
if (args.len < 2) return fail(stderr, "table put needs <table> <pk> [field=value ...]");
|
||||
var table = try cobalt.Table.open(db, allocator, args[0]);
|
||||
defer table.deinit();
|
||||
const pk = parseValue(args[1]) catch return fail(stderr, "invalid primary key");
|
||||
|
||||
var fields = std.ArrayList(Field).init(allocator);
|
||||
defer fields.deinit();
|
||||
for (args[2..]) |raw| {
|
||||
const f = parseField(raw) catch return fail(stderr, "invalid field, expected name=value");
|
||||
try fields.append(f);
|
||||
}
|
||||
table.put(pk, fields.items) catch |err| return fail(stderr, @errorName(err));
|
||||
try stdout.writeAll("OK\n");
|
||||
} else if (std.mem.eql(u8, sub, "get")) {
|
||||
if (args.len < 2) return fail(stderr, "table get needs <table> <pk>");
|
||||
var table = try cobalt.Table.open(db, allocator, args[0]);
|
||||
defer table.deinit();
|
||||
const pk = parseValue(args[1]) catch return fail(stderr, "invalid primary key");
|
||||
var got = try table.get(pk);
|
||||
if (got) |*row| {
|
||||
defer row.deinit();
|
||||
try printRow(stdout, row.*);
|
||||
} else {
|
||||
try stdout.writeAll("(nil)\n");
|
||||
}
|
||||
} else if (std.mem.eql(u8, sub, "del")) {
|
||||
if (args.len < 2) return fail(stderr, "table del needs <table> <pk>");
|
||||
var table = try cobalt.Table.open(db, allocator, args[0]);
|
||||
defer table.deinit();
|
||||
const pk = parseValue(args[1]) catch return fail(stderr, "invalid primary key");
|
||||
try table.delete(pk);
|
||||
try stdout.writeAll("OK\n");
|
||||
} else if (std.mem.eql(u8, sub, "scan")) {
|
||||
if (args.len < 1) return fail(stderr, "table scan needs <table>");
|
||||
var table = try cobalt.Table.open(db, allocator, args[0]);
|
||||
defer table.deinit();
|
||||
const rows = try table.scanAll();
|
||||
defer cobalt.query.freeRows(allocator, rows);
|
||||
for (rows) |r| try printRow(stdout, r);
|
||||
} else if (std.mem.eql(u8, sub, "select")) {
|
||||
if (args.len < 1) return fail(stderr, "table select needs <table> [expr...] [--limit N] [--offset N] [--order-by field[:desc][,field[:desc]...]]");
|
||||
var table = try cobalt.Table.open(db, allocator, args[0]);
|
||||
defer table.deinit();
|
||||
|
||||
const sel = extractSelectFlags(allocator, args[1..]) 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);
|
||||
|
||||
var parsed: ?cobalt.expr.Parsed = null;
|
||||
defer if (parsed) |*p| p.deinit();
|
||||
if (exprText.len > 0) {
|
||||
parsed = cobalt.expr.parse(allocator, exprText) catch |err| return fail(stderr, @errorName(err));
|
||||
}
|
||||
const pred: ?cobalt.Predicate = if (parsed) |p| p.predicate else null;
|
||||
|
||||
const rows = try table.selectPageOrdered(pred, .{ .offset = sel.flags.page.offset, .limit = sel.flags.page.limit }, sel.flags.orderBy);
|
||||
defer cobalt.query.freeRows(allocator, rows);
|
||||
for (rows) |r| try printRow(stdout, r);
|
||||
} else if (std.mem.eql(u8, sub, "explain")) {
|
||||
if (args.len < 1) return fail(stderr, "table explain needs <table> [expr...]");
|
||||
var table = try cobalt.Table.open(db, allocator, args[0]);
|
||||
defer table.deinit();
|
||||
|
||||
const exprText = try std.mem.join(allocator, " ", args[1..]);
|
||||
defer allocator.free(exprText);
|
||||
|
||||
var parsed: ?cobalt.expr.Parsed = null;
|
||||
defer if (parsed) |*p| p.deinit();
|
||||
if (exprText.len > 0) {
|
||||
parsed = cobalt.expr.parse(allocator, exprText) catch |err| return fail(stderr, @errorName(err));
|
||||
}
|
||||
const pred: ?cobalt.Predicate = if (parsed) |p| p.predicate else null;
|
||||
|
||||
}
|
||||
|
||||
fn runVerify(allocator: std.mem.Allocator, stdout: anytype, dir: []const u8, count: usize) !void {
|
||||
|
||||
Reference in new issue
Block a user