feat: extend index

This commit is contained in:
dualfroz committed 2026-09-04 21:37:58 +02:00
1 parent 910afabd97
commit 9f207fd036
1 file changed
+155
+155
View File
@@ -1268,3 +1268,158 @@ test "rowKey and indexEntryKey stay within their own namespace" {
try std.testing.expectEqual(@as(u8, 'I'), ik[0]);
}
test "prefixUpperBound bounds a prefix scan" {
const allocator = std.testing.allocator;
const upper = (try prefixUpperBound(allocator, "abc")).?;
defer allocator.free(upper);
try std.testing.expect(std.mem.order(u8, "abc", upper) == .lt);
try std.testing.expect(std.mem.order(u8, "abd", upper) != .lt);
}
test "IndexSet 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 IndexSet.load(allocator, &db, "users");
defer set.deinit();
try std.testing.expectEqual(@as(usize, 0), set.fields.items.len);
try set.add("email");
try set.add("age");
try set.add("email");
try std.testing.expectEqual(@as(usize, 2), set.fields.items.len);
try set.save(&db, "users");
var reloaded = try IndexSet.load(allocator, &db, "users");
defer reloaded.deinit();
try std.testing.expect(reloaded.has("email"));
try std.testing.expect(reloaded.has("age"));
try std.testing.expect(!reloaded.has("missing"));
try std.testing.expect(reloaded.remove("email"));
try std.testing.expect(!reloaded.remove("email"));
try reloaded.save(&db, "users");
var third = try IndexSet.load(allocator, &db, "users");
defer third.deinit();
try std.testing.expect(!third.has("email"));
try std.testing.expect(third.has("age"));
}
test "UniqueSet 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 UniqueSet.load(allocator, &db, "users");
defer set.deinit();
try std.testing.expectEqual(@as(usize, 0), set.fields.items.len);
try set.add("email");
try set.add("ssn");
try set.add("email");
try std.testing.expectEqual(@as(usize, 2), set.fields.items.len);
try set.save(&db, "users");
var reloaded = try UniqueSet.load(allocator, &db, "users");
defer reloaded.deinit();
try std.testing.expect(reloaded.has("email"));
try std.testing.expect(reloaded.has("ssn"));
try std.testing.expect(!reloaded.has("missing"));
try std.testing.expect(reloaded.remove("email"));
try std.testing.expect(!reloaded.remove("email"));
try reloaded.save(&db, "users");
var third = try UniqueSet.load(allocator, &db, "users");
defer third.deinit();
try std.testing.expect(!third.has("email"));
try std.testing.expect(third.has("ssn"));
}
test "UniqueSet and IndexSet on the same table never collide, each keeps its own membership" {
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, "users");
defer indexes.deinit();
try indexes.add("email");
try indexes.add("city");
try indexes.save(&db, "users");
var unique = try UniqueSet.load(allocator, &db, "users");
defer unique.deinit();
try unique.add("email");
try unique.save(&db, "users");
// "city" is indexed but not unique, "email" is both - the two sets
// must agree on "email" and disagree on "city" after an independent
// reload of each, proving neither save() overwrote the other's key.
var reloadedIndexes = try IndexSet.load(allocator, &db, "users");
defer reloadedIndexes.deinit();
var reloadedUnique = try UniqueSet.load(allocator, &db, "users");
defer reloadedUnique.deinit();
try std.testing.expect(reloadedIndexes.has("email"));
try std.testing.expect(reloadedIndexes.has("city"));
try std.testing.expect(reloadedUnique.has("email"));
try std.testing.expect(!reloadedUnique.has("city"));
}
test "ForeignKeySet 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 ForeignKeySet.load(allocator, &db, "orders");
defer set.deinit();
try std.testing.expectEqual(@as(usize, 0), set.specs.items.len);
try set.add("customer_id", "customers", "__pk", .cascade);
try set.add("customer_id", "customers", "some_other_field", .restrict);
try std.testing.expectEqual(@as(usize, 1), set.specs.items.len);
try set.save(&db, "orders");
var reloaded = try ForeignKeySet.load(allocator, &db, "orders");
defer reloaded.deinit();
try std.testing.expect(reloaded.has("customer_id"));
try std.testing.expect(!reloaded.has("missing"));
const spec = reloaded.find("customer_id").?;
try std.testing.expectEqualStrings("customers", spec.refTable);
try std.testing.expectEqualStrings("__pk", spec.refField);
// First `add` wins (mirrors `has` guard above) - `onDelete` survives the
// save/load round trip as the mode from that first call, not the
// no-op second one.
try std.testing.expectEqual(OnDelete.cascade, spec.onDelete);
try std.testing.expect(reloaded.remove("customer_id"));
try std.testing.expect(!reloaded.remove("customer_id"));
try reloaded.save(&db, "orders");
var third = try ForeignKeySet.load(allocator, &db, "orders");
defer third.deinit();
try std.testing.expect(!third.has("customer_id"));
}