From 4ab4567114841dd320a0aae820db8f5a953272f5 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Wed, 9 Sep 2026 21:52:23 +0200 Subject: [PATCH] feat: expand repl --- src/repl.zig | 154 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) diff --git a/src/repl.zig b/src/repl.zig index 7cd38e3..392fac8 100644 --- a/src/repl.zig +++ b/src/repl.zig @@ -1577,3 +1577,157 @@ test "table select --order-by rejects an empty segment from a doubled comma inst try std.testing.expectEqualStrings("OK\nerror: InvalidPageFlag\n", out); } +test "table select reports a parse error on malformed expr 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 select items price bogus i:1\n"); + defer allocator.free(out); + try std.testing.expectEqualStrings("error: UnknownOperator\n", out); +} + +test "table explain reports full_scan, an index, and a composite index, matching what table select would actually use" { + const allocator = std.testing.allocator; + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + var db = try openTemp(&tmp, allocator); + defer db.close(); + + { + var table = try Table.open(&db, allocator, "users"); + defer table.deinit(); + try table.createIndex("age"); + try table.createCompositeIndex("city_age", &.{ "city", "age" }); + } + + var repl = Repl.init(allocator, &db); + defer repl.deinit(); + + const out = try runScript(allocator, &repl, + \\table put users i:1 city=s:warsaw age=i:30 + \\table explain users + \\table explain users city eq s:warsaw + \\table explain users age eq i:30 + \\table explain users city eq s:warsaw and age eq i:30 + \\ + ); + defer allocator.free(out); + try std.testing.expectEqualStrings( + "OK\n" ++ + "full scan (no usable index)\n" ++ + "full scan (no usable index)\n" ++ + "index 'age' (equality)\n" ++ + "composite index 'city_age' (equality, 2/2 columns matched)\n", + out, + ); +} + +test "table update applies --set to matching rows, prints the count, and leaves other rows untouched" { + 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 users i:1 city=s:warsaw active=i:0 + \\table put users i:2 city=s:warsaw active=i:0 + \\table put users i:3 city=s:krakow active=i:0 + \\table update users --set active=i:1 city eq s:warsaw + \\table select users --order-by __pk + \\ + ); + defer allocator.free(out); + try std.testing.expectEqualStrings( + "OK\nOK\nOK\n" ++ + "2\n" ++ + "__pk=1\tcity=warsaw\tactive=1\n" ++ + "__pk=2\tcity=warsaw\tactive=1\n" ++ + "__pk=3\tcity=krakow\tactive=0\n", + out, + ); +} + +test "table update without --set is rejected as MissingSetFlag instead of silently updating nothing" { + 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 users i:1 city=s:warsaw\ntable update users city eq s:warsaw\n"); + defer allocator.free(out); + try std.testing.expectEqualStrings("OK\nerror: MissingSetFlag\n", out); +} + +test "table join inner/left/right/full match main.zig's L/R printing" { + 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 customers i:1 name=alice + \\table put customers i:2 name=bob + \\table put orders i:100 customer_id=i:1 total=i:50 + \\table put orders i:101 customer_id=i:999 total=i:5 + \\table join customers __pk orders customer_id + \\table join --left customers __pk orders customer_id + \\table join --right customers __pk orders customer_id + \\table join --full customers __pk orders customer_id + \\ + ); + defer allocator.free(out); + try std.testing.expectEqualStrings( + // plain inner join: only alice/order100 matches, bob and the orphan + // order are absent. + "OK\nOK\nOK\nOK\n" ++ + "L __pk=1\tname=alice\n" ++ + "R __pk=100\tcustomer_id=1\ttotal=50\n" ++ + // --left: bob survives with R (nil), the orphan order is still absent. + "L __pk=1\tname=alice\n" ++ + "R __pk=100\tcustomer_id=1\ttotal=50\n" ++ + "L __pk=2\tname=bob\n" ++ + "R (nil)\n" ++ + // --right: the orphan order survives with L (nil), bob is absent. + "L __pk=1\tname=alice\n" ++ + "R __pk=100\tcustomer_id=1\ttotal=50\n" ++ + "L (nil)\n" ++ + "R __pk=101\tcustomer_id=999\ttotal=5\n" ++ + // --full: both bob and the orphan order survive. + "L __pk=1\tname=alice\n" ++ + "R __pk=100\tcustomer_id=1\ttotal=50\n" ++ + "L __pk=2\tname=bob\n" ++ + "R (nil)\n" ++ + "L (nil)\n" ++ + "R __pk=101\tcustomer_id=999\ttotal=5\n", + out, + ); +} + +test "table join respects --limit and --offset like table select does" { + 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(); +