From 6ff5e832eb2d393601479a5cf474ac73b9de5e95 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Tue, 8 Sep 2026 11:50:29 +0200 Subject: [PATCH] feat: build out protocol --- src/protocol.zig | 156 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) diff --git a/src/protocol.zig b/src/protocol.zig index 8b2610a..cd94320 100644 --- a/src/protocol.zig +++ b/src/protocol.zig @@ -2514,3 +2514,159 @@ test "decodeTableSelect rejects an order-by count claiming more columns than wer try std.testing.expectError(error.Malformed, decodeTableSelect(allocator, corrupted)); } +test "rows round trip through encode and decode" { + const allocator = std.testing.allocator; + var rows = [_]rowmod.Row{ rowmod.Row.init(allocator), rowmod.Row.init(allocator) }; + defer for (&rows) |*r| r.deinit(); + try rows[0].set("name", .{ .text = "ada" }); + try rows[0].set("age", .{ .int = 36 }); + try rows[1].set("name", .{ .text = "bob" }); + try rows[1].set("age", .{ .int = 24 }); + + const payload = try encodeRows(allocator, &rows); + defer allocator.free(payload); + + const decoded = try decodeRows(allocator, payload); + defer freeRows(allocator, decoded); + + try std.testing.expectEqual(@as(usize, 2), decoded.len); + try std.testing.expectEqualStrings("ada", decoded[0].get("name").?.text); + try std.testing.expectEqual(@as(i64, 36), decoded[0].get("age").?.int); + try std.testing.expectEqualStrings("bob", decoded[1].get("name").?.text); + try std.testing.expectEqual(@as(i64, 24), decoded[1].get("age").?.int); +} + +test "empty row list round trips" { + const allocator = std.testing.allocator; + const payload = try encodeRows(allocator, &[_]rowmod.Row{}); + defer allocator.free(payload); + const decoded = try decodeRows(allocator, payload); + defer freeRows(allocator, decoded); + try std.testing.expectEqual(@as(usize, 0), decoded.len); +} + +test "JoinMode.fromByte rejects an unknown mode" { + try std.testing.expectError(error.UnknownJoinMode, JoinMode.fromByte(9)); +} + +test "table_join args round trip mode, tables, fields, and both expr texts" { + const allocator = std.testing.allocator; + const payload = try encodeTableJoin(allocator, .left, "customers", "__pk", "orders", "customer_id", 0, 0, "age gt i:10", "total gt i:0"); + defer allocator.free(payload); + const args = try decodeTableJoin(payload); + try std.testing.expectEqual(JoinMode.left, args.mode); + try std.testing.expectEqualStrings("customers", args.leftTable); + try std.testing.expectEqualStrings("__pk", args.leftField); + try std.testing.expectEqualStrings("orders", args.rightTable); + try std.testing.expectEqualStrings("customer_id", args.rightField); + try std.testing.expectEqual(@as(u32, 0), args.offset); + try std.testing.expectEqual(@as(u32, 0), args.limit); + try std.testing.expectEqualStrings("age gt i:10", args.leftExprText); + try std.testing.expectEqualStrings("total gt i:0", args.rightExprText); +} + +test "table_join args round trip with both expr texts empty" { + const allocator = std.testing.allocator; + const payload = try encodeTableJoin(allocator, .inner, "a", "x", "b", "y", 0, 0, "", ""); + defer allocator.free(payload); + const args = try decodeTableJoin(payload); + try std.testing.expectEqual(JoinMode.inner, args.mode); + try std.testing.expectEqualStrings("", args.leftExprText); + try std.testing.expectEqualStrings("", args.rightExprText); +} + +test "table_join args round trip a nonzero offset and limit" { + const allocator = std.testing.allocator; + const payload = try encodeTableJoin(allocator, .full, "a", "x", "b", "y", 3, 7, "", ""); + defer allocator.free(payload); + const args = try decodeTableJoin(payload); + try std.testing.expectEqual(@as(u32, 3), args.offset); + try std.testing.expectEqual(@as(u32, 7), args.limit); +} + +test "join pairs round trip a fully matched pair (inner-join shape)" { + const allocator = std.testing.allocator; + var left = rowmod.Row.init(allocator); + defer left.deinit(); + try left.set("name", .{ .text = "ada" }); + var right = rowmod.Row.init(allocator); + defer right.deinit(); + try right.set("total", .{ .int = 42 }); + + const pairs = [_]JoinPair{.{ .left = left, .right = right }}; + const payload = try encodeJoinedRows(allocator, &pairs); + defer allocator.free(payload); + + const decoded = try decodeJoinedRows(allocator, payload); + defer freeJoinPairs(allocator, decoded); + + try std.testing.expectEqual(@as(usize, 1), decoded.len); + try std.testing.expectEqualStrings("ada", decoded[0].left.?.get("name").?.text); + try std.testing.expectEqual(@as(i64, 42), decoded[0].right.?.get("total").?.int); +} + +test "join pairs round trip a nil left side and a nil right side" { + const allocator = std.testing.allocator; + var right = rowmod.Row.init(allocator); + defer right.deinit(); + try right.set("total", .{ .int = 7 }); + var left = rowmod.Row.init(allocator); + defer left.deinit(); + try left.set("name", .{ .text = "carol" }); + + const pairs = [_]JoinPair{ + .{ .left = null, .right = right }, + .{ .left = left, .right = null }, + }; + const payload = try encodeJoinedRows(allocator, &pairs); + defer allocator.free(payload); + + const decoded = try decodeJoinedRows(allocator, payload); + defer freeJoinPairs(allocator, decoded); + + try std.testing.expectEqual(@as(usize, 2), decoded.len); + try std.testing.expect(decoded[0].left == null); + try std.testing.expectEqual(@as(i64, 7), decoded[0].right.?.get("total").?.int); + try std.testing.expectEqualStrings("carol", decoded[1].left.?.get("name").?.text); + try std.testing.expect(decoded[1].right == null); +} + +test "empty join pair list round trips" { + const allocator = std.testing.allocator; + const payload = try encodeJoinedRows(allocator, &[_]JoinPair{}); + defer allocator.free(payload); + const decoded = try decodeJoinedRows(allocator, payload); + defer freeJoinPairs(allocator, decoded); + try std.testing.expectEqual(@as(usize, 0), decoded.len); +} + +test "table_aggregate args round trip a groupField, several specs, and an expr" { + const allocator = std.testing.allocator; + const specs = [_]aggregatemod.AggSpec{ + .{ .field = "", .kind = .count }, + .{ .field = "total", .kind = .sum }, + .{ .field = "total", .kind = .max }, + .{ .field = "city", .kind = .count_distinct }, + }; + const payload = try encodeTableAggregate(allocator, "orders", "city", &specs, 0, 0, &.{}, "", "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("orders", args.table); + try std.testing.expectEqualStrings("city", args.groupField); + try std.testing.expectEqual(@as(u32, 0), args.offset); + try std.testing.expectEqual(@as(u32, 0), args.limit); + try std.testing.expectEqualStrings("", args.havingText); + try std.testing.expectEqualStrings("total gt i:10", args.exprText); + try std.testing.expectEqual(@as(usize, 4), args.specs.len); + try std.testing.expectEqual(aggregatemod.AggKind.count, args.specs[0].kind); + try std.testing.expectEqualStrings("", args.specs[0].field); + try std.testing.expectEqual(aggregatemod.AggKind.sum, args.specs[1].kind); + try std.testing.expectEqualStrings("total", args.specs[1].field); + try std.testing.expectEqual(aggregatemod.AggKind.max, args.specs[2].kind); + try std.testing.expectEqual(aggregatemod.AggKind.count_distinct, args.specs[3].kind); + try std.testing.expectEqualStrings("city", args.specs[3].field); +} +