feat: extend window
This commit is contained in:
1 parent
1e1e7ebdfc
commit
3de0c11f63
1 file changed
+158
+158
@@ -819,3 +819,161 @@ test "row_number restarts at 1 per partition" {
|
||||
const results = try compute(allocator, rows.items, &partition, &order, .{ .field = "", .kind = .row_number }, .rows);
|
||||
defer freeResults(allocator, results);
|
||||
|
||||
// warsaw: score 5 (idx0), score 1 (idx2) -> sorted asc: idx2=1st, idx0=2nd.
|
||||
try std.testing.expectEqual(@as(i64, 2), results[0].int);
|
||||
try std.testing.expectEqual(@as(i64, 1), results[2].int);
|
||||
// krakow: score 9 (idx1), score 2 (idx3) -> sorted asc: idx3=1st, idx1=2nd.
|
||||
try std.testing.expectEqual(@as(i64, 2), results[1].int);
|
||||
try std.testing.expectEqual(@as(i64, 1), results[3].int);
|
||||
}
|
||||
|
||||
test "rank skips over ties, dense_rank does not" {
|
||||
const allocator = std.testing.allocator;
|
||||
var rows = std.ArrayList(Row).init(allocator);
|
||||
defer {
|
||||
for (rows.items) |*r| r.deinit();
|
||||
rows.deinit();
|
||||
}
|
||||
// Scores: 10, 10, 20, 30, 30, 30 (already in ascending order so the
|
||||
// sort is a no-op tie-preserving identity, keeping the expected
|
||||
// positions easy to reason about).
|
||||
const scores = [_]i64{ 10, 10, 20, 30, 30, 30 };
|
||||
for (scores) |s| {
|
||||
try rows.append(try makeRow(allocator, &.{
|
||||
.{ .name = "score", .value = .{ .int = s } },
|
||||
}));
|
||||
}
|
||||
|
||||
const order = [_]querymod.OrderBy{.{ .field = "score" }};
|
||||
|
||||
const ranks = try compute(allocator, rows.items, &.{}, &order, .{ .field = "", .kind = .rank }, .rows);
|
||||
defer freeResults(allocator, ranks);
|
||||
const expectedRank = [_]i64{ 1, 1, 3, 4, 4, 4 };
|
||||
for (expectedRank, 0..) |want, i| try std.testing.expectEqual(want, ranks[i].int);
|
||||
|
||||
const dense = try compute(allocator, rows.items, &.{}, &order, .{ .field = "", .kind = .dense_rank }, .rows);
|
||||
defer freeResults(allocator, dense);
|
||||
const expectedDense = [_]i64{ 1, 1, 2, 3, 3, 3 };
|
||||
for (expectedDense, 0..) |want, i| try std.testing.expectEqual(want, dense[i].int);
|
||||
}
|
||||
|
||||
test "rank and dense_rank without ORDER BY: whole partition ties, every row is 1" {
|
||||
const allocator = std.testing.allocator;
|
||||
var rows = try threeRowsOnePartition(allocator);
|
||||
defer {
|
||||
for (rows.items) |*r| r.deinit();
|
||||
rows.deinit();
|
||||
}
|
||||
|
||||
const ranks = try compute(allocator, rows.items, &.{}, &.{}, .{ .field = "", .kind = .rank }, .rows);
|
||||
defer freeResults(allocator, ranks);
|
||||
for (ranks) |r| try std.testing.expectEqual(@as(i64, 1), r.int);
|
||||
|
||||
const dense = try compute(allocator, rows.items, &.{}, &.{}, .{ .field = "", .kind = .dense_rank }, .rows);
|
||||
defer freeResults(allocator, dense);
|
||||
for (dense) |r| try std.testing.expectEqual(@as(i64, 1), r.int);
|
||||
|
||||
// row_number still needs no ORDER BY - just first-seen position.
|
||||
const nums = try compute(allocator, rows.items, &.{}, &.{}, .{ .field = "", .kind = .row_number }, .rows);
|
||||
defer freeResults(allocator, nums);
|
||||
try std.testing.expectEqual(@as(i64, 1), nums[0].int);
|
||||
try std.testing.expectEqual(@as(i64, 2), nums[1].int);
|
||||
try std.testing.expectEqual(@as(i64, 3), nums[2].int);
|
||||
}
|
||||
|
||||
test "running sum walks cumulatively in ORDER BY order, per partition" {
|
||||
const allocator = std.testing.allocator;
|
||||
var rows = std.ArrayList(Row).init(allocator);
|
||||
defer {
|
||||
for (rows.items) |*r| r.deinit();
|
||||
rows.deinit();
|
||||
}
|
||||
// warsaw, in input order: 100, 250 (already ascending by "day").
|
||||
// krakow: 40.
|
||||
const data = [_]struct { city: []const u8, day: i64, amount: i64 }{
|
||||
.{ .city = "warsaw", .day = 1, .amount = 100 },
|
||||
.{ .city = "krakow", .day = 1, .amount = 40 },
|
||||
.{ .city = "warsaw", .day = 2, .amount = 250 },
|
||||
};
|
||||
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 results = try compute(allocator, rows.items, &partition, &order, .{ .field = "amount", .kind = .sum }, .rows);
|
||||
defer freeResults(allocator, results);
|
||||
|
||||
try std.testing.expectEqual(@as(i64, 100), results[0].int); // warsaw day1: running=100
|
||||
try std.testing.expectEqual(@as(i64, 40), results[1].int); // krakow day1: running=40
|
||||
try std.testing.expectEqual(@as(i64, 350), results[2].int); // warsaw day2: running=100+250
|
||||
}
|
||||
|
||||
test "RANGE frame: rows tied on ORDER BY share the tie group's combined total, ROWS gives each its own" {
|
||||
const allocator = std.testing.allocator;
|
||||
var rows = std.ArrayList(Row).init(allocator);
|
||||
defer {
|
||||
for (rows.items) |*r| r.deinit();
|
||||
rows.deinit();
|
||||
}
|
||||
// One partition, ordered by "day": two rows tied at day=1 (10, 20),
|
||||
// then one row at day=2 (5).
|
||||
const data = [_]struct { day: i64, amount: i64 }{
|
||||
.{ .day = 1, .amount = 10 },
|
||||
.{ .day = 1, .amount = 20 },
|
||||
.{ .day = 2, .amount = 5 },
|
||||
};
|
||||
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 rowsResult = try compute(allocator, rows.items, &.{}, &order, .{ .field = "amount", .kind = .sum }, .rows);
|
||||
defer freeResults(allocator, rowsResult);
|
||||
// ROWS: each physical row gets its own running total, even tied ones.
|
||||
try std.testing.expectEqual(@as(i64, 10), rowsResult[0].int);
|
||||
try std.testing.expectEqual(@as(i64, 30), rowsResult[1].int);
|
||||
try std.testing.expectEqual(@as(i64, 35), rowsResult[2].int);
|
||||
|
||||
const rangeResult = try compute(allocator, rows.items, &.{}, &order, .{ .field = "amount", .kind = .sum }, .range);
|
||||
defer freeResults(allocator, rangeResult);
|
||||
// RANGE: both day=1 rows share the GROUP's combined total (10+20=30),
|
||||
// not their own individual position within the tie; day=2 then adds 5.
|
||||
try std.testing.expectEqual(@as(i64, 30), rangeResult[0].int);
|
||||
try std.testing.expectEqual(@as(i64, 30), rangeResult[1].int);
|
||||
try std.testing.expectEqual(@as(i64, 35), rangeResult[2].int);
|
||||
}
|
||||
|
||||
test "RANGE frame on count/avg/min/max matches the same tie-group grouping as sum" {
|
||||
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 = 30 },
|
||||
.{ .day = 1, .amount = 10 },
|
||||
.{ .day = 2, .amount = 20 },
|
||||
};
|
||||
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 counts = try compute(allocator, rows.items, &.{}, &order, .{ .field = "", .kind = .count }, .range);
|
||||
defer freeResults(allocator, counts);
|
||||
try std.testing.expectEqual(@as(i64, 2), counts[0].int);
|
||||
try std.testing.expectEqual(@as(i64, 2), counts[1].int);
|
||||
try std.testing.expectEqual(@as(i64, 3), counts[2].int);
|
||||
|
||||
Reference in new issue
Block a user