feat: flesh out window
This commit is contained in:
1 parent
4f1dba3077
commit
41e0f00bbd
1 file changed
+158
+158
@@ -1445,3 +1445,161 @@ test "orderedIndices propagates OutOfMemory without leaking" {
|
|||||||
try std.testing.expectError(error.OutOfMemory, orderedIndices(failing.allocator(), rows.items, &.{}, &.{}));
|
try std.testing.expectError(error.OutOfMemory, orderedIndices(failing.allocator(), rows.items, &.{}, &.{}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "computeMany matches calling compute() once per spec, sharing one partition/order/frame" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
var rows = std.ArrayList(Row).init(allocator);
|
||||||
|
defer {
|
||||||
|
for (rows.items) |*r| r.deinit();
|
||||||
|
rows.deinit();
|
||||||
|
}
|
||||||
|
const data = [_]struct { city: []const u8, amount: i64 }{
|
||||||
|
.{ .city = "warsaw", .amount = 10 },
|
||||||
|
.{ .city = "krakow", .amount = 9 },
|
||||||
|
.{ .city = "warsaw", .amount = 20 },
|
||||||
|
.{ .city = "krakow", .amount = 2 },
|
||||||
|
};
|
||||||
|
for (data) |d| {
|
||||||
|
try rows.append(try makeRow(allocator, &.{
|
||||||
|
.{ .name = "city", .value = .{ .text = d.city } },
|
||||||
|
.{ .name = "amount", .value = .{ .int = d.amount } },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const partition = [_][]const u8{"city"};
|
||||||
|
const order = [_]querymod.OrderBy{.{ .field = "amount" }};
|
||||||
|
const specs = [_]WindowSpec{
|
||||||
|
.{ .field = "amount", .kind = .sum },
|
||||||
|
.{ .field = "amount", .kind = .avg },
|
||||||
|
.{ .field = "", .kind = .row_number },
|
||||||
|
};
|
||||||
|
|
||||||
|
const many = try computeMany(allocator, rows.items, &partition, &order, &specs, .rows);
|
||||||
|
defer freeManyResults(allocator, many);
|
||||||
|
|
||||||
|
for (specs, 0..) |spec, specIdx| {
|
||||||
|
const solo = try compute(allocator, rows.items, &partition, &order, spec, .rows);
|
||||||
|
defer freeResults(allocator, solo);
|
||||||
|
for (rows.items, 0..) |_, rowIdx| {
|
||||||
|
try std.testing.expect(rowmod.compare(many[rowIdx][specIdx], solo[rowIdx]) == .eq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "computeMany keeps min/max text results independently owned per row and per spec" {
|
||||||
|
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" };
|
||||||
|
for (names) |n| {
|
||||||
|
try rows.append(try makeRow(allocator, &.{.{ .name = "name", .value = .{ .text = n } }}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const specs = [_]WindowSpec{
|
||||||
|
.{ .field = "name", .kind = .min },
|
||||||
|
.{ .field = "name", .kind = .max },
|
||||||
|
};
|
||||||
|
const results = try computeMany(allocator, rows.items, &.{}, &.{}, &specs, .rows);
|
||||||
|
defer freeManyResults(allocator, results);
|
||||||
|
|
||||||
|
for (results) |row| {
|
||||||
|
try std.testing.expectEqualStrings("apple", row[0].text);
|
||||||
|
try std.testing.expectEqualStrings("mango", row[1].text);
|
||||||
|
}
|
||||||
|
// Every row's `min` slice is its own allocation, not a shared pointer -
|
||||||
|
// `freeManyResults` frees each independently, which the testing
|
||||||
|
// allocator would already reject as a double free if they aliased.
|
||||||
|
try std.testing.expect(results[0][0].text.ptr != results[1][0].text.ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "computeMany with zero specs returns one empty values slice per row, no crash" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
var rows = try threeRowsOnePartition(allocator);
|
||||||
|
defer {
|
||||||
|
for (rows.items) |*r| r.deinit();
|
||||||
|
rows.deinit();
|
||||||
|
}
|
||||||
|
const results = try computeMany(allocator, rows.items, &.{}, &.{}, &.{}, .rows);
|
||||||
|
defer freeManyResults(allocator, results);
|
||||||
|
try std.testing.expectEqual(@as(usize, 3), results.len);
|
||||||
|
for (results) |row| try std.testing.expectEqual(@as(usize, 0), row.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "computeMany 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();
|
||||||
|
}
|
||||||
|
const specs = [_]WindowSpec{
|
||||||
|
.{ .field = "score", .kind = .sum },
|
||||||
|
.{ .field = "", .kind = .row_number },
|
||||||
|
};
|
||||||
|
var failing = std.testing.FailingAllocator.init(allocator, .{ .fail_index = 0 });
|
||||||
|
try std.testing.expectError(error.OutOfMemory, computeMany(failing.allocator(), rows.items, &.{}, &.{}, &specs, .rows));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "computeMany propagates OutOfMemory without leaking, mid-way through the row-major transpose" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
var rows = try threeRowsOnePartition(allocator);
|
||||||
|
defer {
|
||||||
|
for (rows.items) |*r| r.deinit();
|
||||||
|
rows.deinit();
|
||||||
|
}
|
||||||
|
const specs = [_]WindowSpec{
|
||||||
|
.{ .field = "score", .kind = .sum },
|
||||||
|
.{ .field = "team", .kind = .min },
|
||||||
|
};
|
||||||
|
// Large enough to run past both columns' own computation (sum has no
|
||||||
|
// extra allocations per row; min dupes a `Value` per row observed) and
|
||||||
|
// into the per-row `rowValues`/`dupe` allocations the transpose loop
|
||||||
|
// itself makes - proven by the assertion below, not by counting by hand.
|
||||||
|
var failIndex: usize = 0;
|
||||||
|
while (failIndex < 40) : (failIndex += 1) {
|
||||||
|
var failing = std.testing.FailingAllocator.init(allocator, .{ .fail_index = failIndex });
|
||||||
|
const result = computeMany(failing.allocator(), rows.items, &.{}, &.{}, &specs, .rows);
|
||||||
|
if (result) |ok| {
|
||||||
|
freeManyResults(allocator, ok);
|
||||||
|
break;
|
||||||
|
} else |err| {
|
||||||
|
try std.testing.expectEqual(error.OutOfMemory, err);
|
||||||
|
}
|
||||||
|
} else return error.TestUnexpectedResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
test "lag/lead default offset of 1: NULL (default) at the edge, adjacent value otherwise" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
var rows = std.ArrayList(Row).init(allocator);
|
||||||
|
defer {
|
||||||
|
for (rows.items) |*r| r.deinit();
|
||||||
|
rows.deinit();
|
||||||
|
}
|
||||||
|
const data = [_]struct { day: i64, amount: i64 }{
|
||||||
|
.{ .day = 1, .amount = 10 },
|
||||||
|
.{ .day = 2, .amount = 20 },
|
||||||
|
.{ .day = 3, .amount = 30 },
|
||||||
|
};
|
||||||
|
for (data) |d| {
|
||||||
|
try rows.append(try makeRow(allocator, &.{
|
||||||
|
.{ .name = "day", .value = .{ .int = d.day } },
|
||||||
|
.{ .name = "amount", .value = .{ .int = d.amount } },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
const order = [_]querymod.OrderBy{.{ .field = "day" }};
|
||||||
|
|
||||||
|
const lag = try compute(allocator, rows.items, &.{}, &order, .{ .field = "amount", .kind = .lag }, .rows);
|
||||||
|
defer freeResults(allocator, lag);
|
||||||
|
try std.testing.expectEqual(rowmod.ValueTag.none, @as(rowmod.ValueTag, lag[0]));
|
||||||
|
try std.testing.expectEqual(@as(i64, 10), lag[1].int);
|
||||||
|
try std.testing.expectEqual(@as(i64, 20), lag[2].int);
|
||||||
|
|
||||||
|
const lead = try compute(allocator, rows.items, &.{}, &order, .{ .field = "amount", .kind = .lead }, .rows);
|
||||||
|
defer freeResults(allocator, lead);
|
||||||
|
try std.testing.expectEqual(@as(i64, 20), lead[0].int);
|
||||||
|
try std.testing.expectEqual(@as(i64, 30), lead[1].int);
|
||||||
|
try std.testing.expectEqual(rowmod.ValueTag.none, @as(rowmod.ValueTag, lead[2]));
|
||||||
|
}
|
||||||
|
|
||||||
Reference in new issue
Block a user