From dce10777088ac1ca667d05cef55d6e2aebf93f9a Mon Sep 17 00:00:00 2001 From: DualFroz Date: Thu, 10 Sep 2026 14:02:22 +0200 Subject: [PATCH] feat: fill in repl --- src/repl.zig | 157 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/src/repl.zig b/src/repl.zig index e9ae03e..2be3c68 100644 --- a/src/repl.zig +++ b/src/repl.zig @@ -2355,3 +2355,160 @@ test "unknown command and wrong arg counts report errors without crashing" { ); } +test "staged writes inside a transaction are invisible until commit, then durable" { + 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\nput bal 100\nget bal\ncommit\nget bal\n"); + defer allocator.free(out); + try std.testing.expectEqualStrings("OK\nOK\n100\nOK\n100\n", out); +} + +test "get mid-transaction steers the next put, applied atomically on commit" { + 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("bal", "100"); + + var repl = Repl.init(allocator, &db); + defer repl.deinit(); + + const out = try runScript(allocator, &repl, "begin\nget bal\nput bal 150\ncommit\nget bal\n"); + defer allocator.free(out); + try std.testing.expectEqualStrings("OK\n100\nOK\nOK\n150\n", out); + + const stored = (try db.get(allocator, "bal")).?; + defer allocator.free(stored); + try std.testing.expectEqualStrings("150", stored); +} + +test "rollback discards staged writes entirely" { + 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\nput x 1\nrollback\nget x\n"); + defer allocator.free(out); + try std.testing.expectEqualStrings("OK\nOK\nOK\n(nil)\n", out); + try std.testing.expectEqual(@as(?[]u8, null), try db.get(allocator, "x")); +} + +test "commit/rollback without an active transaction report NoActiveTransaction" { + 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, "commit\nrollback\nput a 1\nget a\n"); + defer allocator.free(out); + try std.testing.expectEqualStrings( + "error: NoActiveTransaction\nerror: NoActiveTransaction\nOK\n1\n", + out, + ); +} + +test "double begin is rejected and leaves the first transaction 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\nput a 1\nbegin\ncommit\nget a\n"); + defer allocator.free(out); + try std.testing.expectEqualStrings("OK\nOK\nerror: TransactionAlreadyActive\nOK\n1\n", out); +} + +test "table put/get/del inside a transaction stage through TableTxn atomically across two tables" { + 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\n" ++ + "table put accounts 1 name=alice balance=i:100\n" ++ + "table put ledger 1 note=opening\n" ++ + "table get accounts 1\n" ++ + "commit\n" ++ + "table get accounts 1\n" ++ + "table get ledger 1\n", + ); + defer allocator.free(out); + + var lines = std.mem.splitScalar(u8, std.mem.trimRight(u8, out, "\n"), '\n'); + try std.testing.expectEqualStrings("OK", lines.next().?); + try std.testing.expectEqualStrings("OK", lines.next().?); + try std.testing.expectEqualStrings("OK", lines.next().?); + const preCommitGet = lines.next().?; + try std.testing.expect(std.mem.indexOf(u8, preCommitGet, "name=alice") != null); + try std.testing.expectEqualStrings("OK", lines.next().?); + const postAccounts = lines.next().?; + try std.testing.expect(std.mem.indexOf(u8, postAccounts, "balance=100") != null); + const postLedger = lines.next().?; + try std.testing.expect(std.mem.indexOf(u8, postLedger, "note=opening") != null); + try std.testing.expectEqual(@as(?[]const u8, null), lines.next()); + + var directTable = try Table.open(&db, allocator, "accounts"); + defer directTable.deinit(); + var row = (try directTable.get(.{ .text = "1" })).?; + defer row.deinit(); + try std.testing.expectEqual(@as(i64, 100), row.get("balance").?.int); +} + +test "table writes made before commit are not visible to an independent Table handle" { + 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\ntable put accounts 1 name=alice\n"); + var out = std.ArrayList(u8).init(allocator); + defer out.deinit(); + try repl.runFromReader(stream.reader(), out.writer(), false); + try std.testing.expectEqualStrings("OK\nOK\n", out.items); + + var outsider = try Table.open(&db, allocator, "accounts"); + defer outsider.deinit(); + try std.testing.expectEqual(@as(?Row, null), try outsider.get(.{ .text = "1" })); + + try repl.txn.?.commit(); + repl.discardTxnState(); + + var again = try Table.open(&db, allocator, "accounts"); + defer again.deinit(); + var row = (try again.get(.{ .text = "1" })).?; + defer row.deinit(); + try std.testing.expectEqualStrings("alice", row.get("name").?.text); +} +