Files
zyna-db/src/index_codec.zig

128 lines
3.5 KiB
Zig
Raw Normal View History

2026-01-20 12:44:41 -05:00
/// Secondary index entry encoding
/// Index entries store pointers to primary keys, not full items
const std = @import("std");
/// Encode a primary key reference for storage in an index entry
/// Format: [pk_len:varint][pk:bytes][sk_len:varint][sk:bytes]?
/// Returns owned slice that caller must free
pub fn encodePrimaryKeyRef(
allocator: std.mem.Allocator,
pk_value: []const u8,
sk_value: ?[]const u8,
) ![]u8 {
var buf = std.ArrayList(u8).init(allocator);
errdefer buf.deinit();
const writer = buf.writer();
// Encode partition key
try encodeVarint(writer, pk_value.len);
try writer.writeAll(pk_value);
// Encode sort key if present
if (sk_value) |sk| {
try encodeVarint(writer, sk.len);
try writer.writeAll(sk);
}
return buf.toOwnedSlice();
}
/// Decode a primary key reference from an index entry
/// Returns struct with owned slices that caller must free
pub fn decodePrimaryKeyRef(allocator: std.mem.Allocator, data: []const u8) !PrimaryKeyRef {
var decoder = BinaryDecoder.init(data);
// Decode partition key
const pk_len = try decoder.readVarint();
const pk = try decoder.readBytes(pk_len);
const owned_pk = try allocator.dupe(u8, pk);
errdefer allocator.free(owned_pk);
// Decode sort key if present
var owned_sk: ?[]u8 = null;
if (decoder.hasMore()) {
const sk_len = try decoder.readVarint();
const sk = try decoder.readBytes(sk_len);
owned_sk = try allocator.dupe(u8, sk);
}
return PrimaryKeyRef{
.pk = owned_pk,
.sk = owned_sk,
};
}
pub const PrimaryKeyRef = struct {
pk: []u8,
sk: ?[]u8,
pub fn deinit(self: *PrimaryKeyRef, allocator: std.mem.Allocator) void {
allocator.free(self.pk);
if (self.sk) |sk| allocator.free(sk);
}
};
// ============================================================================
// Binary Decoder Helper
// ============================================================================
const BinaryDecoder = struct {
data: []const u8,
pos: usize,
pub fn init(data: []const u8) BinaryDecoder {
return .{ .data = data, .pos = 0 };
}
pub fn readBytes(self: *BinaryDecoder, len: usize) ![]const u8 {
if (self.pos + len > self.data.len) return error.UnexpectedEndOfData;
const bytes = self.data[self.pos .. self.pos + len];
self.pos += len;
return bytes;
}
pub fn readVarint(self: *BinaryDecoder) !usize {
var result: usize = 0;
var shift: u6 = 0;
while (self.pos < self.data.len) {
const byte = self.data[self.pos];
self.pos += 1;
result |= @as(usize, byte & 0x7F) << shift;
if ((byte & 0x80) == 0) {
return result;
}
shift += 7;
if (shift >= 64) return error.VarintOverflow;
}
return error.UnexpectedEndOfData;
}
pub fn hasMore(self: *BinaryDecoder) bool {
return self.pos < self.data.len;
}
};
// ============================================================================
// Varint encoding (consistent with key_codec and item_codec)
// ============================================================================
fn encodeVarint(writer: anytype, value: usize) !void {
var v = value;
while (true) {
const byte = @as(u8, @intCast(v & 0x7F));
v >>= 7;
if (v == 0) {
try writer.writeByte(byte);
return;
} else {
try writer.writeByte(byte | 0x80);
}
}
}