From 4180f7a9b8c2b8df1457d8532fe474c838c6c159 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Thu, 10 Sep 2026 11:04:25 +0200 Subject: [PATCH] feat: build out repl --- src/repl.zig | 159 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/src/repl.zig b/src/repl.zig index 392fac8..ad2a65b 100644 --- a/src/repl.zig +++ b/src/repl.zig @@ -1731,3 +1731,162 @@ test "table join respects --limit and --offset like table select does" { var repl = Repl.init(allocator, &db); defer repl.deinit(); + const out = try runScript(allocator, &repl, + \\table put customers i:1 name=alice + \\table put customers i:2 name=bob + \\table put customers i:3 name=carol + \\table join --left customers __pk orders customer_id --offset 1 --limit 1 + \\table join --left customers __pk orders customer_id --offset 50 + \\ + ); + defer allocator.free(out); + try std.testing.expectEqualStrings( + "OK\nOK\nOK\n" ++ + // --offset 1 --limit 1: skip alice, keep exactly bob. + "L __pk=2\tname=bob\n" ++ + "R (nil)\n" ++ + // --offset 50 is past the end of a 3-row left outer join: nothing. + "", + out, + ); +} + +test "table join needs exactly four positional args after mode flags" { + const allocator = std.testing.allocator; + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + var db = try openTemp(&tmp, allocator); + defer db.close(); + + var repl = Repl.init(allocator, &db); + defer repl.deinit(); + + const out = try runScript(allocator, &repl, "table join --left customers __pk orders\n"); + defer allocator.free(out); + try std.testing.expectEqualStrings("error: WrongArgCount\n", out); +} + +test "table aggregate groups by field with count/sum/avg/min/max, matching main.zig's CLI output" { + const allocator = std.testing.allocator; + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + var db = try openTemp(&tmp, allocator); + defer db.close(); + + var repl = Repl.init(allocator, &db); + defer repl.deinit(); + + const out = try runScript(allocator, &repl, + \\table put orders i:1 city=warsaw total=i:100 + \\table put orders i:2 city=warsaw total=i:250 + \\table put orders i:3 city=krakow total=i:40 + \\table aggregate orders city count,sum:total,avg:total,min:total,max:total + \\ + ); + defer allocator.free(out); + try std.testing.expectEqualStrings( + "OK\nOK\nOK\n" ++ + "group=warsaw\tcount=2\tsum_total=350\tavg_total=175\tmin_total=100\tmax_total=250\n" ++ + "group=krakow\tcount=1\tsum_total=40\tavg_total=40\tmin_total=40\tmax_total=40\n", + out, + ); +} + +test "table aggregate distinct:field counts unique values, collapsing repeats within a group" { + const allocator = std.testing.allocator; + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + var db = try openTemp(&tmp, allocator); + defer db.close(); + + var repl = Repl.init(allocator, &db); + defer repl.deinit(); + + const out = try runScript(allocator, &repl, + \\table put orders i:1 city=warsaw region=mazovia + \\table put orders i:2 city=warsaw region=mazovia + \\table put orders i:3 city=warsaw region=innerwarsaw + \\table put orders i:4 city=krakow region=malopolska + \\table aggregate orders city count,distinct:region + \\table aggregate orders * distinct:region + \\ + ); + defer allocator.free(out); + try std.testing.expectEqualStrings( + "OK\nOK\nOK\nOK\n" ++ + "group=warsaw\tcount=3\tcount_distinct_region=2\n" ++ + "group=krakow\tcount=1\tcount_distinct_region=1\n" ++ + "count_distinct_region=3\n", + out, + ); +} + +test "table aggregate with * groups nothing and honors an expr filter before aggregating" { + const allocator = std.testing.allocator; + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + var db = try openTemp(&tmp, allocator); + defer db.close(); + + var repl = Repl.init(allocator, &db); + defer repl.deinit(); + + const out = try runScript(allocator, &repl, + \\table put orders i:1 city=warsaw total=i:100 + \\table put orders i:2 city=warsaw total=i:250 + \\table put orders i:3 city=krakow total=i:40 + \\table aggregate orders * count,sum:total + \\table aggregate orders * count,sum:total total gt i:50 + \\ + ); + defer allocator.free(out); + try std.testing.expectEqualStrings( + "OK\nOK\nOK\n" ++ + "count=3\tsum_total=390\n" ++ + "count=2\tsum_total=350\n", + out, + ); +} + +test "table aggregate rejects an invalid spec without crashing" { + const allocator = std.testing.allocator; + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + var db = try openTemp(&tmp, allocator); + defer db.close(); + + var repl = Repl.init(allocator, &db); + defer repl.deinit(); + + const out = try runScript(allocator, &repl, "table aggregate orders city bogus:total\n"); + defer allocator.free(out); + try std.testing.expectEqualStrings("error: InvalidArgument\n", out); +} + +test "table window row_number annotates every row without collapsing, restarting per partition in order" { + const allocator = std.testing.allocator; + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + var db = try openTemp(&tmp, allocator); + defer db.close(); + + var repl = Repl.init(allocator, &db); + defer repl.deinit(); + + const out = try runScript(allocator, &repl, + \\table put orders i:1 city=warsaw total=i:100 + \\table put orders i:2 city=warsaw total=i:250 + \\table put orders i:3 city=krakow total=i:40 + \\table window orders row_number partition city order total + \\ + ); + defer allocator.free(out); + try std.testing.expectEqualStrings( + "OK\nOK\nOK\n" ++ + "__pk=1\tcity=warsaw\ttotal=100\trow_number=1\n" ++ + "__pk=2\tcity=warsaw\ttotal=250\trow_number=2\n" ++ + "__pk=3\tcity=krakow\ttotal=40\trow_number=1\n", + out, + ); +} +