feat: add parseOrderByFlag and parseOrderByList to repl
This commit is contained in:
1 parent
4b78a16183
commit
e0a7cc311b
1 file changed
+154
+154
@@ -1103,3 +1103,157 @@ fn extractSelectFlags(allocator: Allocator, args: []const []const u8) !struct {
|
|||||||
return .{ .flags = flags, .rest = try rest.toOwnedSlice() };
|
return .{ .flags = flags, .rest = try rest.toOwnedSlice() };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses `--order-by`'s `field[:desc]` value - mirrors main.zig's
|
||||||
|
/// identically-shaped `parseOrderByFlag` (kept as its own copy for the same
|
||||||
|
/// reason `extractPageFlags`'s doc comment gives: repl.zig cannot import
|
||||||
|
/// main.zig, the binary entry point). `:desc`/`:asc` are the only accepted
|
||||||
|
/// suffixes; anything else is a malformed flag, not a silent default.
|
||||||
|
fn parseOrderByFlag(text: []const u8) !querymod.OrderBy {
|
||||||
|
if (std.mem.indexOfScalar(u8, text, ':')) |colon| {
|
||||||
|
const field = text[0..colon];
|
||||||
|
const dir = text[colon + 1 ..];
|
||||||
|
if (field.len == 0) return error.InvalidPageFlag;
|
||||||
|
if (std.mem.eql(u8, dir, "desc")) return .{ .field = field, .descending = true };
|
||||||
|
if (std.mem.eql(u8, dir, "asc")) return .{ .field = field, .descending = false };
|
||||||
|
return error.InvalidPageFlag;
|
||||||
|
}
|
||||||
|
if (text.len == 0) return error.InvalidPageFlag;
|
||||||
|
return .{ .field = text };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Splits `--order-by`'s comma-separated value into one or more columns -
|
||||||
|
/// mirrors main.zig's identically-shaped `parseOrderByList` (kept as its
|
||||||
|
/// own copy for the same reason `parseOrderByFlag` above is). Caller owns
|
||||||
|
/// and frees the returned slice.
|
||||||
|
fn parseOrderByList(allocator: Allocator, text: []const u8) ![]querymod.OrderBy {
|
||||||
|
var list = std.ArrayList(querymod.OrderBy).init(allocator);
|
||||||
|
errdefer list.deinit();
|
||||||
|
var it = std.mem.splitScalar(u8, text, ',');
|
||||||
|
while (it.next()) |segment| {
|
||||||
|
try list.append(try parseOrderByFlag(segment));
|
||||||
|
}
|
||||||
|
return list.toOwnedSlice();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Splits `name=value` into a Field, validating the name the same way
|
||||||
|
/// index/table identifiers already are. Values follow the CLI's `i:`/`f:`/
|
||||||
|
/// `b:`/`s:` typed-literal convention via `Value.parseCli`.
|
||||||
|
fn parseField(text: []const u8) !Field {
|
||||||
|
const eq = std.mem.indexOfScalar(u8, text, '=') orelse return ReplError.InvalidField;
|
||||||
|
const name = text[0..eq];
|
||||||
|
if (!idx.validIdentifier(name)) return ReplError.InvalidField;
|
||||||
|
const value = try Value.parseCli(text[eq + 1 ..]);
|
||||||
|
return .{ .name = name, .value = value };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Splits `--set`'s comma-separated `name=value[,name=value...]` value into
|
||||||
|
/// a list of Fields, each parsed by `parseField` - mirrors main.zig's
|
||||||
|
/// identically-shaped `parseSetList` (kept as its own copy for the same
|
||||||
|
/// reason `parseOrderByList` above is). Caller owns and frees the returned
|
||||||
|
/// slice.
|
||||||
|
fn parseSetList(allocator: Allocator, text: []const u8) ![]Field {
|
||||||
|
var list = std.ArrayList(Field).init(allocator);
|
||||||
|
errdefer list.deinit();
|
||||||
|
var it = std.mem.splitScalar(u8, text, ',');
|
||||||
|
while (it.next()) |segment| {
|
||||||
|
try list.append(try parseField(segment));
|
||||||
|
}
|
||||||
|
return list.toOwnedSlice();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pulls a single required `--set name=value[,name=value...]` flag out of a
|
||||||
|
/// `table update` argument list - mirrors main.zig's identically-shaped
|
||||||
|
/// `extractSetFlag`. Missing `--set` is `error.MissingSetFlag`: a `SET`-less
|
||||||
|
/// `UPDATE` names nothing to change. Caller owns and frees `rest` and the
|
||||||
|
/// returned `sets` slice.
|
||||||
|
fn extractSetFlag(allocator: Allocator, args: []const []const u8) !struct { sets: []Field, rest: [][]const u8 } {
|
||||||
|
var sets: []Field = &.{};
|
||||||
|
errdefer allocator.free(sets);
|
||||||
|
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], "--set")) {
|
||||||
|
i += 1;
|
||||||
|
if (i >= args.len) return error.InvalidSetFlag;
|
||||||
|
allocator.free(sets);
|
||||||
|
sets = parseSetList(allocator, args[i]) catch return error.InvalidSetFlag;
|
||||||
|
} else {
|
||||||
|
try rest.append(args[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sets.len == 0) return error.MissingSetFlag;
|
||||||
|
return .{ .sets = sets, .rest = try rest.toOwnedSlice() };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseAggKind(name: []const u8) ?aggregatemod.AggKind {
|
||||||
|
if (std.mem.eql(u8, name, "count")) return .count;
|
||||||
|
if (std.mem.eql(u8, name, "sum")) return .sum;
|
||||||
|
if (std.mem.eql(u8, name, "avg")) return .avg;
|
||||||
|
if (std.mem.eql(u8, name, "min")) return .min;
|
||||||
|
if (std.mem.eql(u8, name, "max")) return .max;
|
||||||
|
if (std.mem.eql(u8, name, "distinct")) return .count_distinct;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses a comma-separated `table aggregate` spec list, same grammar and
|
||||||
|
/// same small standalone copy as main.zig's identically-named helper (see
|
||||||
|
/// `extractPageFlags` above for why this is not shared - main.zig is the
|
||||||
|
/// binary entry point, not a module repl.zig can import).
|
||||||
|
fn parseAggSpecs(allocator: Allocator, text: []const u8) ![]aggregatemod.AggSpec {
|
||||||
|
var specs = std.ArrayList(aggregatemod.AggSpec).init(allocator);
|
||||||
|
errdefer specs.deinit();
|
||||||
|
var it = std.mem.splitScalar(u8, text, ',');
|
||||||
|
while (it.next()) |part| {
|
||||||
|
if (part.len == 0) return error.InvalidAggSpec;
|
||||||
|
if (std.mem.indexOfScalar(u8, part, ':')) |colon| {
|
||||||
|
const kind = parseAggKind(part[0..colon]) orelse return error.InvalidAggSpec;
|
||||||
|
const field = part[colon + 1 ..];
|
||||||
|
if (!idx.validIdentifier(field)) return error.InvalidAggSpec;
|
||||||
|
try specs.append(.{ .field = field, .kind = kind });
|
||||||
|
} else {
|
||||||
|
const kind = parseAggKind(part) orelse return error.InvalidAggSpec;
|
||||||
|
if (kind != .count) return error.InvalidAggSpec;
|
||||||
|
try specs.append(.{ .field = "", .kind = kind });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (specs.items.len == 0) return error.InvalidAggSpec;
|
||||||
|
return specs.toOwnedSlice();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn printAggLabel(writer: anytype, spec: aggregatemod.AggSpec) !void {
|
||||||
|
if (spec.field.len == 0) {
|
||||||
|
try writer.writeAll(@tagName(spec.kind));
|
||||||
|
} else {
|
||||||
|
try writer.print("{s}_{s}", .{ @tagName(spec.kind), spec.field });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseWindowKind(name: []const u8) ?windowmod.WindowKind {
|
||||||
|
if (std.mem.eql(u8, name, "row_number")) return .row_number;
|
||||||
|
if (std.mem.eql(u8, name, "rank")) return .rank;
|
||||||
|
if (std.mem.eql(u8, name, "dense_rank")) return .dense_rank;
|
||||||
|
if (std.mem.eql(u8, name, "sum")) return .sum;
|
||||||
|
if (std.mem.eql(u8, name, "avg")) return .avg;
|
||||||
|
if (std.mem.eql(u8, name, "count")) return .count;
|
||||||
|
if (std.mem.eql(u8, name, "count_distinct")) return .count_distinct;
|
||||||
|
if (std.mem.eql(u8, name, "min")) return .min;
|
||||||
|
if (std.mem.eql(u8, name, "max")) return .max;
|
||||||
|
if (std.mem.eql(u8, name, "lag")) return .lag;
|
||||||
|
if (std.mem.eql(u8, name, "lead")) return .lead;
|
||||||
|
if (std.mem.eql(u8, name, "first_value")) return .first_value;
|
||||||
|
if (std.mem.eql(u8, name, "last_value")) return .last_value;
|
||||||
|
if (std.mem.eql(u8, name, "nth_value")) return .nth_value;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses a single `table window` spec token, same grammar (including
|
||||||
|
/// lag/lead's optional `:offset[:default]` suffix and nth_value's required
|
||||||
|
/// `:n[:default]` suffix) and same small standalone copy as main.zig's
|
||||||
|
/// identically-named helper (see `parseAggSpecs` above for why this is not
|
||||||
|
/// shared).
|
||||||
|
fn parseWindowSpec(text: []const u8) !windowmod.WindowSpec {
|
||||||
|
var it = std.mem.splitScalar(u8, text, ':');
|
||||||
|
const kind = parseWindowKind(it.next().?) orelse return error.InvalidWindowSpec;
|
||||||
|
|
||||||
Reference in new issue
Block a user