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 } },
|
.{ .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