tiny.sql.search.vbyte
Defined in search.
API (17)
Actions
Public operations.
Decoder.initDecoder.initValidatedDecoder.nextDecoder.nextBlockEncoder.appendEncoder.finishEncoder.initcontrolByteCountdecodeencodeencodedSizevalueByteCount
Types and contracts
Public types and contracts.
Values and defaults
Public values and defaults.
Source
Source: lib/sql/src/search/root.zig:9
zig
pub const vbyte = @import("vbyte.zig");Source: lib/sql/src/search/vbyte.zig
zig
const std = @import("std");pub const upstream_revision = "8afe8693964b33abb06b3b6b48fad4d33693a241";pub const upstream_source = "https://github.com/fast-pack/streamvbyte";pub const Error = error{ InvalidSearchIndex, OutputTooSmall,};pub const Decoder = struct { input: []const u8 = &.{}, value_count: usize = 0, value_index: usize = 0, data_offset: usize = 0, pub fn init(input: []const u8, value_count: usize) Error!Decoder { const control_count = controlByteCount(value_count); if (input.len < control_count) return error.InvalidSearchIndex; _ = std.math.mul(usize, value_count, 4) catch return error.InvalidSearchIndex; var data_count: usize = 0; const full_control_count = value_count / 4; for (input[0..full_control_count]) |control| data_count += controlDataByteCount(control); const remaining = value_count % 4; if (remaining != 0) { const control = input[full_control_count]; if (control >> @intCast(remaining * 2) != 0) return error.InvalidSearchIndex; for (0..remaining) |index| data_count += @as(usize, (control >> @intCast(index * 2)) & 0x3) + 1; } const encoded_count = std.math.add(usize, control_count, data_count) catch return error.InvalidSearchIndex; if (encoded_count != input.len) return error.InvalidSearchIndex; return initValidated(input, value_count); } pub fn initValidated(input: []const u8, value_count: usize) Decoder { return .{ .input = input, .value_count = value_count, .data_offset = controlByteCount(value_count), }; } pub fn next(self: *Decoder) Error!?u32 { if (self.value_index >= self.value_count) return null; const control = self.input[self.value_index / 4]; const shift: u3 = @intCast((self.value_index % 4) * 2); const code = (control >> shift) & 0x3; const value = try readValue(self.input, &self.data_offset, code); self.value_index += 1; return value; } pub fn nextBlock(self: *Decoder, output: *[4]u32, count: usize) Error!void { if (count == 0 or count > 4 or self.value_index % 4 != 0 or count > self.value_count - self.value_index) return error.InvalidSearchIndex; const control = self.input[self.value_index / 4]; if (control == 0) { const end = std.math.add(usize, self.data_offset, count) catch return error.InvalidSearchIndex; if (end > self.input.len) return error.InvalidSearchIndex; for (0..count) |index| output[index] = self.input[self.data_offset + index]; self.data_offset = end; self.value_index += count; return; } for (0..count) |index| output[index] = (try self.next()) orelse return error.InvalidSearchIndex; }};pub const Encoder = struct { output: []u8, value_count: usize, value_index: usize = 0, data_offset: usize, pub fn init(output: []u8, value_count: usize) Error!Encoder { const control_count = controlByteCount(value_count); if (output.len < control_count) return error.OutputTooSmall; @memset(output[0..control_count], 0); return .{ .output = output, .value_count = value_count, .data_offset = control_count, }; } pub fn append(self: *Encoder, value: u32) Error!void { if (self.value_index >= self.value_count) return error.InvalidSearchIndex; const byte_count = valueByteCount(value); const end = std.math.add(usize, self.data_offset, byte_count) catch return error.OutputTooSmall; if (end > self.output.len) return error.OutputTooSmall; const shift: u3 = @intCast((self.value_index % 4) * 2); self.output[self.value_index / 4] |= @as(u8, @intCast(byte_count - 1)) << shift; writeValue(self.output[self.data_offset..end], value); self.data_offset = end; self.value_index += 1; } pub fn finish(self: *const Encoder) Error!void { if (self.value_index != self.value_count or self.data_offset != self.output.len) return error.InvalidSearchIndex; }};pub fn controlByteCount(value_count: usize) usize { return value_count / 4 + @intFromBool(value_count % 4 != 0);}pub fn encodedSize(values: []const u32) ?usize { var size = controlByteCount(values.len); for (values) |value| size = std.math.add(usize, size, valueByteCount(value)) catch return null; return size;}pub fn encode(target: []u8, values: []const u32) Error![]const u8 { const encoded_size = encodedSize(values) orelse return error.InvalidSearchIndex; if (target.len < encoded_size) return error.OutputTooSmall; var encoder = try Encoder.init(target[0..encoded_size], values.len); for (values) |value| try encoder.append(value); try encoder.finish(); return target[0..encoded_size];}pub fn decode(target: []u32, input: []const u8) Error!void { var decoder = try Decoder.init(input, target.len); for (target) |*value| value.* = (try decoder.next()) orelse return error.InvalidSearchIndex; if (try decoder.next() != null) return error.InvalidSearchIndex;}pub fn valueByteCount(value: u32) usize { return 1 + @as(usize, @intFromBool(value > 0xff)) + @as(usize, @intFromBool(value > 0xffff)) + @as(usize, @intFromBool(value > 0xff_ffff));}fn controlDataByteCount(control: u8) usize { return 4 + @as(usize, control & 0x3) + @as(usize, (control >> 2) & 0x3) + @as(usize, (control >> 4) & 0x3) + @as(usize, control >> 6);}fn writeValue(target: []u8, value: u32) void { std.debug.assert(target.len == valueByteCount(value)); switch (target.len) { 1 => target[0] = @truncate(value), 2 => std.mem.writeInt(u16, target[0..2], @truncate(value), .little), 3 => { target[0] = @truncate(value); target[1] = @truncate(value >> 8); target[2] = @truncate(value >> 16); }, 4 => std.mem.writeInt(u32, target[0..4], value, .little), else => unreachable, }}fn readValue(input: []const u8, offset: *usize, code: u8) Error!u32 { const byte_count = @as(usize, code) + 1; const end = std.math.add(usize, offset.*, byte_count) catch return error.InvalidSearchIndex; if (end > input.len) return error.InvalidSearchIndex; const value: u32 = switch (byte_count) { 1 => input[offset.*], 2 => std.mem.readInt(u16, input[offset.*..][0..2], .little), 3 => @as(u32, input[offset.*]) | (@as(u32, input[offset.* + 1]) << 8) | (@as(u32, input[offset.* + 2]) << 16), 4 => std.mem.readInt(u32, input[offset.*..][0..4], .little), else => unreachable, }; offset.* = end; return value;}test "Stream VByte 8afe869 format fixture is exact" { const values = [_]u32{ 0, 100, 200, 300, 400, 500, 600, 700 }; const expected = [_]u8{ 0x40, 0x55, 0x00, 0x64, 0xc8, 0x2c, 0x01, 0x90, 0x01, 0xf4, 0x01, 0x58, 0x02, 0xbc, 0x02 }; var encoded: [expected.len]u8 = undefined; _ = try encode(&encoded, &values); try std.testing.expectEqualSlices(u8, &expected, &encoded); var decoded: [values.len]u32 = undefined; try decode(&decoded, &encoded); try std.testing.expectEqualSlices(u32, &values, &decoded);}test "Stream VByte preserves byte-width boundaries" { var empty_encoded: [0]u8 = .{}; var empty_decoded: [0]u32 = .{}; try std.testing.expectEqual(@as(usize, 0), (try encode(&empty_encoded, &empty_decoded)).len); try decode(&empty_decoded, &empty_encoded); const values = [_]u32{ 0, 0xff, 0x100, 0xffff, 0x1_0000, 0xff_ffff, 0x100_0000, std.math.maxInt(u32) }; var encoded: [22]u8 = undefined; _ = try encode(&encoded, &values); var decoded: [values.len]u32 = undefined; try decode(&decoded, &encoded); try std.testing.expectEqualSlices(u32, &values, &decoded);}test "Stream VByte rejects malformed streams" { const values = [_]u32{ 1, 0x100, 3 }; var encoded: [5]u8 = undefined; _ = try encode(&encoded, &values); var decoded: [values.len]u32 = undefined; try std.testing.expectError(error.InvalidSearchIndex, decode(&decoded, encoded[0 .. encoded.len - 1])); var trailing: [encoded.len + 1]u8 = undefined; @memcpy(trailing[0..encoded.len], &encoded); trailing[encoded.len] = 0; try std.testing.expectError(error.InvalidSearchIndex, decode(&decoded, &trailing)); var noncanonical = encoded; noncanonical[0] |= 0xc0; try std.testing.expectError(error.InvalidSearchIndex, decode(&decoded, &noncanonical)); var too_small: [encoded.len - 1]u8 = undefined; try std.testing.expectError(error.OutputTooSmall, encode(&too_small, &values));}Complete caller list for search.vbyte.controlByteCount
7 direct callers.
lib.sql.src.search.posting.segmentPayloadCount[function] — private source atlib/sql/src/search/posting.zig:717in nearest public ownerlib.sql.src.search.postinglib.sql.src.search.posting.streamPostingSegmentPayloadSize[function] — private source atlib/sql/src/search/posting.zig:649in nearest public ownerlib.sql.src.search.postinglib.sql.src.search.posting.test_search_Stream_VByte_segment_rejects_invalid_gaps_scores_and_bounds[function] — test source atlib/sql/src/search/posting.zig:1305in nearest public ownerlib.sql.src.search.postingtiny.sql.search.vbyte.Decoder.init[function] atlib/sql/src/search/vbyte.zig:18tiny.sql.search.vbyte.Decoder.initValidated[function] atlib/sql/src/search/vbyte.zig:36tiny.sql.search.vbyte.Encoder.init[function] atlib/sql/src/search/vbyte.zig:75tiny.sql.search.vbyte.encodedSize[function] atlib/sql/src/search/vbyte.zig:107
Audit
| Definitions | 18 |
|---|---|
| Public names | 18 |
| Members | 10 |
| Version | 26.7.0 |
| Revision | daab053ee433 |