feat: grow window
This commit is contained in:
1 parent
ead8304bb7
commit
4f1dba3077
1 file changed
+150
+150
@@ -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, &.{}, &.{}));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user