/// 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); } } }