feat: fill in query

This commit is contained in:
dualfroz committed 2026-09-09 17:47:51 +02:00
1 parent 2a8227b64d
commit bda37d1ecf
1 file changed
+159
+159
View File
@@ -3498,3 +3498,162 @@ test "a self-referencing foreign key restricts renaming a UNIQUE field still tar
try std.testing.expectEqualStrings("B1-NEW", renamed.get("badge").?.text); try std.testing.expectEqualStrings("B1-NEW", renamed.get("badge").?.text);
} }
test "createCheckConstraint rejects a row that violates it and accepts one that satisfies it" {
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 orders.createCheckConstraint("qty_positive", "qty gt i:0");
try std.testing.expect(orders.hasCheckConstraint("qty_positive"));
try orders.put(.{ .int = 1 }, &.{.{ .name = "qty", .value = .{ .int = 5 } }});
try std.testing.expectError(error.CheckViolation, orders.put(.{ .int = 2 }, &.{
.{ .name = "qty", .value = .{ .int = 0 } },
}));
try std.testing.expectError(error.CheckViolation, orders.put(.{ .int = 3 }, &.{
.{ .name = "qty", .value = .{ .int = -1 } },
}));
}
test "createCheckConstraint refuses to install over existing rows that already fail it" {
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 orders.put(.{ .int = 1 }, &.{.{ .name = "qty", .value = .{ .int = -5 } }});
try std.testing.expectError(error.CheckViolation, orders.createCheckConstraint("qty_positive", "qty gt i:0"));
try std.testing.expect(!orders.hasCheckConstraint("qty_positive"));
}
test "createCheckConstraint rejects a malformed expression before persisting anything" {
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.InvalidCheckExpression, orders.createCheckConstraint("bad", "qty gt"));
try std.testing.expect(!orders.hasCheckConstraint("bad"));
}
test "dropCheckConstraint lifts the constraint, and a check 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 orders = try Table.open(&db, allocator, "orders");
defer orders.deinit();
try orders.createCheckConstraint("qty_positive", "qty gt i:0");
}
var reopened = try Table.open(&db, allocator, "orders");
defer reopened.deinit();
try std.testing.expect(reopened.hasCheckConstraint("qty_positive"));
try std.testing.expectError(error.CheckViolation, reopened.put(.{ .int = 1 }, &.{
.{ .name = "qty", .value = .{ .int = 0 } },
}));
try reopened.dropCheckConstraint("qty_positive");
try std.testing.expect(!reopened.hasCheckConstraint("qty_positive"));
try reopened.put(.{ .int = 1 }, &.{.{ .name = "qty", .value = .{ .int = 0 } }});
}
test "a row missing the checked field entirely is exempt (like is_null), but present-and-none is still checked" {
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 orders.createCheckConstraint("qty_known", "qty is_not_null");
try std.testing.expectError(error.CheckViolation, orders.put(.{ .int = 1 }, &.{}));
}
test "multiple CHECK constraints on the same table all must pass" {
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 orders.createCheckConstraint("qty_positive", "qty gt i:0");
try orders.createCheckConstraint("qty_capped", "qty lt i:1000");
try orders.put(.{ .int = 1 }, &.{.{ .name = "qty", .value = .{ .int = 500 } }});
try std.testing.expectError(error.CheckViolation, orders.put(.{ .int = 2 }, &.{
.{ .name = "qty", .value = .{ .int = 1000 } },
}));
}
test "ON DELETE CASCADE removes referencing rows instead of restricting the parent delete" {
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, .cascade);
try orders.put(.{ .int = 100 }, &.{.{ .name = "customer_id", .value = .{ .int = 1 } }});
try orders.put(.{ .int = 101 }, &.{.{ .name = "customer_id", .value = .{ .int = 1 } }});
try orders.put(.{ .int = 200 }, &.{.{ .name = "customer_id", .value = .{ .int = 2 } }});
try customers.delete(.{ .int = 1 });
try std.testing.expect((try customers.get(.{ .int = 1 })) == null);
try std.testing.expect((try orders.get(.{ .int = 100 })) == null);
try std.testing.expect((try orders.get(.{ .int = 101 })) == null);
// Untouched: customer 2 and its own order were never in the cascade's
// path.
var otherCustomer = (try customers.get(.{ .int = 2 })).?;
otherCustomer.deinit();
var otherOrder = (try orders.get(.{ .int = 200 })).?;
otherOrder.deinit();
}
test "ON DELETE CASCADE keeps secondary indexes on the cascaded rows in sync" {
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.createIndex("status");
try orders.createForeignKey("customer_id", "customers", pkField, .cascade);
try orders.put(.{ .int = 100 }, &.{
.{ .name = "customer_id", .value = .{ .int = 1 } },
.{ .name = "status", .value = .{ .text = "open" } },
});