feat: add decode and putU16 to row

This commit is contained in:
dualfroz committed 2026-09-10 20:25:22 +02:00
1 parent a4eaa8e896
commit 875d8fe62f
1 file changed
+157
+157
View File
@@ -158,3 +158,160 @@ pub const Row = struct {
return out.toOwnedSlice();
}
pub fn decode(allocator: Allocator, data: []const u8) !Row {
var row = Row.init(allocator);
errdefer row.deinit();
var pos: usize = 0;
const fieldCount = try readU32(data, &pos);
var i: u32 = 0;
while (i < fieldCount) : (i += 1) {
const nameLen = try readU16(data, &pos);
if (pos + nameLen > data.len) return error.Malformed;
const name = data[pos .. pos + nameLen];
pos += nameLen;
if (pos >= data.len) return error.Malformed;
const tag: ValueTag = std.meta.intToEnum(ValueTag, data[pos]) catch return error.Malformed;
pos += 1;
const value: Value = switch (tag) {
.none => .{ .none = {} },
.boolean => blk: {
if (pos >= data.len) return error.Malformed;
const b = data[pos];
pos += 1;
break :blk .{ .boolean = b != 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;
const s = data[pos .. pos + len];
pos += len;
break :blk .{ .text = s };
},
.bytes => blk: {
const len = try readU32(data, &pos);
if (pos + len > data.len) return error.Malformed;
const s = data[pos .. pos + len];
pos += len;
break :blk .{ .bytes = s };
},
};
try row.set(name, value);
}
if (pos != data.len) return error.Malformed;
return row;
}
};
fn putU16(out: *std.ArrayList(u8), v: u16) !void {
var b: [2]u8 = undefined;
std.mem.writeInt(u16, &b, v, .little);
try out.appendSlice(&b);
}
fn putU32(out: *std.ArrayList(u8), v: u32) !void {
var b: [4]u8 = undefined;
std.mem.writeInt(u32, &b, v, .little);
try out.appendSlice(&b);
}
fn putU64(out: *std.ArrayList(u8), v: u64) !void {
var b: [8]u8 = undefined;
std.mem.writeInt(u64, &b, v, .little);
try out.appendSlice(&b);
}
fn readU16(data: []const u8, pos: *usize) !u16 {
if (pos.* + 2 > data.len) return error.Malformed;
const v = std.mem.readInt(u16, data[pos.*..][0..2], .little);
pos.* += 2;
return v;
}
fn readU32(data: []const u8, pos: *usize) !u32 {
if (pos.* + 4 > data.len) return error.Malformed;
const v = std.mem.readInt(u32, data[pos.*..][0..4], .little);
pos.* += 4;
return v;
}
fn readU64(data: []const u8, pos: *usize) !u64 {
if (pos.* + 8 > data.len) return error.Malformed;
const v = std.mem.readInt(u64, data[pos.*..][0..8], .little);
pos.* += 8;
return v;
}
/// Appends an order-preserving byte encoding of `value` to `out`, such that
/// lexicographic byte comparison of two encodings matches `compare()` of the
/// original values. Used to build index keys and primary-key suffixes that
/// must sort correctly inside the LSM tree's byte-ordered keyspace.
///
/// Text/bytes payloads are escaped (0x00 -> 0x00 0xFF) and terminated with
/// 0x00 0x00 so that concatenating several encoded values back to back stays
/// unambiguous and order-preserving (no payload can be a byte-for-byte prefix
/// of another once escaped and terminated).
pub fn appendOrdered(out: *std.ArrayList(u8), value: Value) !void {
try out.append(@intFromEnum(@as(ValueTag, value)));
switch (value) {
.none => {},
.boolean => |v| try out.append(if (v) 1 else 0),
.int => |v| {
const flipped: u64 = @as(u64, @bitCast(v)) ^ 0x8000000000000000;
var b: [8]u8 = undefined;
std.mem.writeInt(u64, &b, flipped, .big);
try out.appendSlice(&b);
},
.float => |v| {
var bits: u64 = @bitCast(v);
if (bits & 0x8000000000000000 != 0) {
bits = ~bits;
} else {
bits |= 0x8000000000000000;
}
var b: [8]u8 = undefined;
std.mem.writeInt(u64, &b, bits, .big);
try out.appendSlice(&b);
},
.text, .bytes => |s| {
for (s) |byte| {
if (byte == 0x00) {
try out.appendSlice(&.{ 0x00, 0xFF });
} else {
try out.append(byte);
}
}
try out.appendSlice(&.{ 0x00, 0x00 });
},
}
}
pub fn encodeOrdered(allocator: Allocator, value: Value) ![]u8 {
var out = std.ArrayList(u8).init(allocator);
errdefer out.deinit();
try appendOrdered(&out, value);
return out.toOwnedSlice();
}
/// Encodes a single standalone Value using the same (non order-preserving)
/// tag+payload format as a Row field, minus the field name. Used to store
/// primary keys as index-entry values, where later exact reconstruction
/// matters more than byte ordering.
pub fn encodeValue(allocator: Allocator, value: Value) ![]u8 {
var out = std.ArrayList(u8).init(allocator);
errdefer out.deinit();
try out.append(@intFromEnum(@as(ValueTag, value)));
switch (value) {
.none => {},
.boolean => |v| try out.append(if (v) 1 else 0),
.int => |v| try putU64(&out, @bitCast(v)),
.float => |v| try putU64(&out, @bitCast(v)),
.text, .bytes => |s| {
try putU32(&out, @intCast(s.len));
try out.appendSlice(s);
},
}
return out.toOwnedSlice();
}