feat: grow window

This commit is contained in:
dualfroz committed 2026-09-14 02:33:00 +02:00
1 parent ead8304bb7
commit 4f1dba3077
1 file changed
+150
+150
View File
@@ -1295,3 +1295,153 @@ test "running min and max over a text field, independently duped per row" {
}
const order = [_]querymod.OrderBy{.{ .field = "seq" }};
const mins = try compute(allocator, rows.items, &.{}, &order, .{ .field = "name", .kind = .min }, .rows);
defer freeResults(allocator, mins);
try std.testing.expectEqualStrings("mango", mins[0].text);
try std.testing.expectEqualStrings("apple", mins[1].text);
try std.testing.expectEqualStrings("apple", mins[2].text);
const maxs = try compute(allocator, rows.items, &.{}, &order, .{ .field = "name", .kind = .max }, .rows);
defer freeResults(allocator, maxs);
try std.testing.expectEqualStrings("mango", maxs[0].text);
try std.testing.expectEqualStrings("mango", maxs[1].text);
try std.testing.expectEqualStrings("mango", maxs[2].text);
}
test "sum/avg/min/max require a field, ranking family and COUNT(*) do not" {
const allocator = std.testing.allocator;
var rows = try threeRowsOnePartition(allocator);
defer {
for (rows.items) |*r| r.deinit();
rows.deinit();
}
try std.testing.expectError(error.MissingField, compute(allocator, rows.items, &.{}, &.{}, .{ .field = "", .kind = .sum }, .rows));
try std.testing.expectError(error.MissingField, compute(allocator, rows.items, &.{}, &.{}, .{ .field = "", .kind = .avg }, .rows));
try std.testing.expectError(error.MissingField, compute(allocator, rows.items, &.{}, &.{}, .{ .field = "", .kind = .min }, .rows));
try std.testing.expectError(error.MissingField, compute(allocator, rows.items, &.{}, &.{}, .{ .field = "", .kind = .max }, .rows));
const stars = try compute(allocator, rows.items, &.{}, &.{}, .{ .field = "", .kind = .count }, .rows);
defer freeResults(allocator, stars);
const nums = try compute(allocator, rows.items, &.{}, &.{}, .{ .field = "", .kind = .row_number }, .rows);
defer freeResults(allocator, nums);
}
test "sum on a non-numeric field is NonNumericField, integer overflow is Overflow" {
const allocator = std.testing.allocator;
var rows = std.ArrayList(Row).init(allocator);
defer {
for (rows.items) |*r| r.deinit();
rows.deinit();
}
try rows.append(try makeRow(allocator, &.{.{ .name = "x", .value = .{ .text = "nope" } }}));
try std.testing.expectError(error.NonNumericField, compute(allocator, rows.items, &.{}, &.{}, .{ .field = "x", .kind = .sum }, .rows));
var overflowRows = std.ArrayList(Row).init(allocator);
defer {
for (overflowRows.items) |*r| r.deinit();
overflowRows.deinit();
}
try overflowRows.append(try makeRow(allocator, &.{.{ .name = "x", .value = .{ .int = std.math.maxInt(i64) } }}));
try overflowRows.append(try makeRow(allocator, &.{.{ .name = "x", .value = .{ .int = 1 } }}));
try std.testing.expectError(error.Overflow, compute(allocator, overflowRows.items, &.{}, &.{}, .{ .field = "x", .kind = .sum }, .rows));
}
test "compute on zero rows returns an empty result, no crash" {
const allocator = std.testing.allocator;
const rows = [_]Row{};
const results = try compute(allocator, &rows, &.{}, &.{}, .{ .field = "", .kind = .row_number }, .rows);
defer freeResults(allocator, results);
try std.testing.expectEqual(@as(usize, 0), results.len);
}
test "specLabel matches aggregate.zig's kind_field / bare-kind convention" {
const allocator = std.testing.allocator;
const bare = try specLabel(allocator, .{ .field = "", .kind = .row_number });
defer allocator.free(bare);
try std.testing.expectEqualStrings("row_number", bare);
const withField = try specLabel(allocator, .{ .field = "score", .kind = .sum });
defer allocator.free(withField);
try std.testing.expectEqualStrings("sum_score", withField);
}
test "compute propagates OutOfMemory without leaking, at an early allocation" {
const allocator = std.testing.allocator;
var rows = try threeRowsOnePartition(allocator);
defer {
for (rows.items) |*r| r.deinit();
rows.deinit();
}
var failing = std.testing.FailingAllocator.init(allocator, .{ .fail_index = 0 });
try std.testing.expectError(error.OutOfMemory, compute(failing.allocator(), rows.items, &.{}, &.{}, .{ .field = "score", .kind = .sum }, .rows));
}
test "compute propagates OutOfMemory without leaking, mid-way through partitioned min/max" {
const allocator = std.testing.allocator;
var rows = std.ArrayList(Row).init(allocator);
defer {
for (rows.items) |*r| r.deinit();
rows.deinit();
}
const names = [_][]const u8{ "mango", "apple", "kiwi", "banana", "fig" };
for (names) |n| {
try rows.append(try makeRow(allocator, &.{.{ .name = "name", .value = .{ .text = n } }}));
}
var failing = std.testing.FailingAllocator.init(allocator, .{ .fail_index = 4 });
try std.testing.expectError(error.OutOfMemory, compute(failing.allocator(), rows.items, &.{}, &.{}, .{ .field = "name", .kind = .min }, .rows));
}
test "orderedIndices flattens partitions in first-seen order, each sorted by orderBy" {
const allocator = std.testing.allocator;
var rows = std.ArrayList(Row).init(allocator);
defer {
for (rows.items) |*r| r.deinit();
rows.deinit();
}
// First-seen partition order: warsaw, krakow. Within each, sort by
// score ascending should reorder relative to input.
const data = [_]struct { city: []const u8, score: i64 }{
.{ .city = "warsaw", .score = 5 },
.{ .city = "krakow", .score = 9 },
.{ .city = "warsaw", .score = 1 },
.{ .city = "krakow", .score = 2 },
};
for (data) |d| {
try rows.append(try makeRow(allocator, &.{
.{ .name = "city", .value = .{ .text = d.city } },
.{ .name = "score", .value = .{ .int = d.score } },
}));
}
const partition = [_][]const u8{"city"};
const order = [_]querymod.OrderBy{.{ .field = "score" }};
const idx = try orderedIndices(allocator, rows.items, &partition, &order);
defer allocator.free(idx);
// warsaw (first-seen) sorted asc: idx2 (score1), idx0 (score5) - then
// krakow sorted asc: idx3 (score2), idx1 (score9).
try std.testing.expectEqualSlices(usize, &.{ 2, 0, 3, 1 }, idx);
}
test "orderedIndices on zero rows returns an empty slice" {
const allocator = std.testing.allocator;
const rows = [_]Row{};
const idx = try orderedIndices(allocator, &rows, &.{}, &.{});
defer allocator.free(idx);
try std.testing.expectEqual(@as(usize, 0), idx.len);
}
test "orderedIndices propagates OutOfMemory without leaking" {
const allocator = std.testing.allocator;
var rows = try threeRowsOnePartition(allocator);
defer {
for (rows.items) |*r| r.deinit();
rows.deinit();
}
var failing = std.testing.FailingAllocator.init(allocator, .{ .fail_index = 0 });
try std.testing.expectError(error.OutOfMemory, orderedIndices(failing.allocator(), rows.items, &.{}, &.{}));
}