feat: expand window
This commit is contained in:
1 parent
dcbdae99b4
commit
fb196a2376
1 file changed
+132
+132
@@ -2088,3 +2088,135 @@ test "nth_value restart per partition, RANGE tie group stays within one city" {
|
|||||||
try std.testing.expectEqual(@as(i64, 90), results[3].int);
|
try std.testing.expectEqual(@as(i64, 90), results[3].int);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "nth_value on a text field dupes independently per row, no shared pointers" {
|
||||||
|
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, 0..) |n, i| {
|
||||||
|
try rows.append(try makeRow(allocator, &.{
|
||||||
|
.{ .name = "seq", .value = .{ .int = @intCast(i) } },
|
||||||
|
.{ .name = "name", .value = .{ .text = n } },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
const order = [_]querymod.OrderBy{.{ .field = "seq" }};
|
||||||
|
const results = try compute(allocator, rows.items, &.{}, &order, .{ .field = "name", .kind = .nth_value, .offset = 1 }, .rows);
|
||||||
|
defer freeResults(allocator, results);
|
||||||
|
try std.testing.expectEqualStrings("mango", results[0].text);
|
||||||
|
try std.testing.expectEqualStrings("mango", results[2].text);
|
||||||
|
try std.testing.expect(results[2].text.ptr != names[0].ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "nth_value requires a field, like first_value/last_value" {
|
||||||
|
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 = .nth_value, .offset = 1 }, .rows));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "nth_value accepts a RANGE frame, unlike lag/lead" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
var rows = try threeRowsOnePartition(allocator);
|
||||||
|
defer {
|
||||||
|
for (rows.items) |*r| r.deinit();
|
||||||
|
rows.deinit();
|
||||||
|
}
|
||||||
|
const order = [_]querymod.OrderBy{.{ .field = "score" }};
|
||||||
|
const results = try compute(allocator, rows.items, &.{}, &order, .{ .field = "score", .kind = .nth_value, .offset = 1 }, .range);
|
||||||
|
defer freeResults(allocator, results);
|
||||||
|
for (results) |v| try std.testing.expectEqual(@as(i64, 10), v.int);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "nth_value propagates OutOfMemory without leaking" {
|
||||||
|
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" };
|
||||||
|
for (names, 0..) |n, i| {
|
||||||
|
try rows.append(try makeRow(allocator, &.{
|
||||||
|
.{ .name = "seq", .value = .{ .int = @intCast(i) } },
|
||||||
|
.{ .name = "name", .value = .{ .text = n } },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
const order = [_]querymod.OrderBy{.{ .field = "seq" }};
|
||||||
|
var failing = std.testing.FailingAllocator.init(allocator, .{ .fail_index = 4 });
|
||||||
|
try std.testing.expectError(error.OutOfMemory, compute(failing.allocator(), rows.items, &.{}, &order, .{ .field = "name", .kind = .nth_value, .offset = 2 }, .rows));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "nth_value on zero rows in a partition does not crash (empty indices guard)" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
const rows = [_]Row{};
|
||||||
|
const results = try compute(allocator, &rows, &.{}, &.{}, .{ .field = "x", .kind = .nth_value, .offset = 1 }, .rows);
|
||||||
|
defer freeResults(allocator, results);
|
||||||
|
try std.testing.expectEqual(@as(usize, 0), results.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "computeMany mixes nth_value with lag/lead/running aggregates/ranking under one shared window" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
var rows = std.ArrayList(Row).init(allocator);
|
||||||
|
defer {
|
||||||
|
for (rows.items) |*r| r.deinit();
|
||||||
|
rows.deinit();
|
||||||
|
}
|
||||||
|
const amounts = [_]i64{ 10, 20, 30 };
|
||||||
|
for (amounts, 0..) |a, i| {
|
||||||
|
try rows.append(try makeRow(allocator, &.{
|
||||||
|
.{ .name = "seq", .value = .{ .int = @intCast(i) } },
|
||||||
|
.{ .name = "amount", .value = .{ .int = a } },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
const order = [_]querymod.OrderBy{.{ .field = "seq" }};
|
||||||
|
const specs = [_]WindowSpec{
|
||||||
|
.{ .field = "amount", .kind = .nth_value, .offset = 2 },
|
||||||
|
.{ .field = "amount", .kind = .lag },
|
||||||
|
.{ .field = "amount", .kind = .row_number },
|
||||||
|
};
|
||||||
|
const results = try computeMany(allocator, rows.items, &.{}, &order, &specs, .rows);
|
||||||
|
defer freeManyResults(allocator, results);
|
||||||
|
try std.testing.expectEqual(@as(usize, 3), results.len);
|
||||||
|
// nth_value(amount, 2): row 1's frame is too small (default None), rows 2-3 both see 20.
|
||||||
|
try std.testing.expectEqual(Value{ .none = {} }, results[0][0]);
|
||||||
|
try std.testing.expectEqual(@as(i64, 20), results[1][0].int);
|
||||||
|
try std.testing.expectEqual(@as(i64, 20), results[2][0].int);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "computeMany mixes lag/lead with running aggregates and ranking under one shared window" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
var rows = std.ArrayList(Row).init(allocator);
|
||||||
|
defer {
|
||||||
|
for (rows.items) |*r| r.deinit();
|
||||||
|
rows.deinit();
|
||||||
|
}
|
||||||
|
const amounts = [_]i64{ 10, 20, 30 };
|
||||||
|
for (amounts, 0..) |a, i| {
|
||||||
|
try rows.append(try makeRow(allocator, &.{
|
||||||
|
.{ .name = "seq", .value = .{ .int = @intCast(i) } },
|
||||||
|
.{ .name = "amount", .value = .{ .int = a } },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
const order = [_]querymod.OrderBy{.{ .field = "seq" }};
|
||||||
|
const specs = [_]WindowSpec{
|
||||||
|
.{ .field = "amount", .kind = .lag },
|
||||||
|
.{ .field = "amount", .kind = .lead },
|
||||||
|
.{ .field = "amount", .kind = .sum },
|
||||||
|
.{ .field = "", .kind = .row_number },
|
||||||
|
};
|
||||||
|
const many = try computeMany(allocator, rows.items, &.{}, &order, &specs, .rows);
|
||||||
|
defer freeManyResults(allocator, many);
|
||||||
|
|
||||||
|
try std.testing.expectEqual(rowmod.ValueTag.none, @as(rowmod.ValueTag, many[0][0]));
|
||||||
|
try std.testing.expectEqual(@as(i64, 10), many[1][0].int); // lag
|
||||||
|
try std.testing.expectEqual(@as(i64, 20), many[0][1].int); // lead
|
||||||
|
try std.testing.expectEqual(rowmod.ValueTag.none, @as(rowmod.ValueTag, many[2][1]));
|
||||||
|
try std.testing.expectEqual(@as(i64, 60), many[2][2].int); // running sum, final row
|
||||||
|
try std.testing.expectEqual(@as(i64, 3), many[2][3].int); // row_number
|
||||||
|
}
|
||||||
Reference in new issue
Block a user