feat: expand window
This commit is contained in:
1 parent
3de0c11f63
commit
95f2c134c3
1 file changed
+159
+159
@@ -977,3 +977,162 @@ test "RANGE frame on count/avg/min/max matches the same tie-group grouping as su
|
|||||||
try std.testing.expectEqual(@as(i64, 2), counts[1].int);
|
try std.testing.expectEqual(@as(i64, 2), counts[1].int);
|
||||||
try std.testing.expectEqual(@as(i64, 3), counts[2].int);
|
try std.testing.expectEqual(@as(i64, 3), counts[2].int);
|
||||||
|
|
||||||
|
const mins = try compute(allocator, rows.items, &.{}, &order, .{ .field = "amount", .kind = .min }, .range);
|
||||||
|
defer freeResults(allocator, mins);
|
||||||
|
try std.testing.expectEqual(@as(i64, 10), mins[0].int);
|
||||||
|
try std.testing.expectEqual(@as(i64, 10), mins[1].int);
|
||||||
|
try std.testing.expectEqual(@as(i64, 10), mins[2].int);
|
||||||
|
|
||||||
|
const maxs = try compute(allocator, rows.items, &.{}, &order, .{ .field = "amount", .kind = .max }, .range);
|
||||||
|
defer freeResults(allocator, maxs);
|
||||||
|
try std.testing.expectEqual(@as(i64, 30), maxs[0].int);
|
||||||
|
try std.testing.expectEqual(@as(i64, 30), maxs[1].int);
|
||||||
|
try std.testing.expectEqual(@as(i64, 30), maxs[2].int);
|
||||||
|
|
||||||
|
const avgs = try compute(allocator, rows.items, &.{}, &order, .{ .field = "amount", .kind = .avg }, .range);
|
||||||
|
defer freeResults(allocator, avgs);
|
||||||
|
try std.testing.expectApproxEqAbs(@as(f64, 20), avgs[0].float, 0.0001);
|
||||||
|
try std.testing.expectApproxEqAbs(@as(f64, 20), avgs[1].float, 0.0001);
|
||||||
|
try std.testing.expectApproxEqAbs(@as(f64, 20), avgs[2].float, 0.0001);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "RANGE with no ties reduces to the same result as ROWS" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
var rows = std.ArrayList(Row).init(allocator);
|
||||||
|
defer {
|
||||||
|
for (rows.items) |*r| r.deinit();
|
||||||
|
rows.deinit();
|
||||||
|
}
|
||||||
|
const data = [_]i64{ 3, 1, 4 };
|
||||||
|
for (data) |amount| {
|
||||||
|
try rows.append(try makeRow(allocator, &.{.{ .name = "amount", .value = .{ .int = amount } }}));
|
||||||
|
}
|
||||||
|
const order = [_]querymod.OrderBy{.{ .field = "amount" }};
|
||||||
|
|
||||||
|
const rowsResult = try compute(allocator, rows.items, &.{}, &order, .{ .field = "amount", .kind = .sum }, .rows);
|
||||||
|
defer freeResults(allocator, rowsResult);
|
||||||
|
const rangeResult = try compute(allocator, rows.items, &.{}, &order, .{ .field = "amount", .kind = .sum }, .range);
|
||||||
|
defer freeResults(allocator, rangeResult);
|
||||||
|
for (rowsResult, rangeResult) |r, g| try std.testing.expectEqual(r.int, g.int);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "RANGE with every row tied gives everyone the whole-partition total, like no ORDER BY at all" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
var rows = try threeRowsOnePartition(allocator); // scores 30, 10, 20, all same "team"
|
||||||
|
defer {
|
||||||
|
for (rows.items) |*r| r.deinit();
|
||||||
|
rows.deinit();
|
||||||
|
}
|
||||||
|
const order = [_]querymod.OrderBy{.{ .field = "team" }}; // every row ties on "team"
|
||||||
|
|
||||||
|
const results = try compute(allocator, rows.items, &.{}, &order, .{ .field = "score", .kind = .sum }, .range);
|
||||||
|
defer freeResults(allocator, results);
|
||||||
|
for (results) |r| try std.testing.expectEqual(@as(i64, 60), r.int);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "frame is rejected outright for the ranking family, never silently ignored" {
|
||||||
|
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 = "", .kind = .row_number }, .range));
|
||||||
|
try std.testing.expectError(error.RangeNotApplicable, compute(allocator, rows.items, &.{}, &order, .{ .field = "", .kind = .rank }, .range));
|
||||||
|
try std.testing.expectError(error.RangeNotApplicable, compute(allocator, rows.items, &.{}, &order, .{ .field = "", .kind = .dense_rank }, .range));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "compute propagates OutOfMemory without leaking, mid-way through a RANGE tie group" {
|
||||||
|
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", "mango", "apple" };
|
||||||
|
for (names) |n| {
|
||||||
|
try rows.append(try makeRow(allocator, &.{.{ .name = "name", .value = .{ .text = n } }}));
|
||||||
|
}
|
||||||
|
const order = [_]querymod.OrderBy{.{ .field = "name" }};
|
||||||
|
|
||||||
|
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 = .min }, .range));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "sum without ORDER BY gives every row the whole-partition total" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
var rows = try threeRowsOnePartition(allocator);
|
||||||
|
defer {
|
||||||
|
for (rows.items) |*r| r.deinit();
|
||||||
|
rows.deinit();
|
||||||
|
}
|
||||||
|
// scores 30, 10, 20 -> total 60, same value for every row.
|
||||||
|
const results = try compute(allocator, rows.items, &.{}, &.{}, .{ .field = "score", .kind = .sum }, .rows);
|
||||||
|
defer freeResults(allocator, results);
|
||||||
|
for (results) |r| try std.testing.expectEqual(@as(i64, 60), r.int);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "avg running and count star running" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
var rows = std.ArrayList(Row).init(allocator);
|
||||||
|
defer {
|
||||||
|
for (rows.items) |*r| r.deinit();
|
||||||
|
rows.deinit();
|
||||||
|
}
|
||||||
|
const scores = [_]i64{ 10, 20, 30 };
|
||||||
|
for (scores) |s| {
|
||||||
|
try rows.append(try makeRow(allocator, &.{
|
||||||
|
.{ .name = "score", .value = .{ .int = s } },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
const order = [_]querymod.OrderBy{.{ .field = "score" }};
|
||||||
|
|
||||||
|
const avgs = try compute(allocator, rows.items, &.{}, &order, .{ .field = "score", .kind = .avg }, .rows);
|
||||||
|
defer freeResults(allocator, avgs);
|
||||||
|
try std.testing.expectApproxEqAbs(@as(f64, 10), avgs[0].float, 1e-9);
|
||||||
|
try std.testing.expectApproxEqAbs(@as(f64, 15), avgs[1].float, 1e-9);
|
||||||
|
try std.testing.expectApproxEqAbs(@as(f64, 20), avgs[2].float, 1e-9);
|
||||||
|
|
||||||
|
const counts = try compute(allocator, rows.items, &.{}, &order, .{ .field = "", .kind = .count }, .rows);
|
||||||
|
defer freeResults(allocator, counts);
|
||||||
|
try std.testing.expectEqual(@as(i64, 1), counts[0].int);
|
||||||
|
try std.testing.expectEqual(@as(i64, 2), counts[1].int);
|
||||||
|
try std.testing.expectEqual(@as(i64, 3), counts[2].int);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "count_distinct runs cumulatively per partition, repeats do not grow it" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
var rows = std.ArrayList(Row).init(allocator);
|
||||||
|
defer {
|
||||||
|
for (rows.items) |*r| r.deinit();
|
||||||
|
rows.deinit();
|
||||||
|
}
|
||||||
|
// warsaw, in "day" order: tag a, tag b, tag a (repeat) -> distinct count
|
||||||
|
// grows 1, 2, 2 (the third row's "a" was already seen).
|
||||||
|
// krakow: tag c -> distinct count 1.
|
||||||
|
const data = [_]struct { city: []const u8, day: i64, tag: []const u8 }{
|
||||||
|
.{ .city = "warsaw", .day = 1, .tag = "a" },
|
||||||
|
.{ .city = "krakow", .day = 1, .tag = "c" },
|
||||||
|
.{ .city = "warsaw", .day = 2, .tag = "b" },
|
||||||
|
.{ .city = "warsaw", .day = 3, .tag = "a" },
|
||||||
|
};
|
||||||
|
for (data) |d| {
|
||||||
|
try rows.append(try makeRow(allocator, &.{
|
||||||
|
.{ .name = "city", .value = .{ .text = d.city } },
|
||||||
|
.{ .name = "day", .value = .{ .int = d.day } },
|
||||||
|
.{ .name = "tag", .value = .{ .text = d.tag } },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const partition = [_][]const u8{"city"};
|
||||||
|
const order = [_]querymod.OrderBy{.{ .field = "day" }};
|
||||||
|
const results = try compute(allocator, rows.items, &partition, &order, .{ .field = "tag", .kind = .count_distinct }, .rows);
|
||||||
|
defer freeResults(allocator, results);
|
||||||
|
|
||||||
|
try std.testing.expectEqual(@as(i64, 1), results[0].int); // warsaw day1: {a}
|
||||||
|
try std.testing.expectEqual(@as(i64, 1), results[1].int); // krakow day1: {c}
|
||||||
|
try std.testing.expectEqual(@as(i64, 2), results[2].int); // warsaw day2: {a,b}
|
||||||
|
try std.testing.expectEqual(@as(i64, 2), results[3].int); // warsaw day3: {a,b} again, "a" repeats
|
||||||
|
}
|
||||||
|
|
||||||
Reference in new issue
Block a user