feat: extend aggregate
This commit is contained in:
1 parent
96f2142f82
commit
b8abb45b61
1 file changed
+152
@@ -658,3 +658,155 @@ test "sum stays exact integer until a float value forces it to switch" {
|
||||
.{ .name = "x", .value = .{ .int = 4 } },
|
||||
}));
|
||||
|
||||
const specs = [_]AggSpec{.{ .field = "x", .kind = .sum }};
|
||||
{
|
||||
const groups = try run(allocator, rows.items, "g", &specs);
|
||||
defer freeGroupRows(allocator, groups);
|
||||
// All-int input: exact int result, not a float with rounding risk.
|
||||
try std.testing.expectEqual(rowmod.ValueTag.int, @as(rowmod.ValueTag, groups[0].values[0]));
|
||||
try std.testing.expectEqual(@as(i64, 7), groups[0].values[0].int);
|
||||
}
|
||||
|
||||
try rows.append(try makeRow(allocator, &.{
|
||||
.{ .name = "g", .value = .{ .int = 1 } },
|
||||
.{ .name = "x", .value = .{ .float = 0.5 } },
|
||||
}));
|
||||
{
|
||||
const groups = try run(allocator, rows.items, "g", &specs);
|
||||
defer freeGroupRows(allocator, groups);
|
||||
try std.testing.expectEqual(rowmod.ValueTag.float, @as(rowmod.ValueTag, groups[0].values[0]));
|
||||
try std.testing.expectApproxEqAbs(@as(f64, 7.5), groups[0].values[0].float, 1e-9);
|
||||
}
|
||||
}
|
||||
|
||||
test "sum reports overflow instead of silently wrapping" {
|
||||
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 = "g", .value = .{ .int = 1 } },
|
||||
.{ .name = "x", .value = .{ .int = std.math.maxInt(i64) } },
|
||||
}));
|
||||
try rows.append(try makeRow(allocator, &.{
|
||||
.{ .name = "g", .value = .{ .int = 1 } },
|
||||
.{ .name = "x", .value = .{ .int = 1 } },
|
||||
}));
|
||||
|
||||
const specs = [_]AggSpec{.{ .field = "x", .kind = .sum }};
|
||||
var gb = GroupBy.init(allocator, "g", &specs);
|
||||
defer gb.deinit();
|
||||
try gb.observe(&rows.items[0]);
|
||||
try std.testing.expectError(error.Overflow, gb.observe(&rows.items[1]));
|
||||
}
|
||||
|
||||
test "sum on a non-numeric field errors without leaking accumulated state" {
|
||||
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 = "g", .value = .{ .int = 1 } },
|
||||
.{ .name = "x", .value = .{ .text = "nope" } },
|
||||
}));
|
||||
|
||||
const specs = [_]AggSpec{.{ .field = "x", .kind = .sum }};
|
||||
var gb = GroupBy.init(allocator, "g", &specs);
|
||||
defer gb.deinit();
|
||||
try std.testing.expectError(error.NonNumericField, gb.observe(&rows.items[0]));
|
||||
}
|
||||
|
||||
test "min and max use the total order across owned text values" {
|
||||
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{ "delta", "alpha", "charlie", "bravo" };
|
||||
for (names) |n| {
|
||||
try rows.append(try makeRow(allocator, &.{
|
||||
.{ .name = "g", .value = .{ .int = 1 } },
|
||||
.{ .name = "name", .value = .{ .text = n } },
|
||||
}));
|
||||
}
|
||||
|
||||
const specs = [_]AggSpec{
|
||||
.{ .field = "name", .kind = .min },
|
||||
.{ .field = "name", .kind = .max },
|
||||
};
|
||||
const groups = try run(allocator, rows.items, "g", &specs);
|
||||
defer freeGroupRows(allocator, groups);
|
||||
|
||||
try std.testing.expectEqualStrings("alpha", groups[0].values[0].text);
|
||||
try std.testing.expectEqualStrings("delta", groups[0].values[1].text);
|
||||
}
|
||||
|
||||
test "count_distinct counts unique field values once per group, repeats collapsed" {
|
||||
const allocator = std.testing.allocator;
|
||||
var rows = std.ArrayList(Row).init(allocator);
|
||||
defer {
|
||||
for (rows.items) |*r| r.deinit();
|
||||
rows.deinit();
|
||||
}
|
||||
const data = [_]struct { team: []const u8, tag: []const u8 }{
|
||||
.{ .team = "a", .tag = "red" },
|
||||
.{ .team = "a", .tag = "blue" },
|
||||
.{ .team = "a", .tag = "red" }, // repeat, must not double count
|
||||
.{ .team = "b", .tag = "green" },
|
||||
};
|
||||
for (data) |d| {
|
||||
try rows.append(try makeRow(allocator, &.{
|
||||
.{ .name = "team", .value = .{ .text = d.team } },
|
||||
.{ .name = "tag", .value = .{ .text = d.tag } },
|
||||
}));
|
||||
}
|
||||
|
||||
const specs = [_]AggSpec{.{ .field = "tag", .kind = .count_distinct }};
|
||||
const groups = try run(allocator, rows.items, "team", &specs);
|
||||
defer freeGroupRows(allocator, groups);
|
||||
|
||||
const a = findGroup(groups, .{ .text = "a" }).?;
|
||||
try std.testing.expectEqual(@as(i64, 2), a.values[0].int);
|
||||
const b = findGroup(groups, .{ .text = "b" }).?;
|
||||
try std.testing.expectEqual(@as(i64, 1), b.values[0].int);
|
||||
}
|
||||
|
||||
test "count_distinct ignores rows missing the field and 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 = "team", .value = .{ .text = "a" } },
|
||||
.{ .name = "tag", .value = .{ .text = "red" } },
|
||||
}));
|
||||
// No "tag" field on this row - must not create a phantom distinct value.
|
||||
try rows.append(try makeRow(allocator, &.{
|
||||
.{ .name = "team", .value = .{ .text = "a" } },
|
||||
}));
|
||||
|
||||
const specs = [_]AggSpec{.{ .field = "tag", .kind = .count_distinct }};
|
||||
const groups = try run(allocator, rows.items, "team", &specs);
|
||||
defer freeGroupRows(allocator, groups);
|
||||
|
||||
try std.testing.expectEqual(@as(usize, 1), groups.len);
|
||||
try std.testing.expectEqual(@as(i64, 1), groups[0].values[0].int);
|
||||
}
|
||||
|
||||
test "count_distinct on zero observed values reports zero, not none" {
|
||||
const allocator = std.testing.allocator;
|
||||
const rows: []const Row = &.{};
|
||||
const specs = [_]AggSpec{.{ .field = "tag", .kind = .count_distinct }};
|
||||
const values = try runGlobal(allocator, rows, &specs);
|
||||
defer freeGlobalResult(allocator, values);
|
||||
|
||||
try std.testing.expectEqual(@as(i64, 0), values[0].int);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user