feat: add parseOrderByFlag and parseOrderByList to repl

This commit is contained in:
dualfroz committed 2026-09-09 20:09:27 +02:00
1 parent 4b78a16183
commit e0a7cc311b
1 file changed
+154
+154
View File
@@ -1103,3 +1103,157 @@ fn extractSelectFlags(allocator: Allocator, args: []const []const u8) !struct {
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;