feat: expand txn_table
This commit is contained in:
1 parent
71737debca
commit
8cfd0fb7aa
1 file changed
+157
@@ -632,3 +632,160 @@ test "TableTxn put FOREIGN KEY on pkField sees a parent staged earlier in the sa
|
||||
try employees.createForeignKey("manager_id", "employees", pkField, .restrict);
|
||||
}
|
||||
|
||||
var txn = Transaction.begin(&db);
|
||||
defer txn.deinit();
|
||||
var t = try TableTxn.open(&txn, allocator, "employees");
|
||||
defer t.deinit();
|
||||
|
||||
// Neither row is committed yet. The manager (pk=1) is only staged in
|
||||
// this same transaction's write set, but the FK check on the report
|
||||
// (pk=2) must see it via `Transaction.get`'s read-your-writes, not
|
||||
// just via the underlying Db.
|
||||
try t.put(.{ .int = 1 }, &.{});
|
||||
try t.put(.{ .int = 2 }, &.{.{ .name = "manager_id", .value = .{ .int = 1 } }});
|
||||
|
||||
try txn.commit();
|
||||
|
||||
var plain = try querymod.Table.open(&db, allocator, "employees");
|
||||
defer plain.deinit();
|
||||
var report = (try plain.get(.{ .int = 2 })).?;
|
||||
defer report.deinit();
|
||||
try std.testing.expectEqual(@as(i64, 1), report.get("manager_id").?.int);
|
||||
}
|
||||
|
||||
test "TableTxn put FOREIGN KEY on a non-pk unique field sees a target 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();
|
||||
|
||||
{
|
||||
var products = try querymod.Table.open(&db, allocator, "products");
|
||||
defer products.deinit();
|
||||
try products.createUniqueIndex("sku");
|
||||
|
||||
var lineItems = try querymod.Table.open(&db, allocator, "line_items");
|
||||
defer lineItems.deinit();
|
||||
try lineItems.createForeignKey("product_sku", "products", "sku", .restrict);
|
||||
}
|
||||
|
||||
var txn = Transaction.begin(&db);
|
||||
defer txn.deinit();
|
||||
|
||||
// Stage a brand-new product in the same transaction as the line item
|
||||
// that references it. `foreignKeyTargetExists`'s non-pk branch now
|
||||
// goes through `Transaction.scanPrefix`, which sees the product's
|
||||
// staged index-entry write even though neither row is committed yet -
|
||||
// same read-your-writes guarantee the pkField branch above already had.
|
||||
var products = try TableTxn.open(&txn, allocator, "products");
|
||||
defer products.deinit();
|
||||
try products.put(.{ .int = 1 }, &.{.{ .name = "sku", .value = .{ .text = "WIDGET-1" } }});
|
||||
|
||||
var lineItems = try TableTxn.open(&txn, allocator, "line_items");
|
||||
defer lineItems.deinit();
|
||||
try lineItems.put(.{ .int = 1 }, &.{
|
||||
.{ .name = "product_sku", .value = .{ .text = "WIDGET-1" } },
|
||||
});
|
||||
|
||||
// A dangling reference in the same transaction is still rejected.
|
||||
try std.testing.expectError(error.ForeignKeyViolation, lineItems.put(.{ .int = 2 }, &.{
|
||||
.{ .name = "product_sku", .value = .{ .text = "NO-SUCH-SKU" } },
|
||||
}));
|
||||
|
||||
try txn.commit();
|
||||
|
||||
var plain = try querymod.Table.open(&db, allocator, "line_items");
|
||||
defer plain.deinit();
|
||||
var row = (try plain.get(.{ .int = 1 })).?;
|
||||
defer row.deinit();
|
||||
try std.testing.expectEqualStrings("WIDGET-1", row.get("product_sku").?.text);
|
||||
}
|
||||
|
||||
test "TableTxn put UNIQUE constraint catches a conflict 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();
|
||||
|
||||
{
|
||||
var setup = try querymod.Table.open(&db, allocator, "users");
|
||||
defer setup.deinit();
|
||||
try setup.createUniqueIndex("email");
|
||||
}
|
||||
|
||||
var txn = Transaction.begin(&db);
|
||||
defer txn.deinit();
|
||||
var t = try TableTxn.open(&txn, allocator, "users");
|
||||
defer t.deinit();
|
||||
|
||||
// First row claims the email; nothing committed yet.
|
||||
try t.put(.{ .int = 1 }, &.{.{ .name = "email", .value = .{ .text = "a@example.com" } }});
|
||||
|
||||
// A second, different row in the SAME still-open transaction tries to
|
||||
// claim the same email. Before `Transaction.scanPrefix`, the UNIQUE
|
||||
// check ran `idx.scanEq` straight against `Db` and never saw the first
|
||||
// row's staged index entry, so this put would have wrongly succeeded
|
||||
// and only failed later as a generic OCC conflict at commit (or not at
|
||||
// all, since the two rows touch disjoint keys and would not conflict).
|
||||
try std.testing.expectError(error.UniqueViolation, t.put(.{ .int = 2 }, &.{
|
||||
.{ .name = "email", .value = .{ .text = "a@example.com" } },
|
||||
}));
|
||||
|
||||
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);
|
||||
var row1 = (try plain.get(.{ .int = 1 })).?;
|
||||
defer row1.deinit();
|
||||
try std.testing.expectEqualStrings("a@example.com", row1.get("email").?.text);
|
||||
}
|
||||
|
||||
test "TableTxn put enforces a CHECK constraint installed through query.zig's Table" {
|
||||
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, "orders");
|
||||
defer setup.deinit();
|
||||
try setup.createCheckConstraint("qty_positive", "qty gt i:0");
|
||||
}
|
||||
|
||||
var txn = Transaction.begin(&db);
|
||||
defer txn.deinit();
|
||||
var t = try TableTxn.open(&txn, allocator, "orders");
|
||||
defer t.deinit();
|
||||
try std.testing.expect(t.hasCheckConstraint("qty_positive"));
|
||||
|
||||
try t.put(.{ .int = 1 }, &.{.{ .name = "qty", .value = .{ .int = 5 } }});
|
||||
try std.testing.expectError(error.CheckViolation, t.put(.{ .int = 2 }, &.{
|
||||
.{ .name = "qty", .value = .{ .int = 0 } },
|
||||
}));
|
||||
|
||||
try txn.commit();
|
||||
|
||||
var plain = try querymod.Table.open(&db, allocator, "orders");
|
||||
defer plain.deinit();
|
||||
var row1 = (try plain.get(.{ .int = 1 })).?;
|
||||
defer row1.deinit();
|
||||
try std.testing.expect((try plain.get(.{ .int = 2 })) == null);
|
||||
}
|
||||
|
||||
test "TableTxn `txn <script>` shape: a CHECK violation on the second put leaves the whole transaction uncommitted" {
|
||||
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, "orders");
|
||||
defer setup.deinit();
|
||||
try setup.createCheckConstraint("qty_positive", "qty gt i:0");
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user