feat: flesh out query

This commit is contained in:
dualfroz committed 2026-09-09 16:50:31 +02:00
1 parent 530759d7d3
commit 1a76c40f4d
1 file changed
+161
+161
View File
@@ -3174,3 +3174,164 @@ test "deleting a parent row is restricted while a child still references it via
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 }));
// 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();