feat: add decodeValue to row
This commit is contained in:
1 parent
875d8fe62f
commit
65e5da36e5
1 file changed
+170
+170
@@ -315,3 +315,173 @@ pub fn encodeValue(allocator: Allocator, value: Value) ![]u8 {
|
||||
return out.toOwnedSlice();
|
||||
}
|
||||
|
||||
pub fn decodeValue(allocator: Allocator, data: []const u8) !Value {
|
||||
var pos: usize = 0;
|
||||
if (pos >= data.len) return error.Malformed;
|
||||
const tag: ValueTag = std.meta.intToEnum(ValueTag, data[pos]) catch return error.Malformed;
|
||||
pos += 1;
|
||||
return switch (tag) {
|
||||
.none => .{ .none = {} },
|
||||
.boolean => blk: {
|
||||
if (pos >= data.len) return error.Malformed;
|
||||
break :blk .{ .boolean = data[pos] != 0 };
|
||||
},
|
||||
.int => .{ .int = @bitCast(try readU64(data, &pos)) },
|
||||
.float => .{ .float = @bitCast(try readU64(data, &pos)) },
|
||||
.text => blk: {
|
||||
const len = try readU32(data, &pos);
|
||||
if (pos + len > data.len) return error.Malformed;
|
||||
break :blk .{ .text = try allocator.dupe(u8, data[pos .. pos + len]) };
|
||||
},
|
||||
.bytes => blk: {
|
||||
const len = try readU32(data, &pos);
|
||||
if (pos + len > data.len) return error.Malformed;
|
||||
break :blk .{ .bytes = try allocator.dupe(u8, data[pos .. pos + len]) };
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test "row set get overwrite and free" {
|
||||
const allocator = std.testing.allocator;
|
||||
var row = Row.init(allocator);
|
||||
defer row.deinit();
|
||||
|
||||
try row.set("name", .{ .text = "ada" });
|
||||
try row.set("age", .{ .int = 30 });
|
||||
try row.set("name", .{ .text = "ada lovelace" });
|
||||
|
||||
try std.testing.expectEqual(@as(usize, 2), row.count());
|
||||
try std.testing.expectEqualStrings("ada lovelace", row.get("name").?.text);
|
||||
try std.testing.expectEqual(@as(i64, 30), row.get("age").?.int);
|
||||
try std.testing.expect(row.get("missing") == null);
|
||||
}
|
||||
|
||||
test "row encode decode round trip all types" {
|
||||
const allocator = std.testing.allocator;
|
||||
var row = Row.init(allocator);
|
||||
defer row.deinit();
|
||||
|
||||
try row.set("id", .{ .int = -42 });
|
||||
try row.set("score", .{ .float = 3.5 });
|
||||
try row.set("active", .{ .boolean = true });
|
||||
try row.set("bio", .{ .text = "hello world" });
|
||||
try row.set("blob", .{ .bytes = &.{ 0, 1, 2, 255 } });
|
||||
try row.set("nothing", .{ .none = {} });
|
||||
|
||||
const encoded = try row.encode(allocator);
|
||||
defer allocator.free(encoded);
|
||||
|
||||
var decoded = try Row.decode(allocator, encoded);
|
||||
defer decoded.deinit();
|
||||
|
||||
try std.testing.expectEqual(@as(i64, -42), decoded.get("id").?.int);
|
||||
try std.testing.expectEqual(@as(f64, 3.5), decoded.get("score").?.float);
|
||||
try std.testing.expectEqual(true, decoded.get("active").?.boolean);
|
||||
try std.testing.expectEqualStrings("hello world", decoded.get("bio").?.text);
|
||||
try std.testing.expectEqualSlices(u8, &.{ 0, 1, 2, 255 }, decoded.get("blob").?.bytes);
|
||||
try std.testing.expectEqual(ValueTag.none, @as(ValueTag, decoded.get("nothing").?));
|
||||
}
|
||||
|
||||
test "row decode rejects malformed data" {
|
||||
const allocator = std.testing.allocator;
|
||||
try std.testing.expectError(error.Malformed, Row.decode(allocator, &.{ 1, 0, 0, 0 }));
|
||||
try std.testing.expectError(error.Malformed, Row.decode(allocator, &.{ 1, 0, 0, 0, 5, 0, 'a' }));
|
||||
}
|
||||
|
||||
test "compare orders by type tag then payload" {
|
||||
try std.testing.expectEqual(std.math.Order.lt, compare(.{ .none = {} }, .{ .boolean = false }));
|
||||
try std.testing.expectEqual(std.math.Order.lt, compare(.{ .int = 5 }, .{ .float = 0.0 }));
|
||||
try std.testing.expectEqual(std.math.Order.lt, compare(.{ .int = -5 }, .{ .int = 3 }));
|
||||
try std.testing.expectEqual(std.math.Order.gt, compare(.{ .text = "b" }, .{ .text = "a" }));
|
||||
try std.testing.expect(Value.eql(.{ .text = "x" }, .{ .text = "x" }));
|
||||
}
|
||||
|
||||
test "appendOrdered preserves int order across sign boundary" {
|
||||
const allocator = std.testing.allocator;
|
||||
const values = [_]i64{ std.math.minInt(i64), -1000, -1, 0, 1, 1000, std.math.maxInt(i64) };
|
||||
var prev: ?[]u8 = null;
|
||||
defer if (prev) |p| allocator.free(p);
|
||||
for (values) |v| {
|
||||
const encoded = try encodeOrdered(allocator, .{ .int = v });
|
||||
if (prev) |p| {
|
||||
try std.testing.expect(std.mem.order(u8, p, encoded) == .lt);
|
||||
allocator.free(p);
|
||||
}
|
||||
prev = encoded;
|
||||
}
|
||||
}
|
||||
|
||||
test "appendOrdered preserves float order across sign and magnitude" {
|
||||
const allocator = std.testing.allocator;
|
||||
const values = [_]f64{ -100.0, -1.5, -0.001, 0.0, 0.001, 1.5, 100.0 };
|
||||
var prev: ?[]u8 = null;
|
||||
defer if (prev) |p| allocator.free(p);
|
||||
for (values) |v| {
|
||||
const encoded = try encodeOrdered(allocator, .{ .float = v });
|
||||
if (prev) |p| {
|
||||
try std.testing.expect(std.mem.order(u8, p, encoded) == .lt);
|
||||
allocator.free(p);
|
||||
}
|
||||
prev = encoded;
|
||||
}
|
||||
}
|
||||
|
||||
test "appendOrdered escapes embedded zero bytes unambiguously" {
|
||||
const allocator = std.testing.allocator;
|
||||
const a = try encodeOrdered(allocator, .{ .text = "ab" });
|
||||
defer allocator.free(a);
|
||||
const b = try encodeOrdered(allocator, .{ .text = "ab\x00c" });
|
||||
defer allocator.free(b);
|
||||
const c = try encodeOrdered(allocator, .{ .text = "ab" ++ "c"[0..0] });
|
||||
defer allocator.free(c);
|
||||
// "ab" must sort before "ab\x00c" (shorter, unescaped terminator wins),
|
||||
// and the escaped embedded zero must not corrupt ordering.
|
||||
try std.testing.expect(std.mem.order(u8, a, b) == .lt);
|
||||
try std.testing.expect(std.mem.eql(u8, a, c));
|
||||
}
|
||||
|
||||
test "encodeValue decodeValue round trip" {
|
||||
const allocator = std.testing.allocator;
|
||||
const original = Value{ .text = "primary-key-42" };
|
||||
const encoded = try encodeValue(allocator, original);
|
||||
defer allocator.free(encoded);
|
||||
const decoded = try decodeValue(allocator, encoded);
|
||||
defer decoded.free(allocator);
|
||||
try std.testing.expectEqualStrings("primary-key-42", decoded.text);
|
||||
|
||||
const intEncoded = try encodeValue(allocator, .{ .int = -7 });
|
||||
defer allocator.free(intEncoded);
|
||||
const intDecoded = try decodeValue(allocator, intEncoded);
|
||||
try std.testing.expectEqual(@as(i64, -7), intDecoded.int);
|
||||
}
|
||||
|
||||
test "appendOrdered text ordering matches string ordering" {
|
||||
const allocator = std.testing.allocator;
|
||||
const words = [_][]const u8{ "apple", "banana", "banjo", "zebra" };
|
||||
var prev: ?[]u8 = null;
|
||||
defer if (prev) |p| allocator.free(p);
|
||||
for (words) |w| {
|
||||
const encoded = try encodeOrdered(allocator, .{ .text = w });
|
||||
if (prev) |p| {
|
||||
try std.testing.expect(std.mem.order(u8, p, encoded) == .lt);
|
||||
allocator.free(p);
|
||||
}
|
||||
prev = encoded;
|
||||
}
|
||||
}
|
||||
|
||||
test "parseCli honors type prefixes and defaults to text" {
|
||||
try std.testing.expectEqual(@as(i64, 42), (try Value.parseCli("i:42")).int);
|
||||
try std.testing.expectEqual(@as(i64, -3), (try Value.parseCli("i:-3")).int);
|
||||
try std.testing.expectEqual(@as(f64, 1.5), (try Value.parseCli("f:1.5")).float);
|
||||
try std.testing.expectEqual(true, (try Value.parseCli("b:true")).boolean);
|
||||
try std.testing.expectEqual(false, (try Value.parseCli("b:false")).boolean);
|
||||
try std.testing.expectEqualStrings("hi", (try Value.parseCli("s:hi")).text);
|
||||
try std.testing.expectEqualStrings("plain", (try Value.parseCli("plain")).text);
|
||||
}
|
||||
|
||||
test "parseCli rejects malformed prefixed values" {
|
||||
try std.testing.expectError(error.InvalidValue, Value.parseCli("i:notanumber"));
|
||||
try std.testing.expectError(error.InvalidValue, Value.parseCli("f:notafloat"));
|
||||
try std.testing.expectError(error.InvalidValue, Value.parseCli("b:maybe"));
|
||||
}
|
||||
Reference in new issue
Block a user