feat: grow query

This commit is contained in:
dualfroz committed 2026-09-09 16:14:58 +02:00
1 parent 5b47228e4e
commit 530759d7d3
1 file changed
+157
+157
View File
@@ -3017,3 +3017,160 @@ test "unique constraint survives a table reopen (persisted across process restar
try table.put(.{ .int = 1 }, &.{.{ .name = "email", .value = .{ .text = "a@x.com" } }});
}
var reopened = try Table.open(&db, allocator, "users");
defer reopened.deinit();
try std.testing.expect(reopened.hasUniqueIndex("email"));
try std.testing.expectError(error.UniqueViolation, reopened.put(.{ .int = 2 }, &.{
.{ .name = "email", .value = .{ .text = "a@x.com" } },
}));
}
test "createUniqueIndex rejects a bad field identifier" {
const allocator = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
var db = try openTemp(&tmp, allocator);
defer db.close();
var table = try Table.open(&db, allocator, "users");
defer table.deinit();
try std.testing.expectError(error.InvalidIdentifier, table.createUniqueIndex("bad\x00field"));
}
test "rows missing the unique field entirely never collide with each other" {
const allocator = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
var db = try openTemp(&tmp, allocator);
defer db.close();
var table = try Table.open(&db, allocator, "users");
defer table.deinit();
try table.put(.{ .int = 1 }, &.{.{ .name = "age", .value = .{ .int = 20 } }});
try table.put(.{ .int = 2 }, &.{.{ .name = "age", .value = .{ .int = 21 } }});
try table.createUniqueIndex("email");
try table.put(.{ .int = 3 }, &.{.{ .name = "age", .value = .{ .int = 22 } }});
const rows = try table.scanAll();
defer freeRows(allocator, rows);
try std.testing.expectEqual(@as(usize, 3), rows.len);
}
test "createForeignKey against the primary key rejects a dangling reference and accepts a real one" {
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 std.testing.expect(orders.hasForeignKey("customer_id"));
try orders.put(.{ .int = 100 }, &.{.{ .name = "customer_id", .value = .{ .int = 1 } }});
try std.testing.expectError(error.ForeignKeyViolation, orders.put(.{ .int = 101 }, &.{
.{ .name = "customer_id", .value = .{ .int = 999 } },
}));
}
test "createForeignKey against a UNIQUE non-pk field works, and refuses a target with no unique backing" {
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.put(.{ .int = 1 }, &.{.{ .name = "sku", .value = .{ .text = "SKU-1" } }});
var lineItems = try Table.open(&db, allocator, "line_items");
defer lineItems.deinit();
// "sku" has no unique constraint on products yet.
try std.testing.expectError(error.ForeignKeyRequiresUniqueTarget, lineItems.createForeignKey("product_sku", "products", "sku", .restrict));
try products.createUniqueIndex("sku");
try lineItems.createForeignKey("product_sku", "products", "sku", .restrict);
try lineItems.put(.{ .int = 1 }, &.{.{ .name = "product_sku", .value = .{ .text = "SKU-1" } }});
try std.testing.expectError(error.ForeignKeyViolation, lineItems.put(.{ .int = 2 }, &.{
.{ .name = "product_sku", .value = .{ .text = "NOPE" } },
}));
}
test "createForeignKey refuses to install over a child row that already dangles" {
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.put(.{ .int = 1 }, &.{.{ .name = "customer_id", .value = .{ .int = 42 } }});
try std.testing.expectError(error.ForeignKeyViolation, orders.createForeignKey("customer_id", "customers", pkField, .restrict));
try std.testing.expect(!orders.hasForeignKey("customer_id"));
}
test "a row with no value for the foreign key field is exempt from the constraint" {
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);
// No "customer_id" field at all - a nullable foreign key, not a violation.
try orders.put(.{ .int = 1 }, &.{.{ .name = "note", .value = .{ .text = "guest checkout" } }});
var row = (try orders.get(.{ .int = 1 })).?;
defer row.deinit();
try std.testing.expect(row.get("customer_id") == null);
}
test "dropForeignKey lifts the constraint" {
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);
try std.testing.expectError(error.ForeignKeyViolation, orders.put(.{ .int = 1 }, &.{
.{ .name = "customer_id", .value = .{ .int = 42 } },
}));
try orders.dropForeignKey("customer_id");
try std.testing.expect(!orders.hasForeignKey("customer_id"));
try orders.put(.{ .int = 1 }, &.{.{ .name = "customer_id", .value = .{ .int = 42 } }});
}
test "deleting a parent row is restricted while a child still references it via the primary key" {
const allocator = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
var db = try openTemp(&tmp, allocator);
defer db.close();