feat: expand query
This commit is contained in:
1 parent
b16f2ddf47
commit
a012d94448
1 file changed
+155
+155
@@ -2701,3 +2701,158 @@ test "updateWhere keeps a composite index in sync across leading and trailing co
|
|||||||
try std.testing.expectEqualStrings(exp.name, rows[0].get("name").?.text);
|
try std.testing.expectEqualStrings(exp.name, rows[0].get("name").?.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const everyone = try table.select(null);
|
||||||
|
defer freeRows(allocator, everyone);
|
||||||
|
try std.testing.expectEqual(@as(usize, 4), everyone.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "deleteWhere removes every matching row and reports the count" {
|
||||||
|
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 = "city", .value = .{ .text = "warsaw" } }});
|
||||||
|
try table.put(.{ .int = 2 }, &.{.{ .name = "city", .value = .{ .text = "warsaw" } }});
|
||||||
|
try table.put(.{ .int = 3 }, &.{.{ .name = "city", .value = .{ .text = "krakow" } }});
|
||||||
|
|
||||||
|
const pred = Predicate{ .eq = .{ .field = "city", .value = .{ .text = "warsaw" } } };
|
||||||
|
const deleted = try table.deleteWhere(pred);
|
||||||
|
try std.testing.expectEqual(@as(usize, 2), deleted);
|
||||||
|
|
||||||
|
try std.testing.expectEqual(@as(?Row, null), try table.get(.{ .int = 1 }));
|
||||||
|
try std.testing.expectEqual(@as(?Row, null), try table.get(.{ .int = 2 }));
|
||||||
|
|
||||||
|
var row3 = (try table.get(.{ .int = 3 })).?;
|
||||||
|
defer row3.deinit();
|
||||||
|
try std.testing.expectEqualStrings("krakow", row3.get("city").?.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "deleteWhere with a null predicate empties the table" {
|
||||||
|
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, "counters");
|
||||||
|
defer table.deinit();
|
||||||
|
try table.put(.{ .int = 1 }, &.{.{ .name = "n", .value = .{ .int = 5 } }});
|
||||||
|
try table.put(.{ .int = 2 }, &.{.{ .name = "n", .value = .{ .int = 7 } }});
|
||||||
|
|
||||||
|
const deleted = try table.deleteWhere(null);
|
||||||
|
try std.testing.expectEqual(@as(usize, 2), deleted);
|
||||||
|
|
||||||
|
const everyone = try table.select(null);
|
||||||
|
defer freeRows(allocator, everyone);
|
||||||
|
try std.testing.expectEqual(@as(usize, 0), everyone.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "deleteWhere with no matches deletes nothing and leaves the table untouched" {
|
||||||
|
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 = "city", .value = .{ .text = "warsaw" } }});
|
||||||
|
|
||||||
|
const deleted = try table.deleteWhere(.{ .eq = .{ .field = "city", .value = .{ .text = "gdansk" } } });
|
||||||
|
try std.testing.expectEqual(@as(usize, 0), deleted);
|
||||||
|
|
||||||
|
var row = (try table.get(.{ .int = 1 })).?;
|
||||||
|
defer row.deinit();
|
||||||
|
try std.testing.expectEqualStrings("warsaw", row.get("city").?.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "deleteWhere removes stale secondary index entries so a fresh row can reuse the value" {
|
||||||
|
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("city");
|
||||||
|
try table.put(.{ .int = 1 }, &.{.{ .name = "city", .value = .{ .text = "warsaw" } }});
|
||||||
|
|
||||||
|
const deleted = try table.deleteWhere(.{ .eq = .{ .field = "city", .value = .{ .text = "warsaw" } } });
|
||||||
|
try std.testing.expectEqual(@as(usize, 1), deleted);
|
||||||
|
|
||||||
|
// The old index entry for pk 1 under "warsaw" must be gone, not just the
|
||||||
|
// row - otherwise a later put of a *different* pk under the same "warsaw"
|
||||||
|
// value would find a phantom sibling in the index.
|
||||||
|
const stale = try table.select(.{ .eq = .{ .field = "city", .value = .{ .text = "warsaw" } } });
|
||||||
|
defer freeRows(allocator, stale);
|
||||||
|
try std.testing.expectEqual(@as(usize, 0), stale.len);
|
||||||
|
|
||||||
|
try table.put(.{ .int = 2 }, &.{.{ .name = "city", .value = .{ .text = "warsaw" } }});
|
||||||
|
const fresh = try table.select(.{ .eq = .{ .field = "city", .value = .{ .text = "warsaw" } } });
|
||||||
|
defer freeRows(allocator, fresh);
|
||||||
|
try std.testing.expectEqual(@as(usize, 1), fresh.len);
|
||||||
|
try std.testing.expectEqual(@as(i64, 2), fresh[0].get(pkField).?.int);
|
||||||
|
|
||||||
|
const constTable: *const Table = &table;
|
||||||
|
var plan = try constTable.explain(allocator, .{ .eq = .{ .field = "city", .value = .{ .text = "warsaw" } } });
|
||||||
|
defer plan.deinit(allocator);
|
||||||
|
try std.testing.expectEqual(PlanKind.index_eq, plan.kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "deleteWhere removes stale composite index entries across matched equality prefix" {
|
||||||
|
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.createCompositeIndex("city_age", &.{ "city", "age" });
|
||||||
|
|
||||||
|
try table.put(.{ .int = 1 }, &.{
|
||||||
|
.{ .name = "city", .value = .{ .text = "warsaw" } },
|
||||||
|
.{ .name = "age", .value = .{ .int = 30 } },
|
||||||
|
});
|
||||||
|
try table.put(.{ .int = 2 }, &.{
|
||||||
|
.{ .name = "city", .value = .{ .text = "warsaw" } },
|
||||||
|
.{ .name = "age", .value = .{ .int = 31 } },
|
||||||
|
});
|
||||||
|
try table.put(.{ .int = 3 }, &.{
|
||||||
|
.{ .name = "city", .value = .{ .text = "krakow" } },
|
||||||
|
.{ .name = "age", .value = .{ .int = 30 } },
|
||||||
|
});
|
||||||
|
|
||||||
|
const preds = [_]Predicate{
|
||||||
|
.{ .eq = .{ .field = "city", .value = .{ .text = "warsaw" } } },
|
||||||
|
.{ .eq = .{ .field = "age", .value = .{ .int = 30 } } },
|
||||||
|
};
|
||||||
|
const deleted = try table.deleteWhere(.{ .and_ = &preds });
|
||||||
|
try std.testing.expectEqual(@as(usize, 1), deleted);
|
||||||
|
|
||||||
|
const gone = try table.select(.{ .and_ = &preds });
|
||||||
|
defer freeRows(allocator, gone);
|
||||||
|
try std.testing.expectEqual(@as(usize, 0), gone.len);
|
||||||
|
|
||||||
|
// The composite index must still correctly serve the two untouched rows,
|
||||||
|
// proving the deleted row's entry was surgically removed, not the whole
|
||||||
|
// (city="warsaw") or (age=30) bucket.
|
||||||
|
const survivors = [_]struct { pk: i64, city: []const u8, age: i64 }{
|
||||||
|
.{ .pk = 2, .city = "warsaw", .age = 31 },
|
||||||
|
.{ .pk = 3, .city = "krakow", .age = 30 },
|
||||||
|
};
|
||||||
|
for (survivors) |exp| {
|
||||||
|
const survivorPreds = [_]Predicate{
|
||||||
|
.{ .eq = .{ .field = "city", .value = .{ .text = exp.city } } },
|
||||||
|
.{ .eq = .{ .field = "age", .value = .{ .int = exp.age } } },
|
||||||
|
};
|
||||||
|
const rows = try table.select(.{ .and_ = &survivorPreds });
|
||||||
|
defer freeRows(allocator, rows);
|
||||||
|
try std.testing.expectEqual(@as(usize, 1), rows.len);
|
||||||
|
try std.testing.expectEqual(exp.pk, rows[0].get(pkField).?.int);
|
||||||
|
|
||||||
Reference in new issue
Block a user