feat: extend txn_table

This commit is contained in:
dualfroz committed 2026-09-12 22:14:02 +02:00
1 parent ad38d08079
commit 71737debca
1 file changed
+162
+162
View File
@@ -470,3 +470,165 @@ test "TableTxn delete removes row and index entries only on commit" {
stillThere.deinit(); stillThere.deinit();
} }
try txn.commit();
var plain = try querymod.Table.open(&db, allocator, "users");
defer plain.deinit();
try std.testing.expect((try plain.get(.{ .int = 1 })) == null);
const warsaw = try plain.select(.{ .eq = .{ .field = "city", .value = .{ .text = "warsaw" } } });
defer querymod.freeRows(allocator, warsaw);
try std.testing.expectEqual(@as(usize, 0), warsaw.len);
}
test "TableTxn rejects invalid table identifier" {
const allocator = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
var db = try openTemp(&tmp, allocator);
defer db.close();
var txn = Transaction.begin(&db);
defer txn.deinit();
try std.testing.expectError(error.InvalidIdentifier, TableTxn.open(&txn, allocator, ""));
}
test "TableTxn read-your-writes sees staged rows before commit" {
const allocator = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
var db = try openTemp(&tmp, allocator);
defer db.close();
var txn = Transaction.begin(&db);
defer txn.deinit();
var t = try TableTxn.open(&txn, allocator, "users");
defer t.deinit();
try t.put(.{ .int = 1 }, &.{.{ .name = "name", .value = .{ .text = "ada" } }});
var seen = (try t.get(.{ .int = 1 })).?;
defer seen.deinit();
try std.testing.expectEqualStrings("ada", seen.get("name").?.text);
try t.delete(.{ .int = 1 });
try std.testing.expect((try t.get(.{ .int = 1 })) == null);
}
test "TableTxn put enforces UNIQUE constraint against committed data" {
const allocator = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
var db = try openTemp(&tmp, allocator);
defer db.close();
{
var setup = try querymod.Table.open(&db, allocator, "users");
defer setup.deinit();
try setup.createUniqueIndex("email");
try setup.put(.{ .int = 1 }, &.{.{ .name = "email", .value = .{ .text = "a@example.com" } }});
}
var txn = Transaction.begin(&db);
defer txn.deinit();
var t = try TableTxn.open(&txn, allocator, "users");
defer t.deinit();
try std.testing.expect(t.hasUniqueIndex("email"));
try std.testing.expectError(error.UniqueViolation, t.put(.{ .int = 2 }, &.{
.{ .name = "email", .value = .{ .text = "a@example.com" } },
}));
// The rejected put staged nothing: committing now applies zero rows.
try txn.commit();
var plain = try querymod.Table.open(&db, allocator, "users");
defer plain.deinit();
try std.testing.expect((try plain.get(.{ .int = 2 })) == null);
}
test "TableTxn put UNIQUE check exempts a row keeping its own previous value" {
const allocator = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
var db = try openTemp(&tmp, allocator);
defer db.close();
{
var setup = try querymod.Table.open(&db, allocator, "users");
defer setup.deinit();
try setup.createUniqueIndex("email");
try setup.put(.{ .int = 1 }, &.{.{ .name = "email", .value = .{ .text = "a@example.com" } }});
}
var txn = Transaction.begin(&db);
defer txn.deinit();
var t = try TableTxn.open(&txn, allocator, "users");
defer t.deinit();
// Updating an unrelated field on row 1 keeps its own email unchanged -
// must not spuriously conflict with itself.
try t.put(.{ .int = 1 }, &.{
.{ .name = "email", .value = .{ .text = "a@example.com" } },
.{ .name = "name", .value = .{ .text = "ada" } },
});
try txn.commit();
var plain = try querymod.Table.open(&db, allocator, "users");
defer plain.deinit();
var row = (try plain.get(.{ .int = 1 })).?;
defer row.deinit();
try std.testing.expectEqualStrings("ada", row.get("name").?.text);
}
test "TableTxn put enforces FOREIGN KEY against committed data and rejects a dangling reference" {
const allocator = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
var db = try openTemp(&tmp, allocator);
defer db.close();
{
var customers = try querymod.Table.open(&db, allocator, "customers");
defer customers.deinit();
try customers.put(.{ .int = 1 }, &.{});
var orders = try querymod.Table.open(&db, allocator, "orders");
defer orders.deinit();
try orders.createForeignKey("customer_id", "customers", pkField, .restrict);
}
var txn = Transaction.begin(&db);
defer txn.deinit();
var t = try TableTxn.open(&txn, allocator, "orders");
defer t.deinit();
try std.testing.expect(t.hasForeignKey("customer_id"));
// customer 1 exists (committed) -> allowed.
try t.put(.{ .int = 100 }, &.{.{ .name = "customer_id", .value = .{ .int = 1 } }});
// customer 999 does not exist anywhere -> rejected.
try std.testing.expectError(error.ForeignKeyViolation, t.put(.{ .int = 101 }, &.{
.{ .name = "customer_id", .value = .{ .int = 999 } },
}));
try txn.commit();
var plain = try querymod.Table.open(&db, allocator, "orders");
defer plain.deinit();
var row = (try plain.get(.{ .int = 100 })).?;
defer row.deinit();
try std.testing.expect((try plain.get(.{ .int = 101 })) == null);
}
test "TableTxn put FOREIGN KEY on pkField sees a parent staged earlier in the same uncommitted transaction" {
const allocator = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
var db = try openTemp(&tmp, allocator);
defer db.close();
{
// Self-referencing table (an org chart: manager_id -> employees.pk),
// installed while empty so no existing-row check gets in the way.
var employees = try querymod.Table.open(&db, allocator, "employees");
defer employees.deinit();
try employees.createForeignKey("manager_id", "employees", pkField, .restrict);
}