feat: expand protocol

This commit is contained in:
dualfroz committed 2026-09-07 22:39:37 +02:00
1 parent 4f10674feb
commit 6f0790ed87
1 file changed
+159
+159
View File
@@ -2355,3 +2355,162 @@ test "entries round trip and are independently owned" {
const payload = try encodeEntries(allocator, &entries);
defer allocator.free(payload);
const owned = try decodeEntries(allocator, payload);
defer freeOwnedEntries(allocator, owned);
try std.testing.expectEqual(@as(usize, 2), owned.len);
try std.testing.expectEqualStrings("a", owned[0].key);
try std.testing.expectEqualStrings("1", owned[0].value);
try std.testing.expectEqualStrings("b", owned[1].key);
try std.testing.expectEqualStrings("2", owned[1].value);
}
test "empty entry list round trips" {
const allocator = std.testing.allocator;
const payload = try encodeEntries(allocator, &[_]dbmod.Entry{});
defer allocator.free(payload);
const owned = try decodeEntries(allocator, payload);
defer freeOwnedEntries(allocator, owned);
try std.testing.expectEqual(@as(usize, 0), owned.len);
}
test "batch ops round trip put and delete" {
const allocator = std.testing.allocator;
const ops = [_]BatchOp{
.{ .put = .{ .key = "a", .value = "1" } },
.{ .del = "b" },
};
const payload = try encodeBatch(allocator, &ops);
defer allocator.free(payload);
const decoded = try decodeBatch(allocator, payload);
defer allocator.free(decoded);
try std.testing.expectEqual(@as(usize, 2), decoded.len);
try std.testing.expectEqualStrings("a", decoded[0].put.key);
try std.testing.expectEqualStrings("1", decoded[0].put.value);
try std.testing.expectEqualStrings("b", decoded[1].del);
}
test "decodeBatch rejects an unknown op kind" {
const allocator = std.testing.allocator;
var buf = std.ArrayList(u8).init(allocator);
defer buf.deinit();
const w = encode.ByteWriter.init(&buf);
try w.putU32(1);
try w.byte(9);
try std.testing.expectError(error.Malformed, decodeBatch(allocator, buf.items));
}
test "stats round trip preserves all fields including per-level detail" {
const allocator = std.testing.allocator;
var stats = dbmod.Stats{};
stats.memtableEntries = 3;
stats.memtableBytes = 128;
stats.sstableCount = 2;
stats.liveEntries = 100;
stats.physicalBytes = 4096;
stats.cacheHits = 10;
stats.cacheMisses = 4;
stats.nextSeq = 55;
stats.levels[0] = .{ .tables = 1, .entries = 50, .physicalBytes = 2048 };
stats.levels[3] = .{ .tables = 2, .entries = 50, .physicalBytes = 2048 };
const payload = try encodeStats(allocator, stats);
defer allocator.free(payload);
const decoded = try decodeStats(payload);
try std.testing.expectEqual(stats.memtableEntries, decoded.memtableEntries);
try std.testing.expectEqual(stats.nextSeq, decoded.nextSeq);
try std.testing.expectEqual(stats.levels[0].tables, decoded.levels[0].tables);
try std.testing.expectEqual(stats.levels[3].entries, decoded.levels[3].entries);
try std.testing.expectEqual(@as(f64, decoded.cacheHitRate()), decoded.cacheHitRate());
}
test "table select args round trip with a non-empty expr" {
const allocator = std.testing.allocator;
const payload = try encodeTableSelect(allocator, "users", "city eq s:warsaw", 0, 0, &.{});
defer allocator.free(payload);
const args = try decodeTableSelect(allocator, payload);
defer allocator.free(args.orderBy);
try std.testing.expectEqualStrings("users", args.table);
try std.testing.expectEqualStrings("city eq s:warsaw", args.exprText);
try std.testing.expectEqual(@as(u32, 0), args.offset);
try std.testing.expectEqual(@as(u32, 0), args.limit);
try std.testing.expectEqual(@as(usize, 0), args.orderBy.len);
}
test "table select args round trip with an empty expr means select all" {
const allocator = std.testing.allocator;
const payload = try encodeTableSelect(allocator, "users", "", 0, 0, &.{});
defer allocator.free(payload);
const args = try decodeTableSelect(allocator, payload);
defer allocator.free(args.orderBy);
try std.testing.expectEqualStrings("users", args.table);
try std.testing.expectEqualStrings("", args.exprText);
}
test "table select args round trip with a page window" {
const allocator = std.testing.allocator;
const payload = try encodeTableSelect(allocator, "users", "age gt i:10", 5, 3, &.{});
defer allocator.free(payload);
const args = try decodeTableSelect(allocator, payload);
defer allocator.free(args.orderBy);
try std.testing.expectEqualStrings("users", args.table);
try std.testing.expectEqualStrings("age gt i:10", args.exprText);
try std.testing.expectEqual(@as(u32, 5), args.offset);
try std.testing.expectEqual(@as(u32, 3), args.limit);
}
test "table select args round trip with an ascending order-by" {
const allocator = std.testing.allocator;
const payload = try encodeTableSelect(allocator, "users", "", 0, 0, &.{.{ .field = "age" }});
defer allocator.free(payload);
const args = try decodeTableSelect(allocator, payload);
defer allocator.free(args.orderBy);
try std.testing.expectEqual(@as(usize, 1), args.orderBy.len);
try std.testing.expectEqualStrings("age", args.orderBy[0].field);
try std.testing.expectEqual(false, args.orderBy[0].descending);
}
test "table select args round trip with a descending order-by, page, and expr all together" {
const allocator = std.testing.allocator;
const payload = try encodeTableSelect(allocator, "users", "city eq s:warsaw", 2, 5, &.{.{ .field = "score", .descending = true }});
defer allocator.free(payload);
const args = try decodeTableSelect(allocator, payload);
defer allocator.free(args.orderBy);
try std.testing.expectEqualStrings("users", args.table);
try std.testing.expectEqualStrings("city eq s:warsaw", args.exprText);
try std.testing.expectEqual(@as(u32, 2), args.offset);
try std.testing.expectEqual(@as(u32, 5), args.limit);
try std.testing.expectEqualStrings("score", args.orderBy[0].field);
try std.testing.expectEqual(true, args.orderBy[0].descending);
}
test "table select args round trip with two order-by columns in given order" {
const allocator = std.testing.allocator;
const payload = try encodeTableSelect(allocator, "users", "", 0, 0, &.{ .{ .field = "city" }, .{ .field = "score", .descending = true } });
defer allocator.free(payload);
const args = try decodeTableSelect(allocator, payload);
defer allocator.free(args.orderBy);
try std.testing.expectEqual(@as(usize, 2), args.orderBy.len);
try std.testing.expectEqualStrings("city", args.orderBy[0].field);
try std.testing.expectEqual(false, args.orderBy[0].descending);
try std.testing.expectEqualStrings("score", args.orderBy[1].field);
try std.testing.expectEqual(true, args.orderBy[1].descending);
}
test "decodeTableSelect rejects an order-by count claiming more columns than were actually encoded" {
const allocator = std.testing.allocator;
const payload = try encodeTableSelect(allocator, "users", "", 0, 0, &.{.{ .field = "age" }});
defer allocator.free(payload);
// The count byte sits right after the two u32 page fields: 4 (table
// len prefix) + 5 ("users") + 4 (offset) + 4 (limit).
const countBytePos = 4 + 5 + 4 + 4;
try std.testing.expectEqual(@as(u8, 1), payload[countBytePos]);
var corrupted = try allocator.dupe(u8, payload);
defer allocator.free(corrupted);
corrupted[countBytePos] = 200;
try std.testing.expectError(error.Malformed, decodeTableSelect(allocator, corrupted));
}