From aead3244841ca8fa6f7d477ef4120465a06ba42c Mon Sep 17 00:00:00 2001 From: DualFroz Date: Thu, 10 Sep 2026 14:06:33 +0200 Subject: [PATCH] feat: extend repl --- src/repl.zig | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) diff --git a/src/repl.zig b/src/repl.zig index 2be3c68..f0c26ca 100644 --- a/src/repl.zig +++ b/src/repl.zig @@ -2512,3 +2512,159 @@ test "table writes made before commit are not visible to an independent Table ha try std.testing.expectEqualStrings("alice", row.get("name").?.text); } +test "table put/get/del outside a transaction autocommit immediately like plain Table" { + 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 widgets 7 sku=s:ABC qty=i:3\ntable get widgets 7\ntable del widgets 7\ntable get widgets 7\n", + ); + defer allocator.free(out); + try std.testing.expect(std.mem.startsWith(u8, out, "OK\n")); + try std.testing.expect(std.mem.indexOf(u8, out, "sku=ABC") != null); + try std.testing.expect(std.mem.indexOf(u8, out, "qty=3") != null); + try std.testing.expect(std.mem.endsWith(u8, out, "(nil)\n")); +} + +test "malformed field is rejected before staging, transaction stays usable" { + 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, + "begin\ntable put accounts 1 badfieldnoequals\ntable put accounts 1 name=bob\ncommit\ntable get accounts 1\n", + ); + defer allocator.free(out); + try std.testing.expect(std.mem.startsWith(u8, out, "OK\nerror: InvalidField\nOK\nOK\n")); + try std.testing.expect(std.mem.indexOf(u8, out, "name=bob") != null); +} + +test "a real external conflict surfaces error.Conflict on commit and applies nothing" { + const allocator = std.testing.allocator; + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + var db = try openTemp(&tmp, allocator); + defer db.close(); + try db.put("balance", "100"); + + var repl = Repl.init(allocator, &db); + defer repl.deinit(); + + var stream = std.io.fixedBufferStream("begin\nget balance\n"); + var out = std.ArrayList(u8).init(allocator); + defer out.deinit(); + try repl.runFromReader(stream.reader(), out.writer(), false); + + try db.put("balance", "external"); + + var stream2 = std.io.fixedBufferStream("put balance 150\ncommit\n"); + try repl.runFromReader(stream2.reader(), out.writer(), false); + try std.testing.expect(std.mem.endsWith(u8, out.items, "OK\nerror: Conflict\n")); + + const current = (try db.get(allocator, "balance")).?; + defer allocator.free(current); + try std.testing.expectEqualStrings("external", current); + try std.testing.expect(repl.inTransaction()); + + var stream3 = std.io.fixedBufferStream("rollback\n"); + try repl.runFromReader(stream3.reader(), out.writer(), false); + try std.testing.expect(!repl.inTransaction()); +} + +test "scan always reflects committed Db state, not an in-flight transaction's staged writes" { + 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, "put k1 v1\nbegin\nput k2 v2\nscan k\n"); + defer allocator.free(out); + try std.testing.expectEqualStrings("OK\nOK\nOK\nk1\tv1\n", out); +} + +test "abandoning an open transaction on deinit frees staged writes and pending TableTxn state" { + 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); + const out = try runScript(allocator, &repl, "begin\nput a 1\ntable put accounts 1 name=alice\n"); + defer allocator.free(out); + try std.testing.expectEqualStrings("OK\nOK\nOK\n", out); + repl.deinit(); + + try std.testing.expectEqual(@as(?[]u8, null), try db.get(allocator, "a")); + var table = try Table.open(&db, allocator, "accounts"); + defer table.deinit(); + try std.testing.expectEqual(@as(?Row, null), try table.get(.{ .text = "1" })); +} + +test "flush and compact run against the live Db and stats reflects 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, "put a 1\nflush\ncompact\nstats\n"); + defer allocator.free(out); + try std.testing.expect(std.mem.startsWith(u8, out, "OK\nOK\nOK\n")); + try std.testing.expect(out.len > "OK\nOK\nOK\n".len); +} + +test "help lists commands without touching db state" { + 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, "help\n"); + defer allocator.free(out); + try std.testing.expect(std.mem.indexOf(u8, out, "begin / commit / rollback") != null); +} + +test "prompt is written before each read only when requested" { + 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(); + + var stream = std.io.fixedBufferStream("begin\nput a 1\n"); + var out = std.ArrayList(u8).init(allocator); + defer out.deinit(); + try repl.runFromReader(stream.reader(), out.writer(), true); + try std.testing.expectEqualStrings("cobalt> OK\ncobalt(txn)> OK\ncobalt(txn)> ", out.items); +} +