feat: fill in query
This commit is contained in:
1 parent
abc713e5fc
commit
d6c31cb6d0
1 file changed
+156
+156
@@ -2381,3 +2381,159 @@ test "explain reports composite_eq and composite_range with matched/total field
|
|||||||
var db = try openTemp(&tmp, allocator);
|
var db = try openTemp(&tmp, allocator);
|
||||||
defer db.close();
|
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 } },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Only the leading column pinned -> composite_eq over a strict prefix
|
||||||
|
// (matchedFields 1 of 2). `findCompositeMatch` only looks inside a
|
||||||
|
// top-level `and_` (see its doc comment), so a bare single `eq` outside
|
||||||
|
// one does not reach the composite index at all - wrapping in `and_`
|
||||||
|
// with itself as the sole conjunct is what actually exercises the
|
||||||
|
// leftmost-prefix match here.
|
||||||
|
const prefixPreds = [_]Predicate{
|
||||||
|
.{ .eq = .{ .field = "city", .value = .{ .text = "warsaw" } } },
|
||||||
|
};
|
||||||
|
var prefixOnly = try table.explain(allocator, .{ .and_ = &prefixPreds });
|
||||||
|
defer prefixOnly.deinit(allocator);
|
||||||
|
try std.testing.expectEqual(PlanKind.composite_eq, prefixOnly.kind);
|
||||||
|
try std.testing.expectEqualStrings("city_age", prefixOnly.indexName.?);
|
||||||
|
try std.testing.expectEqual(@as(usize, 1), prefixOnly.matchedFields);
|
||||||
|
try std.testing.expectEqual(@as(usize, 2), prefixOnly.totalFields);
|
||||||
|
|
||||||
|
// Both columns pinned by equality -> composite_eq, fully matched.
|
||||||
|
const eqPreds = [_]Predicate{
|
||||||
|
.{ .eq = .{ .field = "city", .value = .{ .text = "warsaw" } } },
|
||||||
|
.{ .eq = .{ .field = "age", .value = .{ .int = 30 } } },
|
||||||
|
};
|
||||||
|
var fullEq = try table.explain(allocator, .{ .and_ = &eqPreds });
|
||||||
|
defer fullEq.deinit(allocator);
|
||||||
|
try std.testing.expectEqual(PlanKind.composite_eq, fullEq.kind);
|
||||||
|
try std.testing.expectEqual(@as(usize, 2), fullEq.matchedFields);
|
||||||
|
|
||||||
|
// Leading column pinned by equality, next column range-bound -> composite_range.
|
||||||
|
const rangePreds = [_]Predicate{
|
||||||
|
.{ .eq = .{ .field = "city", .value = .{ .text = "warsaw" } } },
|
||||||
|
.{ .gte = .{ .field = "age", .value = .{ .int = 18 } } },
|
||||||
|
};
|
||||||
|
var rangeMatch = try table.explain(allocator, .{ .and_ = &rangePreds });
|
||||||
|
defer rangeMatch.deinit(allocator);
|
||||||
|
try std.testing.expectEqual(PlanKind.composite_range, rangeMatch.kind);
|
||||||
|
try std.testing.expectEqualStrings("city_age", rangeMatch.indexName.?);
|
||||||
|
try std.testing.expectEqual(@as(usize, 1), rangeMatch.matchedFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "explain never mutates the table (const receiver, no rows touched)" {
|
||||||
|
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 pred = Predicate{ .eq = .{ .field = "city", .value = .{ .text = "warsaw" } } };
|
||||||
|
const constTable: *const Table = &table;
|
||||||
|
var plan = try constTable.explain(allocator, pred);
|
||||||
|
defer plan.deinit(allocator);
|
||||||
|
try std.testing.expectEqual(PlanKind.index_eq, plan.kind);
|
||||||
|
|
||||||
|
// The row explain() was asked about is still there afterwards.
|
||||||
|
const rows = try table.select(pred);
|
||||||
|
defer freeRows(allocator, rows);
|
||||||
|
try std.testing.expectEqual(@as(usize, 1), rows.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "updateWhere applies a set clause to 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" } }, .{ .name = "active", .value = .{ .int = 0 } } });
|
||||||
|
try table.put(.{ .int = 2 }, &.{ .{ .name = "city", .value = .{ .text = "warsaw" } }, .{ .name = "active", .value = .{ .int = 0 } } });
|
||||||
|
try table.put(.{ .int = 3 }, &.{ .{ .name = "city", .value = .{ .text = "krakow" } }, .{ .name = "active", .value = .{ .int = 0 } } });
|
||||||
|
|
||||||
|
const pred = Predicate{ .eq = .{ .field = "city", .value = .{ .text = "warsaw" } } };
|
||||||
|
const updated = try table.updateWhere(pred, &.{.{ .name = "active", .value = .{ .int = 1 } }});
|
||||||
|
try std.testing.expectEqual(@as(usize, 2), updated);
|
||||||
|
|
||||||
|
var row1 = (try table.get(.{ .int = 1 })).?;
|
||||||
|
defer row1.deinit();
|
||||||
|
try std.testing.expectEqual(Value{ .int = 1 }, row1.get("active").?);
|
||||||
|
try std.testing.expectEqualStrings("warsaw", row1.get("city").?.text);
|
||||||
|
|
||||||
|
var row3 = (try table.get(.{ .int = 3 })).?;
|
||||||
|
defer row3.deinit();
|
||||||
|
try std.testing.expectEqual(Value{ .int = 0 }, row3.get("active").?);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "updateWhere with a null predicate touches every row" {
|
||||||
|
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 updated = try table.updateWhere(null, &.{.{ .name = "flag", .value = .{ .text = "seen" } }});
|
||||||
|
try std.testing.expectEqual(@as(usize, 2), updated);
|
||||||
|
|
||||||
|
var row1 = (try table.get(.{ .int = 1 })).?;
|
||||||
|
defer row1.deinit();
|
||||||
|
try std.testing.expectEqualStrings("seen", row1.get("flag").?.text);
|
||||||
|
// Untouched fields survive the merge.
|
||||||
|
try std.testing.expectEqual(Value{ .int = 5 }, row1.get("n").?);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "updateWhere adds a brand new field a matched row never had" {
|
||||||
|
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.updateWhere(null, &.{.{ .name = "tier", .value = .{ .text = "gold" } }});
|
||||||
|
|
||||||
|
var row = (try table.get(.{ .int = 1 })).?;
|
||||||
|
defer row.deinit();
|
||||||
|
try std.testing.expectEqualStrings("gold", row.get("tier").?.text);
|
||||||
|
try std.testing.expectEqualStrings("warsaw", row.get("city").?.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "updateWhere keeps a secondary index in sync when the indexed field changes" {
|
||||||
|
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 updated = try table.updateWhere(
|
||||||
|
.{ .eq = .{ .field = "city", .value = .{ .text = "warsaw" } } },
|
||||||
|
&.{.{ .name = "city", .value = .{ .text = "krakow" } }},
|
||||||
|
);
|
||||||
|
try std.testing.expectEqual(@as(usize, 1), updated);
|
||||||
|
|
||||||
Reference in new issue
Block a user