feat: continue work on query
This commit is contained in:
1 parent
c4c651f782
commit
abc713e5fc
1 file changed
+157
+157
@@ -2224,3 +2224,160 @@ test "composite index stays in sync across update and delete" {
|
||||
try std.testing.expectEqual(@as(usize, 0), stale.len);
|
||||
}
|
||||
|
||||
// A row missing one of the composite columns entirely must not produce
|
||||
// a (partial, ill-formed) entry: the put must still succeed, and the
|
||||
// row must never surface as a false match on any age value including
|
||||
// its own current (missing) one.
|
||||
try table.put(.{ .int = 2 }, &.{.{ .name = "city", .value = .{ .text = "warsaw" } }});
|
||||
{
|
||||
const wholeTable = try table.select(null);
|
||||
defer freeRows(allocator, wholeTable);
|
||||
try std.testing.expectEqual(@as(usize, 2), wholeTable.len);
|
||||
|
||||
const ageless = [_]Predicate{
|
||||
.{ .eq = .{ .field = "city", .value = .{ .text = "warsaw" } } },
|
||||
.{ .eq = .{ .field = "age", .value = .{ .int = 31 } } },
|
||||
};
|
||||
const noMatch = try table.select(.{ .and_ = &ageless });
|
||||
defer freeRows(allocator, noMatch);
|
||||
try std.testing.expectEqual(@as(usize, 1), noMatch.len);
|
||||
try std.testing.expectEqual(@as(i64, 1), noMatch[0].get(pkField).?.int);
|
||||
}
|
||||
|
||||
try table.put(.{ .int = 1 }, &.{
|
||||
.{ .name = "city", .value = .{ .text = "warsaw" } },
|
||||
.{ .name = "age", .value = .{ .int = 30 } },
|
||||
});
|
||||
try table.delete(.{ .int = 1 });
|
||||
{
|
||||
const afterDelete = try table.select(pred);
|
||||
defer freeRows(allocator, afterDelete);
|
||||
try std.testing.expectEqual(@as(usize, 0), afterDelete.len);
|
||||
}
|
||||
}
|
||||
|
||||
test "dropCompositeIndex removes entries and falls back to full scan" {
|
||||
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.dropCompositeIndex("city_age");
|
||||
try std.testing.expect(!table.hasCompositeIndex("city_age"));
|
||||
|
||||
const preds = [_]Predicate{
|
||||
.{ .eq = .{ .field = "city", .value = .{ .text = "warsaw" } } },
|
||||
.{ .eq = .{ .field = "age", .value = .{ .int = 30 } } },
|
||||
};
|
||||
const results = try table.select(.{ .and_ = &preds });
|
||||
defer freeRows(allocator, results);
|
||||
try std.testing.expectEqual(@as(usize, 1), results.len);
|
||||
}
|
||||
|
||||
test "createCompositeIndex rejects bad field counts and identifiers" {
|
||||
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 std.testing.expectError(error.InvalidCompositeIndex, table.createCompositeIndex("solo", &.{"city"}));
|
||||
try std.testing.expectError(error.InvalidIdentifier, table.createCompositeIndex("bad\x00name", &.{ "city", "age" }));
|
||||
try std.testing.expectError(error.InvalidIdentifier, table.createCompositeIndex("ok", &.{ "city", "bad\x00field" }));
|
||||
}
|
||||
|
||||
test "composite index 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.createCompositeIndex("city_age", &.{ "city", "age" });
|
||||
try table.put(.{ .int = 1 }, &.{
|
||||
.{ .name = "city", .value = .{ .text = "warsaw" } },
|
||||
.{ .name = "age", .value = .{ .int = 30 } },
|
||||
});
|
||||
}
|
||||
|
||||
var reopened = try Table.open(&db, allocator, "users");
|
||||
defer reopened.deinit();
|
||||
try std.testing.expect(reopened.hasCompositeIndex("city_age"));
|
||||
|
||||
const preds = [_]Predicate{
|
||||
.{ .eq = .{ .field = "city", .value = .{ .text = "warsaw" } } },
|
||||
.{ .eq = .{ .field = "age", .value = .{ .int = 30 } } },
|
||||
};
|
||||
const results = try reopened.select(.{ .and_ = &preds });
|
||||
defer freeRows(allocator, results);
|
||||
try std.testing.expectEqual(@as(usize, 1), results.len);
|
||||
}
|
||||
|
||||
test "explain reports full_scan for a null predicate and an unindexed 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 = "city", .value = .{ .text = "warsaw" } }});
|
||||
|
||||
var noPred = try table.explain(allocator, null);
|
||||
defer noPred.deinit(allocator);
|
||||
try std.testing.expectEqual(PlanKind.full_scan, noPred.kind);
|
||||
try std.testing.expect(noPred.indexName == null);
|
||||
|
||||
var unindexed = try table.explain(allocator, .{ .eq = .{ .field = "city", .value = .{ .text = "warsaw" } } });
|
||||
defer unindexed.deinit(allocator);
|
||||
try std.testing.expectEqual(PlanKind.full_scan, unindexed.kind);
|
||||
}
|
||||
|
||||
test "explain reports index_eq and index_range for a single-field index" {
|
||||
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, "events");
|
||||
defer table.deinit();
|
||||
try table.createIndex("score");
|
||||
try table.put(.{ .int = 1 }, &.{.{ .name = "score", .value = .{ .int = 5 } }});
|
||||
|
||||
var eqPlan = try table.explain(allocator, .{ .eq = .{ .field = "score", .value = .{ .int = 5 } } });
|
||||
defer eqPlan.deinit(allocator);
|
||||
try std.testing.expectEqual(PlanKind.index_eq, eqPlan.kind);
|
||||
try std.testing.expectEqualStrings("score", eqPlan.indexName.?);
|
||||
try std.testing.expectEqual(@as(usize, 1), eqPlan.matchedFields);
|
||||
try std.testing.expectEqual(@as(usize, 1), eqPlan.totalFields);
|
||||
|
||||
var rangePlan = try table.explain(allocator, .{ .gte = .{ .field = "score", .value = .{ .int = 5 } } });
|
||||
defer rangePlan.deinit(allocator);
|
||||
try std.testing.expectEqual(PlanKind.index_range, rangePlan.kind);
|
||||
try std.testing.expectEqualStrings("score", rangePlan.indexName.?);
|
||||
}
|
||||
|
||||
test "explain reports composite_eq and composite_range with matched/total field counts" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
var db = try openTemp(&tmp, allocator);
|
||||
defer db.close();
|
||||
|
||||
Reference in new issue
Block a user