feat: add parseWindowSpecs and printWindowLabel to repl
This commit is contained in:
1 parent
e0a7cc311b
commit
329e682eab
1 file changed
+160
+160
@@ -1257,3 +1257,163 @@ fn parseWindowSpec(text: []const u8) !windowmod.WindowSpec {
|
||||
var it = std.mem.splitScalar(u8, text, ':');
|
||||
const kind = parseWindowKind(it.next().?) orelse return error.InvalidWindowSpec;
|
||||
|
||||
if (kind == .row_number or kind == .rank or kind == .dense_rank) {
|
||||
if (it.next() != null) return error.InvalidWindowSpec;
|
||||
return .{ .field = "", .kind = kind };
|
||||
}
|
||||
if (kind == .lag or kind == .lead) {
|
||||
const field = it.next() orelse return error.InvalidWindowSpec;
|
||||
if (!idx.validIdentifier(field)) return error.InvalidWindowSpec;
|
||||
var spec = windowmod.WindowSpec{ .field = field, .kind = kind };
|
||||
if (it.next()) |offsetText| {
|
||||
spec.offset = std.fmt.parseInt(u64, offsetText, 10) catch return error.InvalidWindowSpec;
|
||||
// `it.rest()`, NOT another `it.next()`: a typed default literal
|
||||
// (`i:-1`, `s:foo:bar`) can itself contain colons, which would
|
||||
// get wrongly re-split by a further `splitScalar` step.
|
||||
const rest = it.rest();
|
||||
if (rest.len > 0) spec.default = Value.parseCli(rest) catch return error.InvalidWindowSpec;
|
||||
}
|
||||
return spec;
|
||||
}
|
||||
if (kind == .nth_value) {
|
||||
const field = it.next() orelse return error.InvalidWindowSpec;
|
||||
if (!idx.validIdentifier(field)) return error.InvalidWindowSpec;
|
||||
const nText = it.next() orelse return error.InvalidWindowSpec;
|
||||
const n = std.fmt.parseInt(u64, nText, 10) catch return error.InvalidWindowSpec;
|
||||
if (n == 0) return error.InvalidWindowSpec;
|
||||
var spec = windowmod.WindowSpec{ .field = field, .kind = kind, .offset = n };
|
||||
const rest = it.rest();
|
||||
if (rest.len > 0) spec.default = Value.parseCli(rest) catch return error.InvalidWindowSpec;
|
||||
return spec;
|
||||
}
|
||||
|
||||
const field = it.next() orelse {
|
||||
if (kind == .count) return .{ .field = "", .kind = kind };
|
||||
return error.InvalidWindowSpec;
|
||||
};
|
||||
if (it.next() != null) return error.InvalidWindowSpec;
|
||||
if (!idx.validIdentifier(field)) return error.InvalidWindowSpec;
|
||||
return .{ .field = field, .kind = kind };
|
||||
}
|
||||
|
||||
/// Parses `table window`'s comma-separated spec list, same grammar and same
|
||||
/// small standalone copy as main.zig's identically-named helper (see
|
||||
/// `parseAggSpecs` above for why this is not shared) - lets one interactive
|
||||
/// `table window` call annotate rows with multiple columns sharing one
|
||||
/// `PARTITION BY`/`ORDER BY`/frame (`window.computeMany`).
|
||||
fn parseWindowSpecs(allocator: Allocator, text: []const u8) ![]windowmod.WindowSpec {
|
||||
var specs = std.ArrayList(windowmod.WindowSpec).init(allocator);
|
||||
errdefer specs.deinit();
|
||||
var it = std.mem.splitScalar(u8, text, ',');
|
||||
while (it.next()) |part| {
|
||||
if (part.len == 0) return error.InvalidWindowSpec;
|
||||
try specs.append(try parseWindowSpec(part));
|
||||
}
|
||||
if (specs.items.len == 0) return error.InvalidWindowSpec;
|
||||
return specs.toOwnedSlice();
|
||||
}
|
||||
|
||||
fn printWindowLabel(writer: anytype, spec: windowmod.WindowSpec) !void {
|
||||
if (spec.field.len == 0) {
|
||||
try writer.writeAll(@tagName(spec.kind));
|
||||
} else {
|
||||
try writer.print("{s}_{s}", .{ @tagName(spec.kind), spec.field });
|
||||
}
|
||||
}
|
||||
|
||||
fn findLiteralToken(args: []const []const u8, token: []const u8) ?usize {
|
||||
for (args, 0..) |a, i| {
|
||||
if (std.mem.eql(u8, a, token)) return i;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Splits the trailing free-text args of `table window` on literal
|
||||
/// `partition` and `order` tokens, same grammar and same small standalone
|
||||
/// copy as main.zig's identically-named helper (see `parseAggSpecs` above
|
||||
/// for why this is not shared).
|
||||
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, same
|
||||
/// grammar and same small standalone copy as main.zig's identically-named
|
||||
/// helper. Caller owns and frees the returned slice (not each field - they
|
||||
/// borrow from `text`).
|
||||
fn parsePartitionList(allocator: 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 (!idx.validIdentifier(field)) return error.InvalidPartitionField;
|
||||
try list.append(field);
|
||||
}
|
||||
return list.toOwnedSlice();
|
||||
}
|
||||
|
||||
fn printValue(writer: anytype, value: Value) !void {
|
||||
switch (value) {
|
||||
.none => try writer.writeAll("(none)"),
|
||||
.boolean => |b| try writer.print("{}", .{b}),
|
||||
.int => |i| try writer.print("{d}", .{i}),
|
||||
.float => |f| try writer.print("{d}", .{f}),
|
||||
.text => |s| try writer.writeAll(s),
|
||||
.bytes => |b| for (b) |byte| try writer.print("{x:0>2}", .{byte}),
|
||||
}
|
||||
}
|
||||
|
||||
fn printRow(writer: anytype, row: Row) !void {
|
||||
for (row.fields.items, 0..) |f, i| {
|
||||
if (i > 0) try writer.writeAll("\t");
|
||||
try writer.print("{s}=", .{f.name});
|
||||
try printValue(writer, f.value);
|
||||
}
|
||||
try writer.writeAll("\n");
|
||||
}
|
||||
|
||||
/// Same wording as main.zig's own `printPlan` (the two binaries do not
|
||||
/// share this helper - main.zig has no repl.zig dependency and vice versa).
|
||||
fn printPlan(writer: anytype, plan: querymod.Plan) !void {
|
||||
switch (plan.kind) {
|
||||
.full_scan => try writer.writeAll("full scan (no usable index)\n"),
|
||||
.index_eq => try writer.print("index '{s}' (equality)\n", .{plan.indexName.?}),
|
||||
.index_range => try writer.print("index '{s}' (range)\n", .{plan.indexName.?}),
|
||||
.composite_eq => try writer.print("composite index '{s}' (equality, {d}/{d} columns matched)\n", .{ plan.indexName.?, plan.matchedFields, plan.totalFields }),
|
||||
.composite_range => try writer.print("composite index '{s}' (range, {d}/{d} columns matched)\n", .{ plan.indexName.?, plan.matchedFields, plan.totalFields }),
|
||||
}
|
||||
}
|
||||
|
||||
fn openTemp(tmp: *std.testing.TmpDir, allocator: Allocator) !Db {
|
||||
var pathBuf: [std.fs.max_path_bytes]u8 = undefined;
|
||||
const base = try tmp.dir.realpath(".", &pathBuf);
|
||||
var dbPathBuf: [std.fs.max_path_bytes]u8 = undefined;
|
||||
const dbPath = try std.fmt.bufPrint(&dbPathBuf, "{s}/store", .{base});
|
||||
return Db.open(allocator, dbPath, .{});
|
||||
}
|
||||
|
||||
fn runScript(allocator: Allocator, repl: *Repl, script: []const u8) ![]u8 {
|
||||
var stream = std.io.fixedBufferStream(script);
|
||||
var out = std.ArrayList(u8).init(allocator);
|
||||
errdefer out.deinit();
|
||||
try repl.runFromReader(stream.reader(), out.writer(), false);
|
||||
return out.toOwnedSlice();
|
||||
}
|
||||
|
||||
test "plain put/get/del run immediately without a transaction" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
var db = try openTemp(&tmp, allocator);
|
||||
defer db.close();
|
||||
|
||||
Reference in new issue
Block a user