feat: build out query
This commit is contained in:
1 parent
a012d94448
commit
5b47228e4e
1 file changed
+161
+161
@@ -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" } }});
|
||||||
|
}
|
||||||
|
|
||||||
Reference in new issue
Block a user