feat: expand index

This commit is contained in:
dualfroz committed 2026-09-04 22:05:12 +02:00
1 parent 9f207fd036
commit 5c72201c45
1 file changed
+154
+154
View File
@@ -1423,3 +1423,157 @@ test "ForeignKeySet add remove save load round trip" {
try std.testing.expect(!third.has("customer_id"));
}
test "ForeignKeySet.load rejects a corrupted onDelete byte instead of accepting garbage" {
const allocator = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
const dbPath = try tmp.dir.realpathAlloc(allocator, ".");
defer allocator.free(dbPath);
var db = try Db.open(allocator, dbPath, .{});
defer db.close();
var set = try ForeignKeySet.load(allocator, &db, "orders");
defer set.deinit();
try set.add("customer_id", "customers", "__pk", .restrict);
try set.save(&db, "orders");
const key = try foreignKeyMetaKey(allocator, "orders");
defer allocator.free(key);
const raw = try db.get(allocator, key) orelse return error.TestUnexpectedResult;
defer allocator.free(raw);
// Flip the trailing onDelete byte to a value neither `restrict` (0)
// nor `cascade` (1) can ever legitimately be - same "malformed input
// is rejected, not silently coerced" contract every other decoder in
// this file already gives length-prefixed strings.
const corrupted = try allocator.dupe(u8, raw);
defer allocator.free(corrupted);
corrupted[corrupted.len - 1] = 0xFF;
try db.put(key, corrupted);
try std.testing.expectError(error.Malformed, ForeignKeySet.load(allocator, &db, "orders"));
}
test "ReferencedBySet add replaces an existing (childTable, childField) pair instead of duplicating it" {
const allocator = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
const dbPath = try tmp.dir.realpathAlloc(allocator, ".");
defer allocator.free(dbPath);
var db = try Db.open(allocator, dbPath, .{});
defer db.close();
var set = try ReferencedBySet.load(allocator, &db, "customers");
defer set.deinit();
try std.testing.expectEqual(@as(usize, 0), set.specs.items.len);
try set.add("orders", "customer_id", "__pk", .restrict);
try set.add("orders", "customer_id", "__pk", .restrict);
try std.testing.expectEqual(@as(usize, 1), set.specs.items.len);
try set.add("line_items", "customer_id", "__pk", .cascade);
try std.testing.expectEqual(@as(usize, 2), set.specs.items.len);
try set.save(&db, "customers");
var reloaded = try ReferencedBySet.load(allocator, &db, "customers");
defer reloaded.deinit();
try std.testing.expectEqual(@as(usize, 2), reloaded.specs.items.len);
for (reloaded.specs.items) |spec| {
if (std.mem.eql(u8, spec.childTable, "line_items")) {
try std.testing.expectEqual(OnDelete.cascade, spec.onDelete);
} else {
try std.testing.expectEqual(OnDelete.restrict, spec.onDelete);
}
}
// Re-adding the same pair under a different refField AND onDelete mode
// replaces both in place rather than accumulating a second, now-stale
// entry.
try reloaded.add("orders", "customer_id", "email", .cascade);
try std.testing.expectEqual(@as(usize, 2), reloaded.specs.items.len);
var sawOrders = false;
for (reloaded.specs.items) |spec| {
if (std.mem.eql(u8, spec.childTable, "orders")) {
sawOrders = true;
try std.testing.expectEqualStrings("email", spec.refField);
try std.testing.expectEqual(OnDelete.cascade, spec.onDelete);
}
}
try std.testing.expect(sawOrders);
try std.testing.expect(reloaded.remove("orders", "customer_id"));
try std.testing.expect(!reloaded.remove("orders", "customer_id"));
try reloaded.save(&db, "customers");
var third = try ReferencedBySet.load(allocator, &db, "customers");
defer third.deinit();
try std.testing.expectEqual(@as(usize, 1), third.specs.items.len);
try std.testing.expectEqualStrings("line_items", third.specs.items[0].childTable);
}
test "CheckConstraintSet add remove save load round trip" {
const allocator = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
const dbPath = try tmp.dir.realpathAlloc(allocator, ".");
defer allocator.free(dbPath);
var db = try Db.open(allocator, dbPath, .{});
defer db.close();
var set = try CheckConstraintSet.load(allocator, &db, "orders");
defer set.deinit();
try std.testing.expectEqual(@as(usize, 0), set.specs.items.len);
try set.add("qty_positive", "qty gt 0");
try set.add("qty_positive", "qty gt 100");
try std.testing.expectEqual(@as(usize, 1), set.specs.items.len);
try set.save(&db, "orders");
var reloaded = try CheckConstraintSet.load(allocator, &db, "orders");
defer reloaded.deinit();
try std.testing.expect(reloaded.has("qty_positive"));
try std.testing.expect(!reloaded.has("missing"));
const spec = reloaded.find("qty_positive").?;
try std.testing.expectEqualStrings("qty gt 0", spec.source);
try std.testing.expect(reloaded.remove("qty_positive"));
try std.testing.expect(!reloaded.remove("qty_positive"));
try reloaded.save(&db, "orders");
var third = try CheckConstraintSet.load(allocator, &db, "orders");
defer third.deinit();
try std.testing.expect(!third.has("qty_positive"));
}
test "ForeignKeySet, UniqueSet, and IndexSet on the same table never collide" {
const allocator = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
const dbPath = try tmp.dir.realpathAlloc(allocator, ".");
defer allocator.free(dbPath);
var db = try Db.open(allocator, dbPath, .{});
defer db.close();
var indexes = try IndexSet.load(allocator, &db, "orders");
defer indexes.deinit();
try indexes.add("customer_id");
try indexes.save(&db, "orders");
var unique = try UniqueSet.load(allocator, &db, "orders");
defer unique.deinit();
try unique.add("order_number");
try unique.save(&db, "orders");
var fkeys = try ForeignKeySet.load(allocator, &db, "orders");
defer fkeys.deinit();
try fkeys.add("customer_id", "customers", "__pk", .restrict);
try fkeys.save(&db, "orders");
var checks = try CheckConstraintSet.load(allocator, &db, "orders");
defer checks.deinit();
try checks.add("qty_positive", "qty gt 0");
try checks.save(&db, "orders");