feat: grow protocol

This commit is contained in:
dualfroz committed 2026-09-08 12:18:12 +02:00
1 parent 6ff5e832eb
commit c9960ef22a
1 file changed
+159
+159
View File
@@ -2670,3 +2670,162 @@ test "table_aggregate args round trip a groupField, several specs, and an expr"
try std.testing.expectEqualStrings("city", args.specs[3].field);
}
test "table_aggregate args round trip an empty groupField (global, no grouping) and empty expr" {
const allocator = std.testing.allocator;
const specs = [_]aggregatemod.AggSpec{.{ .field = "", .kind = .count }};
const payload = try encodeTableAggregate(allocator, "orders", "", &specs, 0, 0, &.{}, "", "");
defer allocator.free(payload);
const args = try decodeTableAggregate(allocator, payload);
defer allocator.free(args.specs);
defer allocator.free(args.orderBy);
try std.testing.expectEqualStrings("", args.groupField);
try std.testing.expectEqualStrings("", args.exprText);
try std.testing.expectEqual(@as(usize, 1), args.specs.len);
}
test "table_aggregate args round trip a nonzero offset and limit" {
const allocator = std.testing.allocator;
const specs = [_]aggregatemod.AggSpec{.{ .field = "", .kind = .count }};
const payload = try encodeTableAggregate(allocator, "orders", "city", &specs, 2, 5, &.{}, "", "");
defer allocator.free(payload);
const args = try decodeTableAggregate(allocator, payload);
defer allocator.free(args.specs);
defer allocator.free(args.orderBy);
try std.testing.expectEqual(@as(u32, 2), args.offset);
try std.testing.expectEqual(@as(u32, 5), args.limit);
}
test "table_aggregate args round trip a non-empty havingText alongside a non-empty exprText" {
const allocator = std.testing.allocator;
const specs = [_]aggregatemod.AggSpec{
.{ .field = "", .kind = .count },
.{ .field = "total", .kind = .sum },
};
const payload = try encodeTableAggregate(allocator, "orders", "city", &specs, 0, 0, &.{}, "sum_total gt i:1000", "total gt i:10");
defer allocator.free(payload);
const args = try decodeTableAggregate(allocator, payload);
defer allocator.free(args.specs);
defer allocator.free(args.orderBy);
try std.testing.expectEqualStrings("sum_total gt i:1000", args.havingText);
try std.testing.expectEqualStrings("total gt i:10", args.exprText);
}
test "table_aggregate args round trip a multi-column orderBy alongside havingText and exprText" {
const allocator = std.testing.allocator;
const specs = [_]aggregatemod.AggSpec{
.{ .field = "", .kind = .count },
.{ .field = "total", .kind = .sum },
};
const orderBy = [_]querymod.OrderBy{
.{ .field = "sum_total", .descending = true },
.{ .field = "city" },
};
const payload = try encodeTableAggregate(allocator, "orders", "city", &specs, 0, 0, &orderBy, "sum_total gt i:100", "total gt i:10");
defer allocator.free(payload);
const args = try decodeTableAggregate(allocator, payload);
defer allocator.free(args.specs);
defer allocator.free(args.orderBy);
try std.testing.expectEqual(@as(usize, 2), args.orderBy.len);
try std.testing.expectEqualStrings("sum_total", args.orderBy[0].field);
try std.testing.expect(args.orderBy[0].descending);
try std.testing.expectEqualStrings("city", args.orderBy[1].field);
try std.testing.expect(!args.orderBy[1].descending);
try std.testing.expectEqualStrings("sum_total gt i:100", args.havingText);
try std.testing.expectEqualStrings("total gt i:10", args.exprText);
}
test "table_aggregate args round trip an empty orderBy the same as before this feature existed" {
const allocator = std.testing.allocator;
const specs = [_]aggregatemod.AggSpec{.{ .field = "", .kind = .count }};
const payload = try encodeTableAggregate(allocator, "orders", "city", &specs, 0, 0, &.{}, "", "");
defer allocator.free(payload);
const args = try decodeTableAggregate(allocator, payload);
defer allocator.free(args.specs);
defer allocator.free(args.orderBy);
try std.testing.expectEqual(@as(usize, 0), args.orderBy.len);
}
test "decodeTableAggregate rejects an unknown agg kind byte instead of corrupting a spec" {
const allocator = std.testing.allocator;
var buf = std.ArrayList(u8).init(allocator);
defer buf.deinit();
const w = encode.ByteWriter.init(&buf);
try putLenPrefixed(w, "orders");
try putLenPrefixed(w, "city");
try w.putU32(1);
try w.byte(200); // not a valid AggKind tag
try putLenPrefixed(w, "total");
try std.testing.expectError(error.Malformed, decodeTableAggregate(allocator, buf.items));
}
test "agg global result round trips count/sum/max values, including a none from an empty avg" {
const allocator = std.testing.allocator;
const values = [_]rowmod.Value{
.{ .int = 4 },
.{ .float = 10.5 },
.{ .none = {} },
.{ .text = "warsaw" },
};
const payload = try encodeAggGlobalResult(allocator, &values);
defer allocator.free(payload);
const decoded = try decodeAggGlobalResult(allocator, payload);
defer freeAggValues(allocator, decoded);
try std.testing.expectEqual(@as(usize, 4), decoded.len);
try std.testing.expectEqual(@as(i64, 4), decoded[0].int);
try std.testing.expectApproxEqAbs(@as(f64, 10.5), decoded[1].float, 1e-9);
try std.testing.expectEqual(rowmod.ValueTag.none, @as(rowmod.ValueTag, decoded[2]));
try std.testing.expectEqualStrings("warsaw", decoded[3].text);
}
test "empty agg global result round trips" {
const allocator = std.testing.allocator;
const payload = try encodeAggGlobalResult(allocator, &[_]rowmod.Value{});
defer allocator.free(payload);
const decoded = try decodeAggGlobalResult(allocator, payload);
defer freeAggValues(allocator, decoded);
try std.testing.expectEqual(@as(usize, 0), decoded.len);
}
test "agg group rows round trip several groups with several values each" {
const allocator = std.testing.allocator;
const groups = [_]aggregatemod.GroupRow{
.{ .group = .{ .text = "warsaw" }, .values = @constCast(&[_]rowmod.Value{ .{ .int = 2 }, .{ .int = 350 } }) },
.{ .group = .{ .text = "krakow" }, .values = @constCast(&[_]rowmod.Value{ .{ .int = 1 }, .{ .int = 40 } }) },
};
const payload = try encodeAggGroupRows(allocator, &groups);
defer allocator.free(payload);
const decoded = try decodeAggGroupRows(allocator, payload);
defer freeAggGroupRows(allocator, decoded);
try std.testing.expectEqual(@as(usize, 2), decoded.len);
try std.testing.expectEqualStrings("warsaw", decoded[0].group.text);
try std.testing.expectEqual(@as(i64, 2), decoded[0].values[0].int);
try std.testing.expectEqual(@as(i64, 350), decoded[0].values[1].int);
try std.testing.expectEqualStrings("krakow", decoded[1].group.text);
try std.testing.expectEqual(@as(i64, 40), decoded[1].values[1].int);
}
test "empty agg group rows list round trips" {
const allocator = std.testing.allocator;
const payload = try encodeAggGroupRows(allocator, &[_]aggregatemod.GroupRow{});
defer allocator.free(payload);
const decoded = try decodeAggGroupRows(allocator, payload);
defer freeAggGroupRows(allocator, decoded);
try std.testing.expectEqual(@as(usize, 0), decoded.len);
}
test "decodeAggGroupRows rejects a corrupted second group without leaking the first" {
const allocator = std.testing.allocator;
var buf = std.ArrayList(u8).init(allocator);
defer buf.deinit();
const w = encode.ByteWriter.init(&buf);