feat: build out window
This commit is contained in:
1 parent
95f2c134c3
commit
ead8304bb7
1 file changed
+159
+159
@@ -1136,3 +1136,162 @@ test "count_distinct runs cumulatively per partition, repeats do not grow it" {
|
|||||||
try std.testing.expectEqual(@as(i64, 2), results[3].int); // warsaw day3: {a,b} again, "a" repeats
|
try std.testing.expectEqual(@as(i64, 2), results[3].int); // warsaw day3: {a,b} again, "a" repeats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "count_distinct without ORDER BY gives every row the whole-partition distinct total" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
var rows = std.ArrayList(Row).init(allocator);
|
||||||
|
defer {
|
||||||
|
for (rows.items) |*r| r.deinit();
|
||||||
|
rows.deinit();
|
||||||
|
}
|
||||||
|
const tags = [_][]const u8{ "x", "y", "x", "z" };
|
||||||
|
for (tags) |t| {
|
||||||
|
try rows.append(try makeRow(allocator, &.{.{ .name = "tag", .value = .{ .text = t } }}));
|
||||||
|
}
|
||||||
|
const results = try compute(allocator, rows.items, &.{}, &.{}, .{ .field = "tag", .kind = .count_distinct }, .rows);
|
||||||
|
defer freeResults(allocator, results);
|
||||||
|
for (results) |r| try std.testing.expectEqual(@as(i64, 3), r.int); // {x,y,z}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "count_distinct RANGE frame: a tied group's members share one snapshot after the WHOLE group is observed" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
var rows = std.ArrayList(Row).init(allocator);
|
||||||
|
defer {
|
||||||
|
for (rows.items) |*r| r.deinit();
|
||||||
|
rows.deinit();
|
||||||
|
}
|
||||||
|
// Tied at day=1: tags "a","b" (2 distinct together, not counted one at a
|
||||||
|
// time within the tie like ROWS would). Then day=2 adds "a" again (no
|
||||||
|
// growth) then "c" (grows to 3).
|
||||||
|
const data = [_]struct { day: i64, tag: []const u8 }{
|
||||||
|
.{ .day = 1, .tag = "a" },
|
||||||
|
.{ .day = 1, .tag = "b" },
|
||||||
|
.{ .day = 2, .tag = "a" },
|
||||||
|
.{ .day = 2, .tag = "c" },
|
||||||
|
};
|
||||||
|
for (data) |d| {
|
||||||
|
try rows.append(try makeRow(allocator, &.{
|
||||||
|
.{ .name = "day", .value = .{ .int = d.day } },
|
||||||
|
.{ .name = "tag", .value = .{ .text = d.tag } },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
const order = [_]querymod.OrderBy{.{ .field = "day" }};
|
||||||
|
|
||||||
|
const rowsResult = try compute(allocator, rows.items, &.{}, &order, .{ .field = "tag", .kind = .count_distinct }, .rows);
|
||||||
|
defer freeResults(allocator, rowsResult);
|
||||||
|
// ROWS: each physical row gets its own running distinct count.
|
||||||
|
try std.testing.expectEqual(@as(i64, 1), rowsResult[0].int); // {a}
|
||||||
|
try std.testing.expectEqual(@as(i64, 2), rowsResult[1].int); // {a,b}
|
||||||
|
try std.testing.expectEqual(@as(i64, 2), rowsResult[2].int); // {a,b}, "a" repeats
|
||||||
|
try std.testing.expectEqual(@as(i64, 3), rowsResult[3].int); // {a,b,c}
|
||||||
|
|
||||||
|
const rangeResult = try compute(allocator, rows.items, &.{}, &order, .{ .field = "tag", .kind = .count_distinct }, .range);
|
||||||
|
defer freeResults(allocator, rangeResult);
|
||||||
|
// RANGE: both day=1 rows share the GROUP's combined distinct count (2),
|
||||||
|
// not their own individual position within the tie; day=2 group adds
|
||||||
|
// "c" (a repeats), landing on 3 for both its members.
|
||||||
|
try std.testing.expectEqual(@as(i64, 2), rangeResult[0].int);
|
||||||
|
try std.testing.expectEqual(@as(i64, 2), rangeResult[1].int);
|
||||||
|
try std.testing.expectEqual(@as(i64, 3), rangeResult[2].int);
|
||||||
|
try std.testing.expectEqual(@as(i64, 3), rangeResult[3].int);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "count_distinct requires a field like sum/avg/min/max, unlike plain count" {
|
||||||
|
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 = .count_distinct }, .rows));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "count_distinct ignores rows missing the field entirely, never counts none as a value" {
|
||||||
|
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 = "other", .value = .{ .int = 1 } }})); // missing "tag"
|
||||||
|
try rows.append(try makeRow(allocator, &.{.{ .name = "tag", .value = .{ .text = "a" } }}));
|
||||||
|
const results = try compute(allocator, rows.items, &.{}, &.{}, .{ .field = "tag", .kind = .count_distinct }, .rows);
|
||||||
|
defer freeResults(allocator, results);
|
||||||
|
for (results) |r| try std.testing.expectEqual(@as(i64, 1), r.int); // only "a" ever counted
|
||||||
|
}
|
||||||
|
|
||||||
|
test "count_distinct propagates OutOfMemory without leaking already-tracked distinct keys" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
var rows = std.ArrayList(Row).init(allocator);
|
||||||
|
defer {
|
||||||
|
for (rows.items) |*r| r.deinit();
|
||||||
|
rows.deinit();
|
||||||
|
}
|
||||||
|
const tags = [_][]const u8{ "mango", "apple", "kiwi" };
|
||||||
|
for (tags) |t| {
|
||||||
|
try rows.append(try makeRow(allocator, &.{.{ .name = "tag", .value = .{ .text = t } }}));
|
||||||
|
}
|
||||||
|
var failing = std.testing.FailingAllocator.init(allocator, .{ .fail_index = 6 });
|
||||||
|
try std.testing.expectError(error.OutOfMemory, compute(failing.allocator(), rows.items, &.{}, &.{}, .{ .field = "tag", .kind = .count_distinct }, .rows));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "count_distinct alongside lag/lead/first_value/running sum/ranking in one computeMany call" {
|
||||||
|
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, tag: []const u8, amount: i64 }{
|
||||||
|
.{ .day = 1, .tag = "a", .amount = 10 },
|
||||||
|
.{ .day = 2, .tag = "b", .amount = 20 },
|
||||||
|
.{ .day = 3, .tag = "a", .amount = 30 },
|
||||||
|
};
|
||||||
|
for (data) |d| {
|
||||||
|
try rows.append(try makeRow(allocator, &.{
|
||||||
|
.{ .name = "day", .value = .{ .int = d.day } },
|
||||||
|
.{ .name = "tag", .value = .{ .text = d.tag } },
|
||||||
|
.{ .name = "amount", .value = .{ .int = d.amount } },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
const order = [_]querymod.OrderBy{.{ .field = "day" }};
|
||||||
|
const specs = [_]WindowSpec{
|
||||||
|
.{ .field = "tag", .kind = .count_distinct },
|
||||||
|
.{ .field = "amount", .kind = .lag },
|
||||||
|
.{ .field = "amount", .kind = .sum },
|
||||||
|
.{ .field = "", .kind = .row_number },
|
||||||
|
};
|
||||||
|
const results = try computeMany(allocator, rows.items, &.{}, &order, &specs, .rows);
|
||||||
|
defer freeManyResults(allocator, results);
|
||||||
|
|
||||||
|
try std.testing.expectEqual(@as(i64, 1), results[0][0].int); // count_distinct {a}
|
||||||
|
try std.testing.expectEqual(Value{ .none = {} }, results[0][1]); // lag: no predecessor
|
||||||
|
try std.testing.expectEqual(@as(i64, 10), results[0][2].int); // running sum
|
||||||
|
try std.testing.expectEqual(@as(i64, 1), results[0][3].int); // row_number
|
||||||
|
|
||||||
|
try std.testing.expectEqual(@as(i64, 2), results[1][0].int); // {a,b}
|
||||||
|
try std.testing.expectEqual(@as(i64, 10), results[1][1].int); // lag -> day1's amount
|
||||||
|
try std.testing.expectEqual(@as(i64, 30), results[1][2].int);
|
||||||
|
try std.testing.expectEqual(@as(i64, 2), results[1][3].int);
|
||||||
|
|
||||||
|
try std.testing.expectEqual(@as(i64, 2), results[2][0].int); // {a,b}, "a" repeats
|
||||||
|
try std.testing.expectEqual(@as(i64, 20), results[2][1].int); // lag -> day2's amount
|
||||||
|
try std.testing.expectEqual(@as(i64, 60), results[2][2].int);
|
||||||
|
try std.testing.expectEqual(@as(i64, 3), results[2][3].int);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "running min and max over a text field, independently duped per row" {
|
||||||
|
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" }};
|
||||||
|
|
||||||
Reference in new issue
Block a user