feat: flesh out query
This commit is contained in:
1 parent
530759d7d3
commit
1a76c40f4d
1 file changed
+161
+161
@@ -3174,3 +3174,164 @@ test "deleting a parent row is restricted while a child still references it via
|
|||||||
var db = try openTemp(&tmp, allocator);
|
var db = try openTemp(&tmp, allocator);
|
||||||
defer db.close();
|
defer db.close();
|
||||||
|
|
||||||
|
var customers = try Table.open(&db, allocator, "customers");
|
||||||
|
defer customers.deinit();
|
||||||
|
try customers.put(.{ .int = 1 }, &.{.{ .name = "name", .value = .{ .text = "Ada" } }});
|
||||||
|
|
||||||
|
var orders = try Table.open(&db, allocator, "orders");
|
||||||
|
defer orders.deinit();
|
||||||
|
try orders.createForeignKey("customer_id", "customers", pkField, .restrict);
|
||||||
|
try orders.put(.{ .int = 100 }, &.{.{ .name = "customer_id", .value = .{ .int = 1 } }});
|
||||||
|
|
||||||
|
try std.testing.expectError(error.ForeignKeyInUse, customers.delete(.{ .int = 1 }));
|
||||||
|
// Restricted, not silently ignored: the row is still there afterwards.
|
||||||
|
var stillThere = (try customers.get(.{ .int = 1 })).?;
|
||||||
|
stillThere.deinit();
|
||||||
|
|
||||||
|
// Once the referencing child row is gone, the parent can be deleted.
|
||||||
|
try orders.delete(.{ .int = 100 });
|
||||||
|
try customers.delete(.{ .int = 1 });
|
||||||
|
try std.testing.expect((try customers.get(.{ .int = 1 })) == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "deleteWhere on a parent table stops at the first row a child still references" {
|
||||||
|
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 Table.open(&db, allocator, "customers");
|
||||||
|
defer customers.deinit();
|
||||||
|
try customers.put(.{ .int = 1 }, &.{.{ .name = "name", .value = .{ .text = "Ada" } }});
|
||||||
|
try customers.put(.{ .int = 2 }, &.{.{ .name = "name", .value = .{ .text = "Boris" } }});
|
||||||
|
|
||||||
|
var orders = try Table.open(&db, allocator, "orders");
|
||||||
|
defer orders.deinit();
|
||||||
|
try orders.createForeignKey("customer_id", "customers", pkField, .restrict);
|
||||||
|
try orders.put(.{ .int = 100 }, &.{.{ .name = "customer_id", .value = .{ .int = 2 } }});
|
||||||
|
|
||||||
|
try std.testing.expectError(error.ForeignKeyInUse, customers.deleteWhere(null));
|
||||||
|
// customer 1 had no children and was already removed by the time
|
||||||
|
// deleteWhere hit customer 2's violation - deleteWhere has no
|
||||||
|
// per-batch atomicity (see its own doc comment), so a partial effect
|
||||||
|
// here is expected, not a bug this test is trying to hide.
|
||||||
|
try std.testing.expect((try customers.get(.{ .int = 1 })) == null);
|
||||||
|
var stillThere = (try customers.get(.{ .int = 2 })).?;
|
||||||
|
stillThere.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
test "dropForeignKey lifts the delete restriction on the table it pointed at" {
|
||||||
|
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 Table.open(&db, allocator, "customers");
|
||||||
|
defer customers.deinit();
|
||||||
|
try customers.put(.{ .int = 1 }, &.{.{ .name = "name", .value = .{ .text = "Ada" } }});
|
||||||
|
|
||||||
|
var orders = try Table.open(&db, allocator, "orders");
|
||||||
|
defer orders.deinit();
|
||||||
|
try orders.createForeignKey("customer_id", "customers", pkField, .restrict);
|
||||||
|
try orders.put(.{ .int = 100 }, &.{.{ .name = "customer_id", .value = .{ .int = 1 } }});
|
||||||
|
try std.testing.expectError(error.ForeignKeyInUse, customers.delete(.{ .int = 1 }));
|
||||||
|
|
||||||
|
try orders.dropForeignKey("customer_id");
|
||||||
|
try customers.delete(.{ .int = 1 });
|
||||||
|
try std.testing.expect((try customers.get(.{ .int = 1 })) == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "delete restriction works through a UNIQUE non-pk foreign key target too" {
|
||||||
|
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 Table.open(&db, allocator, "products");
|
||||||
|
defer products.deinit();
|
||||||
|
try products.createUniqueIndex("sku");
|
||||||
|
try products.put(.{ .int = 1 }, &.{.{ .name = "sku", .value = .{ .text = "WIDGET-1" } }});
|
||||||
|
|
||||||
|
var lineItems = try Table.open(&db, allocator, "line_items");
|
||||||
|
defer lineItems.deinit();
|
||||||
|
try lineItems.createForeignKey("product_sku", "products", "sku", .restrict);
|
||||||
|
try lineItems.put(.{ .int = 1 }, &.{.{ .name = "product_sku", .value = .{ .text = "WIDGET-1" } }});
|
||||||
|
|
||||||
|
try std.testing.expectError(error.ForeignKeyInUse, products.delete(.{ .int = 1 }));
|
||||||
|
|
||||||
|
try lineItems.delete(.{ .int = 1 });
|
||||||
|
try products.delete(.{ .int = 1 });
|
||||||
|
try std.testing.expect((try products.get(.{ .int = 1 })) == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "a self-referencing foreign key restricts deleting a manager with reports, but not a childless leaf" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
var tmp = std.testing.tmpDir(.{});
|
||||||
|
defer tmp.cleanup();
|
||||||
|
var db = try openTemp(&tmp, allocator);
|
||||||
|
defer db.close();
|
||||||
|
|
||||||
|
var employees = try Table.open(&db, allocator, "employees");
|
||||||
|
defer employees.deinit();
|
||||||
|
try employees.put(.{ .int = 1 }, &.{.{ .name = "name", .value = .{ .text = "CEO" } }});
|
||||||
|
try employees.createForeignKey("manager_id", "employees", pkField, .restrict);
|
||||||
|
try employees.put(.{ .int = 2 }, &.{
|
||||||
|
.{ .name = "name", .value = .{ .text = "VP" } },
|
||||||
|
.{ .name = "manager_id", .value = .{ .int = 1 } },
|
||||||
|
});
|
||||||
|
|
||||||
|
try std.testing.expectError(error.ForeignKeyInUse, employees.delete(.{ .int = 1 }));
|
||||||
|
// The leaf (no reports) deletes cleanly - and does not restrict its
|
||||||
|
// own deletion against itself despite scanning the same table.
|
||||||
|
try employees.delete(.{ .int = 2 });
|
||||||
|
try employees.delete(.{ .int = 1 });
|
||||||
|
try std.testing.expect((try employees.get(.{ .int = 1 })) == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "foreign key constraint survives a table reopen" {
|
||||||
|
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 Table.open(&db, allocator, "customers");
|
||||||
|
defer customers.deinit();
|
||||||
|
var orders = try Table.open(&db, allocator, "orders");
|
||||||
|
defer orders.deinit();
|
||||||
|
try orders.createForeignKey("customer_id", "customers", pkField, .restrict);
|
||||||
|
}
|
||||||
|
|
||||||
|
var reopened = try Table.open(&db, allocator, "orders");
|
||||||
|
defer reopened.deinit();
|
||||||
|
try std.testing.expect(reopened.hasForeignKey("customer_id"));
|
||||||
|
try std.testing.expectError(error.ForeignKeyViolation, reopened.put(.{ .int = 1 }, &.{
|
||||||
|
.{ .name = "customer_id", .value = .{ .int = 7 } },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "createForeignKey rejects bad identifiers for field, refTable, and refField" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
var tmp = std.testing.tmpDir(.{});
|
||||||
|
defer tmp.cleanup();
|
||||||
|
var db = try openTemp(&tmp, allocator);
|
||||||
|
defer db.close();
|
||||||
|
|
||||||
|
var orders = try Table.open(&db, allocator, "orders");
|
||||||
|
defer orders.deinit();
|
||||||
|
try std.testing.expectError(error.InvalidIdentifier, orders.createForeignKey("bad\x00field", "customers", pkField, .restrict));
|
||||||
|
try std.testing.expectError(error.InvalidIdentifier, orders.createForeignKey("customer_id", "bad\x00table", pkField, .restrict));
|
||||||
|
try std.testing.expectError(error.InvalidIdentifier, orders.createForeignKey("customer_id", "customers", "bad\x00field", .restrict));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "a self-referencing foreign key enforces a tree structure against the table's own primary key" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
var tmp = std.testing.tmpDir(.{});
|
||||||
|
defer tmp.cleanup();
|
||||||
|
var db = try openTemp(&tmp, allocator);
|
||||||
|
defer db.close();
|
||||||
|
|
||||||
Reference in new issue
Block a user