diff --git a/src/repl.zig b/src/repl.zig index 328722d..7cd38e3 100644 --- a/src/repl.zig +++ b/src/repl.zig @@ -1417,3 +1417,163 @@ test "plain put/get/del run immediately without a transaction" { var db = try openTemp(&tmp, allocator); defer db.close(); + var repl = Repl.init(allocator, &db); + defer repl.deinit(); + + const out = try runScript(allocator, &repl, "put a 1\nget a\ndel a\nget a\n"); + defer allocator.free(out); + try std.testing.expectEqualStrings("OK\n1\nOK\n(nil)\n", out); +} + +test "comments and blank lines are ignored, exit stops the loop early" { + 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, + \\# a comment + \\ + \\put a 1 + \\exit + \\get a + \\ + ); + defer allocator.free(out); + try std.testing.expectEqualStrings("OK\n", out); +} + +test "table select filters by expr and honors --limit/--offset" { + 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 items i:1 name=a price=i:10 + \\table put items i:2 name=b price=i:20 + \\table put items i:3 name=c price=i:30 + \\table select items price gt i:10 + \\table select items price gt i:10 --limit 1 + \\table select items price gt i:10 --offset 1 + \\table select items + \\ + ); + defer allocator.free(out); + try std.testing.expectEqualStrings( + "OK\nOK\nOK\n" ++ + "__pk=2\tname=b\tprice=20\n" ++ + "__pk=3\tname=c\tprice=30\n" ++ + "__pk=2\tname=b\tprice=20\n" ++ + "__pk=3\tname=c\tprice=30\n" ++ + "__pk=1\tname=a\tprice=10\n" ++ + "__pk=2\tname=b\tprice=20\n" ++ + "__pk=3\tname=c\tprice=30\n", + out, + ); +} + +test "table select --order-by sorts by field, honoring :desc and combining with --limit/--offset" { + 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(); + + // Insert with pk NOT in price order, so a bug that sorted only within + // the natural pk scan order (or ignored --order-by entirely) would + // produce a visibly different sequence than asserted below. + const out = try runScript(allocator, &repl, + \\table put items i:1 name=b price=i:20 + \\table put items i:2 name=a price=i:30 + \\table put items i:3 name=c price=i:10 + \\table select items --order-by price + \\table select items --order-by price:desc + \\table select items --order-by price:desc --limit 1 --offset 1 + \\ + ); + defer allocator.free(out); + try std.testing.expectEqualStrings( + "OK\nOK\nOK\n" ++ + "__pk=3\tname=c\tprice=10\n" ++ + "__pk=1\tname=b\tprice=20\n" ++ + "__pk=2\tname=a\tprice=30\n" ++ + "__pk=2\tname=a\tprice=30\n" ++ + "__pk=1\tname=b\tprice=20\n" ++ + "__pk=3\tname=c\tprice=10\n" ++ + "__pk=1\tname=b\tprice=20\n", + out, + ); +} + +test "table select --order-by rejects a malformed direction suffix instead of defaulting to ascending" { + 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 items i:1 price=i:5\ntable select items --order-by price:dsc\n"); + defer allocator.free(out); + try std.testing.expectEqualStrings("OK\nerror: InvalidPageFlag\n", out); +} + +test "table select --order-by with two comma-separated columns sorts by the first, breaking ties with the second" { + 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(); + + // Two rows share city "warsaw" with prices NOT in insertion order, so + // a single-column city sort alone would leave them in whatever order + // they were inserted - only a real `price` tiebreak fixes that. + const out = try runScript(allocator, &repl, + \\table put items i:1 city=warsaw price=i:30 + \\table put items i:2 city=krakow price=i:5 + \\table put items i:3 city=warsaw price=i:10 + \\table select items --order-by city,price + \\ + ); + defer allocator.free(out); + try std.testing.expectEqualStrings( + "OK\nOK\nOK\n" ++ + "__pk=2\tcity=krakow\tprice=5\n" ++ + "__pk=3\tcity=warsaw\tprice=10\n" ++ + "__pk=1\tcity=warsaw\tprice=30\n", + out, + ); +} + +test "table select --order-by rejects an empty segment from a doubled comma instead of silently collapsing it" { + 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 items i:1 price=i:5\ntable select items --order-by city,,price\n"); + defer allocator.free(out); + try std.testing.expectEqualStrings("OK\nerror: InvalidPageFlag\n", out); +} +