feat: extend txn_table
This commit is contained in:
1 parent
ad38d08079
commit
71737debca
1 file changed
+162
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
Reference in new issue
Block a user