feat: add fourGroupRows and firstLettersOf to aggregate
This commit is contained in:
1 parent
b8abb45b61
commit
289055c4e5
1 file changed
+161
@@ -810,3 +810,164 @@ test "count_distinct on zero observed values reports zero, not none" {
|
|||||||
try std.testing.expectEqual(@as(i64, 0), values[0].int);
|
try std.testing.expectEqual(@as(i64, 0), values[0].int);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "count_distinct propagates OutOfMemory without leaking already-tracked distinct values" {
|
||||||
|
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{ "red", "blue", "green" };
|
||||||
|
for (tags) |t| {
|
||||||
|
try rows.append(try makeRow(allocator, &.{
|
||||||
|
.{ .name = "g", .value = .{ .text = "x" } },
|
||||||
|
.{ .name = "tag", .value = .{ .text = t } },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
var failing = std.testing.FailingAllocator.init(allocator, .{ .fail_index = 6 });
|
||||||
|
const specs = [_]AggSpec{.{ .field = "tag", .kind = .count_distinct }};
|
||||||
|
var gb = GroupBy.init(failing.allocator(), "g", &specs);
|
||||||
|
defer gb.deinit();
|
||||||
|
|
||||||
|
var sawError = false;
|
||||||
|
for (rows.items) |*r| {
|
||||||
|
gb.observe(r) catch {
|
||||||
|
sawError = true;
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
try std.testing.expect(sawError);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "rows missing the group field are excluded from every group" {
|
||||||
|
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" } }}));
|
||||||
|
try rows.append(try makeRow(allocator, &.{})); // no "team" field at all
|
||||||
|
|
||||||
|
const specs = [_]AggSpec{.{ .field = "", .kind = .count }};
|
||||||
|
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 "group order is first-seen, not sorted" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
var rows = std.ArrayList(Row).init(allocator);
|
||||||
|
defer {
|
||||||
|
for (rows.items) |*r| r.deinit();
|
||||||
|
rows.deinit();
|
||||||
|
}
|
||||||
|
const order = [_][]const u8{ "z", "a", "m" };
|
||||||
|
for (order) |g| {
|
||||||
|
try rows.append(try makeRow(allocator, &.{.{ .name = "g", .value = .{ .text = g } }}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const specs = [_]AggSpec{.{ .field = "", .kind = .count }};
|
||||||
|
const groups = try run(allocator, rows.items, "g", &specs);
|
||||||
|
defer freeGroupRows(allocator, groups);
|
||||||
|
|
||||||
|
try std.testing.expectEqual(@as(usize, 3), groups.len);
|
||||||
|
try std.testing.expectEqualStrings("z", groups[0].group.text);
|
||||||
|
try std.testing.expectEqualStrings("a", groups[1].group.text);
|
||||||
|
try std.testing.expectEqualStrings("m", groups[2].group.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "runGlobal aggregates every row into a single result with no grouping" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
var rows = std.ArrayList(Row).init(allocator);
|
||||||
|
defer {
|
||||||
|
for (rows.items) |*r| r.deinit();
|
||||||
|
rows.deinit();
|
||||||
|
}
|
||||||
|
var i: i64 = 1;
|
||||||
|
while (i <= 4) : (i += 1) {
|
||||||
|
try rows.append(try makeRow(allocator, &.{.{ .name = "x", .value = .{ .int = i } }}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const specs = [_]AggSpec{
|
||||||
|
.{ .field = "", .kind = .count },
|
||||||
|
.{ .field = "x", .kind = .sum },
|
||||||
|
.{ .field = "x", .kind = .max },
|
||||||
|
};
|
||||||
|
const values = try runGlobal(allocator, rows.items, &specs);
|
||||||
|
defer freeGlobalResult(allocator, values);
|
||||||
|
|
||||||
|
try std.testing.expectEqual(@as(i64, 4), values[0].int);
|
||||||
|
try std.testing.expectEqual(@as(i64, 10), values[1].int);
|
||||||
|
try std.testing.expectEqual(@as(i64, 4), values[2].int);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "runGlobal on zero rows still reports identity results, not an error" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
const rows: []const Row = &.{};
|
||||||
|
const specs = [_]AggSpec{
|
||||||
|
.{ .field = "", .kind = .count },
|
||||||
|
.{ .field = "x", .kind = .sum },
|
||||||
|
.{ .field = "x", .kind = .max },
|
||||||
|
};
|
||||||
|
const values = try runGlobal(allocator, rows, &specs);
|
||||||
|
defer freeGlobalResult(allocator, values);
|
||||||
|
|
||||||
|
try std.testing.expectEqual(@as(i64, 0), values[0].int);
|
||||||
|
try std.testing.expectEqual(@as(i64, 0), values[1].int);
|
||||||
|
try std.testing.expectEqual(rowmod.ValueTag.none, @as(rowmod.ValueTag, values[2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "GroupBy propagates OutOfMemory without leaking any accumulated group" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
var rows = std.ArrayList(Row).init(allocator);
|
||||||
|
defer {
|
||||||
|
for (rows.items) |*r| r.deinit();
|
||||||
|
rows.deinit();
|
||||||
|
}
|
||||||
|
const groupNames = [_][]const u8{ "a", "b", "c" };
|
||||||
|
for (groupNames) |g| {
|
||||||
|
try rows.append(try makeRow(allocator, &.{
|
||||||
|
.{ .name = "g", .value = .{ .text = g } },
|
||||||
|
.{ .name = "x", .value = .{ .int = 1 } },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
var failing = std.testing.FailingAllocator.init(allocator, .{ .fail_index = 6 });
|
||||||
|
const specs = [_]AggSpec{.{ .field = "x", .kind = .sum }};
|
||||||
|
var gb = GroupBy.init(failing.allocator(), "g", &specs);
|
||||||
|
defer gb.deinit();
|
||||||
|
|
||||||
|
var sawError = false;
|
||||||
|
for (rows.items) |*r| {
|
||||||
|
gb.observe(r) catch {
|
||||||
|
sawError = true;
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
try std.testing.expect(sawError);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fourGroupRows(allocator: Allocator) ![]GroupRow {
|
||||||
|
var rows = std.ArrayList(Row).init(allocator);
|
||||||
|
defer {
|
||||||
|
for (rows.items) |*r| r.deinit();
|
||||||
|
rows.deinit();
|
||||||
|
}
|
||||||
|
const groupNames = [_][]const u8{ "a", "b", "c", "d" };
|
||||||
|
for (groupNames) |g| {
|
||||||
|
try rows.append(try makeRow(allocator, &.{.{ .name = "g", .value = .{ .text = g } }}));
|
||||||
|
}
|
||||||
|
const specs = [_]AggSpec{.{ .field = "", .kind = .count }};
|
||||||
|
return run(allocator, rows.items, "g", &specs);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn firstLettersOf(rows: []const GroupRow) [4]u8 {
|
||||||
|
var out: [4]u8 = undefined;
|
||||||
|
for (rows, 0..) |r, i| out[i] = r.group.text[0];
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
Reference in new issue
Block a user