feat: continue work on window
This commit is contained in:
1 parent
41e0f00bbd
commit
053af60113
1 file changed
+166
+166
@@ -1603,3 +1603,169 @@ test "lag/lead default offset of 1: NULL (default) at the edge, adjacent value o
|
||||
try std.testing.expectEqual(rowmod.ValueTag.none, @as(rowmod.ValueTag, lead[2]));
|
||||
}
|
||||
|
||||
test "lag/lead with an explicit offset skips that many physical positions" {
|
||||
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, 40 };
|
||||
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 lag2 = try compute(allocator, rows.items, &.{}, &order, .{ .field = "amount", .kind = .lag, .offset = 2 }, .rows);
|
||||
defer freeResults(allocator, lag2);
|
||||
try std.testing.expectEqual(rowmod.ValueTag.none, @as(rowmod.ValueTag, lag2[0]));
|
||||
try std.testing.expectEqual(rowmod.ValueTag.none, @as(rowmod.ValueTag, lag2[1]));
|
||||
try std.testing.expectEqual(@as(i64, 10), lag2[2].int);
|
||||
try std.testing.expectEqual(@as(i64, 20), lag2[3].int);
|
||||
|
||||
// offset 0 reads the current row itself.
|
||||
const lagZero = try compute(allocator, rows.items, &.{}, &order, .{ .field = "amount", .kind = .lag, .offset = 0 }, .rows);
|
||||
defer freeResults(allocator, lagZero);
|
||||
for (amounts, 0..) |a, i| try std.testing.expectEqual(a, lagZero[i].int);
|
||||
}
|
||||
|
||||
test "lag/lead use a caller-supplied default instead of NULL at the edge" {
|
||||
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 = "seq", .value = .{ .int = 0 } },
|
||||
.{ .name = "amount", .value = .{ .int = 5 } },
|
||||
}));
|
||||
try rows.append(try makeRow(allocator, &.{
|
||||
.{ .name = "seq", .value = .{ .int = 1 } },
|
||||
.{ .name = "amount", .value = .{ .int = 6 } },
|
||||
}));
|
||||
const order = [_]querymod.OrderBy{.{ .field = "seq" }};
|
||||
|
||||
const lag = try compute(allocator, rows.items, &.{}, &order, .{ .field = "amount", .kind = .lag, .default = .{ .int = -1 } }, .rows);
|
||||
defer freeResults(allocator, lag);
|
||||
try std.testing.expectEqual(@as(i64, -1), lag[0].int);
|
||||
try std.testing.expectEqual(@as(i64, 5), lag[1].int);
|
||||
}
|
||||
|
||||
test "lag/lead restart per partition and treat orderBy ties as distinct physical positions" {
|
||||
const allocator = std.testing.allocator;
|
||||
var rows = std.ArrayList(Row).init(allocator);
|
||||
defer {
|
||||
for (rows.items) |*r| r.deinit();
|
||||
rows.deinit();
|
||||
}
|
||||
// Two rows tied on "day" within "warsaw" - lag/lead must still treat
|
||||
// them as two distinct positions (unlike the RANGE running-aggregate
|
||||
// frame, which would collapse ties into one shared value).
|
||||
const data = [_]struct { city: []const u8, day: i64, amount: i64 }{
|
||||
.{ .city = "warsaw", .day = 1, .amount = 10 },
|
||||
.{ .city = "warsaw", .day = 1, .amount = 20 },
|
||||
.{ .city = "krakow", .day = 1, .amount = 99 },
|
||||
};
|
||||
for (data) |d| {
|
||||
try rows.append(try makeRow(allocator, &.{
|
||||
.{ .name = "city", .value = .{ .text = d.city } },
|
||||
.{ .name = "day", .value = .{ .int = d.day } },
|
||||
.{ .name = "amount", .value = .{ .int = d.amount } },
|
||||
}));
|
||||
}
|
||||
const partition = [_][]const u8{"city"};
|
||||
const order = [_]querymod.OrderBy{.{ .field = "day" }};
|
||||
|
||||
const lead = try compute(allocator, rows.items, &partition, &order, .{ .field = "amount", .kind = .lead }, .rows);
|
||||
defer freeResults(allocator, lead);
|
||||
// Stable sort keeps warsaw's two tied rows in first-seen order (10 then
|
||||
// 20), so warsaw/10's lead is warsaw/20 (not krakow, a different
|
||||
// partition entirely), and warsaw/20 (the partition's last row) is NULL.
|
||||
try std.testing.expectEqual(@as(i64, 20), lead[0].int);
|
||||
try std.testing.expectEqual(rowmod.ValueTag.none, @as(rowmod.ValueTag, lead[1]));
|
||||
// krakow is its own partition of one row - always NULL.
|
||||
try std.testing.expectEqual(rowmod.ValueTag.none, @as(rowmod.ValueTag, lead[2]));
|
||||
}
|
||||
|
||||
test "lag/lead without ORDER BY walk first-seen input order" {
|
||||
const allocator = std.testing.allocator;
|
||||
var rows = try threeRowsOnePartition(allocator); // scores 30, 10, 20, first-seen order
|
||||
defer {
|
||||
for (rows.items) |*r| r.deinit();
|
||||
rows.deinit();
|
||||
}
|
||||
const lag = try compute(allocator, rows.items, &.{}, &.{}, .{ .field = "score", .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, 30), lag[1].int);
|
||||
try std.testing.expectEqual(@as(i64, 10), lag[2].int);
|
||||
}
|
||||
|
||||
test "lag/lead 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 lag = try compute(allocator, rows.items, &.{}, &order, .{ .field = "name", .kind = .lag }, .rows);
|
||||
defer freeResults(allocator, lag);
|
||||
try std.testing.expectEqualStrings("mango", lag[1].text);
|
||||
try std.testing.expectEqualStrings("apple", lag[2].text);
|
||||
try std.testing.expect(lag[1].text.ptr != names[0].ptr);
|
||||
}
|
||||
|
||||
test "lag/lead require a field, like sum/avg/min/max" {
|
||||
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 = .lag }, .rows));
|
||||
try std.testing.expectError(error.MissingField, compute(allocator, rows.items, &.{}, &.{}, .{ .field = "", .kind = .lead }, .rows));
|
||||
}
|
||||
|
||||
test "lag/lead reject a RANGE frame outright, like the ranking family" {
|
||||
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" }};
|
||||
try std.testing.expectError(error.RangeNotApplicable, compute(allocator, rows.items, &.{}, &order, .{ .field = "score", .kind = .lag }, .range));
|
||||
try std.testing.expectError(error.RangeNotApplicable, compute(allocator, rows.items, &.{}, &order, .{ .field = "score", .kind = .lead }, .range));
|
||||
}
|
||||
|
||||
test "lag/lead propagate 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 = .lag }, .rows));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user