feat: build out query

This commit is contained in:
dualfroz committed 2026-09-09 15:02:01 +02:00
1 parent a012d94448
commit 5b47228e4e
1 file changed
+161
+161
View File
@@ -2856,3 +2856,164 @@ test "deleteWhere removes stale composite index entries across matched equality
try std.testing.expectEqual(@as(usize, 1), rows.len); try std.testing.expectEqual(@as(usize, 1), rows.len);
try std.testing.expectEqual(exp.pk, rows[0].get(pkField).?.int); try std.testing.expectEqual(exp.pk, rows[0].get(pkField).?.int);
const constTable: *const Table = &table;
var plan = try constTable.explain(allocator, .{ .and_ = &survivorPreds });
defer plan.deinit(allocator);
try std.testing.expectEqual(PlanKind.composite_eq, plan.kind);
}
}
test "createUniqueIndex backfills the plain index and rejects a colliding insert" {
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 = "email", .value = .{ .text = "a@x.com" } }});
try table.createUniqueIndex("email");
try std.testing.expect(table.hasUniqueIndex("email"));
try std.testing.expect(table.hasIndex("email"));
try std.testing.expectError(error.UniqueViolation, table.put(.{ .int = 2 }, &.{
.{ .name = "email", .value = .{ .text = "a@x.com" } },
}));
try table.put(.{ .int = 2 }, &.{.{ .name = "email", .value = .{ .text = "b@x.com" } }});
const rows = try table.scanAll();
defer freeRows(allocator, rows);
try std.testing.expectEqual(@as(usize, 2), rows.len);
}
test "createUniqueIndex refuses to install over data that already has a duplicate" {
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 = "email", .value = .{ .text = "dup@x.com" } }});
try table.put(.{ .int = 2 }, &.{.{ .name = "email", .value = .{ .text = "dup@x.com" } }});
try std.testing.expectError(error.UniqueViolation, table.createUniqueIndex("email"));
try std.testing.expect(!table.hasUniqueIndex("email"));
// Refused install must not have left a half-installed plain index either.
try std.testing.expect(!table.hasIndex("email"));
}
test "createUniqueIndex over an already-indexed field reuses the index instead of duplicating entries" {
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.createIndex("email");
try table.put(.{ .int = 1 }, &.{.{ .name = "email", .value = .{ .text = "a@x.com" } }});
try table.createUniqueIndex("email");
try std.testing.expect(table.hasUniqueIndex("email"));
try std.testing.expectError(error.UniqueViolation, table.put(.{ .int = 2 }, &.{
.{ .name = "email", .value = .{ .text = "a@x.com" } },
}));
const preds = Predicate{ .eq = .{ .field = "email", .value = .{ .text = "a@x.com" } } };
const rows = try table.select(preds);
defer freeRows(allocator, rows);
try std.testing.expectEqual(@as(usize, 1), rows.len);
}
test "unique constraint does not block updating a row's own value or an unrelated field" {
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 = "email", .value = .{ .text = "a@x.com" } },
.{ .name = "age", .value = .{ .int = 30 } },
});
try table.createUniqueIndex("email");
// Re-writing the same email on the same row must not conflict with itself.
try table.put(.{ .int = 1 }, &.{
.{ .name = "email", .value = .{ .text = "a@x.com" } },
.{ .name = "age", .value = .{ .int = 31 } },
});
var row = (try table.get(.{ .int = 1 })).?;
defer row.deinit();
try std.testing.expectEqual(@as(i64, 31), row.get("age").?.int);
}
test "unique constraint frees a value for reuse once the owning row is deleted" {
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.createUniqueIndex("email");
try table.put(.{ .int = 1 }, &.{.{ .name = "email", .value = .{ .text = "a@x.com" } }});
try std.testing.expectError(error.UniqueViolation, table.put(.{ .int = 2 }, &.{
.{ .name = "email", .value = .{ .text = "a@x.com" } },
}));
try table.delete(.{ .int = 1 });
try table.put(.{ .int = 2 }, &.{.{ .name = "email", .value = .{ .text = "a@x.com" } }});
var row = (try table.get(.{ .int = 2 })).?;
defer row.deinit();
try std.testing.expectEqualStrings("a@x.com", row.get("email").?.text);
}
test "dropIndex on a unique field also drops the unique constraint" {
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.createUniqueIndex("email");
try table.put(.{ .int = 1 }, &.{.{ .name = "email", .value = .{ .text = "a@x.com" } }});
try table.dropIndex("email");
try std.testing.expect(!table.hasUniqueIndex("email"));
try std.testing.expect(!table.hasIndex("email"));
// With no constraint left, a colliding value must now be accepted.
try table.put(.{ .int = 2 }, &.{.{ .name = "email", .value = .{ .text = "a@x.com" } }});
const rows = try table.scanAll();
defer freeRows(allocator, rows);
try std.testing.expectEqual(@as(usize, 2), rows.len);
}
test "unique constraint survives a table reopen (persisted across process restart)" {
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.createUniqueIndex("email");
try table.put(.{ .int = 1 }, &.{.{ .name = "email", .value = .{ .text = "a@x.com" } }});
}