feat: build out index

This commit is contained in:
dualfroz committed 2026-09-06 13:39:07 +02:00
1 parent 5c72201c45
commit 1ca072480d
1 file changed
+160
+160
View File
@@ -1577,3 +1577,163 @@ test "ForeignKeySet, UniqueSet, and IndexSet on the same table never collide" {
try checks.add("qty_positive", "qty gt 0"); try checks.add("qty_positive", "qty gt 0");
try checks.save(&db, "orders"); try checks.save(&db, "orders");
var reloadedIndexes = try IndexSet.load(allocator, &db, "orders");
defer reloadedIndexes.deinit();
var reloadedUnique = try UniqueSet.load(allocator, &db, "orders");
defer reloadedUnique.deinit();
var reloadedFkeys = try ForeignKeySet.load(allocator, &db, "orders");
defer reloadedFkeys.deinit();
var reloadedChecks = try CheckConstraintSet.load(allocator, &db, "orders");
defer reloadedChecks.deinit();
try std.testing.expect(reloadedIndexes.has("customer_id"));
try std.testing.expect(reloadedUnique.has("order_number"));
try std.testing.expect(!reloadedUnique.has("customer_id"));
try std.testing.expect(reloadedFkeys.has("customer_id"));
try std.testing.expect(!reloadedFkeys.has("order_number"));
try std.testing.expect(reloadedChecks.has("qty_positive"));
try std.testing.expect(!reloadedChecks.has("customer_id"));
}
test "addEntry scanEq removeEntry 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();
try addEntry(&db, allocator, "users", "city", .{ .text = "warsaw" }, .{ .int = 1 });
try addEntry(&db, allocator, "users", "city", .{ .text = "warsaw" }, .{ .int = 2 });
try addEntry(&db, allocator, "users", "city", .{ .text = "krakow" }, .{ .int = 3 });
const warsawPks = try scanEq(&db, allocator, "users", "city", .{ .text = "warsaw" });
defer {
for (warsawPks) |v| v.free(allocator);
allocator.free(warsawPks);
}
try std.testing.expectEqual(@as(usize, 2), warsawPks.len);
try removeEntry(&db, allocator, "users", "city", .{ .text = "warsaw" }, .{ .int = 1 });
const afterRemove = try scanEq(&db, allocator, "users", "city", .{ .text = "warsaw" });
defer {
for (afterRemove) |v| v.free(allocator);
allocator.free(afterRemove);
}
try std.testing.expectEqual(@as(usize, 1), afterRemove.len);
try std.testing.expectEqual(@as(i64, 2), afterRemove[0].int);
}
test "EqCursor streams the same pks as eager scanEq and can stop early" {
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 i: i64 = 0;
while (i < 20) : (i += 1) {
try addEntry(&db, allocator, "users", "city", .{ .text = "warsaw" }, .{ .int = i });
}
const eager = try scanEq(&db, allocator, "users", "city", .{ .text = "warsaw" });
defer {
for (eager) |v| v.free(allocator);
allocator.free(eager);
}
try std.testing.expectEqual(@as(usize, 20), eager.len);
// Pull only the first three through the cursor, then drop it: this must
// neither read nor decode the remaining seventeen entries, and the
// testing allocator would flag a leak if the partially-consumed
// RangeIterator's resources were not released on an early deinit.
var cursor = try scanEqCursor(&db, allocator, "users", "city", .{ .text = "warsaw" });
defer cursor.deinit();
var pulled = std.ArrayList(Value).init(allocator);
defer {
for (pulled.items) |v| v.free(allocator);
pulled.deinit();
}
var n: usize = 0;
while (n < 3) : (n += 1) {
const pk = (try cursor.next(allocator)).?;
try pulled.append(pk);
}
try std.testing.expectEqual(@as(usize, 3), pulled.items.len);
for (pulled.items, 0..) |v, idx2| {
try std.testing.expectEqual(eager[idx2].int, v.int);
}
}
test "RangeCursor streams the same pks as eager scanRange and can stop early" {
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 i: i64 = 0;
while (i < 30) : (i += 1) {
try addEntry(&db, allocator, "events", "score", .{ .int = i }, .{ .int = i });
}
var cursor = try scanRangeCursor(&db, allocator, "events", "score", .{ .value = .{ .int = 5 }, .inclusive = true }, null);
defer cursor.deinit();
const first = (try cursor.next(allocator)).?;
defer first.free(allocator);
const second = (try cursor.next(allocator)).?;
defer second.free(allocator);
try std.testing.expectEqual(@as(i64, 5), first.int);
try std.testing.expectEqual(@as(i64, 6), second.int);
// cursor and its underlying iterator are dropped by `defer` here without
// consuming the other 23 matches.
}
test "scanRange respects bounds and inclusivity" {
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 i: i64 = 0;
while (i < 10) : (i += 1) {
try addEntry(&db, allocator, "events", "score", .{ .int = i }, .{ .int = i });
}
const all = try scanRange(&db, allocator, "events", "score", null, null);
defer {
for (all) |v| v.free(allocator);
allocator.free(all);
}
try std.testing.expectEqual(@as(usize, 10), all.len);
const midInclusive = try scanRange(&db, allocator, "events", "score", .{ .value = .{ .int = 3 }, .inclusive = true }, .{ .value = .{ .int = 6 }, .inclusive = true });
defer {
for (midInclusive) |v| v.free(allocator);
allocator.free(midInclusive);
}
try std.testing.expectEqual(@as(usize, 4), midInclusive.len);
try std.testing.expectEqual(@as(i64, 3), midInclusive[0].int);
try std.testing.expectEqual(@as(i64, 6), midInclusive[midInclusive.len - 1].int);
const midExclusive = try scanRange(&db, allocator, "events", "score", .{ .value = .{ .int = 3 }, .inclusive = false }, .{ .value = .{ .int = 6 }, .inclusive = false });
defer {
for (midExclusive) |v| v.free(allocator);
allocator.free(midExclusive);
}
try std.testing.expectEqual(@as(usize, 2), midExclusive.len);
try std.testing.expectEqual(@as(i64, 4), midExclusive[0].int);
try std.testing.expectEqual(@as(i64, 5), midExclusive[1].int);