vortex_array/arrays/varbinview/view/
mod.rs1use std::fmt;
7use std::hash::Hash;
8use std::hash::Hasher;
9use std::ops::Range;
10
11use static_assertions::assert_eq_align;
12use static_assertions::assert_eq_size;
13use vortex_error::VortexExpect;
14
15#[derive(Clone, Copy)]
30#[repr(C, align(16))]
31pub union BinaryView {
32 pub(crate) le_bytes: [u8; 16],
35
36 pub(crate) inlined: Inlined,
38
39 pub(crate) _ref: Ref,
41}
42
43assert_eq_align!(BinaryView, u128);
44assert_eq_size!(BinaryView, [u8; 16]);
45assert_eq_size!(Inlined, [u8; 16]);
46assert_eq_size!(Ref, [u8; 16]);
47
48#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50#[repr(C, align(8))]
51pub struct Inlined {
52 pub size: u32,
54 pub data: [u8; BinaryView::MAX_INLINED_SIZE],
56}
57
58impl Inlined {
59 fn new<const N: usize>(value: &[u8]) -> Self {
61 debug_assert_eq!(value.len(), N);
62 let mut inlined = Self {
63 size: N.try_into().vortex_expect("inlined size must fit in u32"),
64 data: [0u8; BinaryView::MAX_INLINED_SIZE],
65 };
66 inlined.data[..N].copy_from_slice(&value[..N]);
67 inlined
68 }
69
70 #[inline]
72 pub fn value(&self) -> &[u8] {
73 &self.data[0..(self.size as usize)]
74 }
75}
76
77#[derive(Clone, Copy, Debug)]
79#[repr(C, align(8))]
80pub struct Ref {
81 pub size: u32,
83 pub prefix: [u8; 4],
85 pub buffer_index: u32,
87 pub offset: u32,
89}
90
91impl Ref {
92 #[inline]
94 pub fn as_range(&self) -> Range<usize> {
95 self.offset as usize..(self.offset + self.size) as usize
96 }
97
98 #[inline]
100 pub fn with_buffer_and_offset(&self, buffer_index: u32, offset: u32) -> Ref {
101 Self {
102 size: self.size,
103 prefix: self.prefix,
104 buffer_index,
105 offset,
106 }
107 }
108}
109
110impl BinaryView {
111 pub const MAX_INLINED_SIZE: usize = 12;
115
116 #[inline(never)]
124 pub fn make_view(value: &[u8], block: u32, offset: u32) -> Self {
125 match value.len() {
126 0 => Self {
127 inlined: Inlined::new::<0>(value),
128 },
129 1 => Self {
130 inlined: Inlined::new::<1>(value),
131 },
132 2 => Self {
133 inlined: Inlined::new::<2>(value),
134 },
135 3 => Self {
136 inlined: Inlined::new::<3>(value),
137 },
138 4 => Self {
139 inlined: Inlined::new::<4>(value),
140 },
141 5 => Self {
142 inlined: Inlined::new::<5>(value),
143 },
144 6 => Self {
145 inlined: Inlined::new::<6>(value),
146 },
147 7 => Self {
148 inlined: Inlined::new::<7>(value),
149 },
150 8 => Self {
151 inlined: Inlined::new::<8>(value),
152 },
153 9 => Self {
154 inlined: Inlined::new::<9>(value),
155 },
156 10 => Self {
157 inlined: Inlined::new::<10>(value),
158 },
159 11 => Self {
160 inlined: Inlined::new::<11>(value),
161 },
162 12 => Self {
163 inlined: Inlined::new::<12>(value),
164 },
165 _ => Self::new_ref(
166 u32::try_from(value.len()).vortex_expect("value length must fit in u32"),
167 value[0..4]
168 .try_into()
169 .ok()
170 .vortex_expect("prefix must be exactly 4 bytes"),
171 block,
172 offset,
173 ),
174 }
175 }
176
177 #[inline]
179 pub fn empty_view() -> Self {
180 Self { le_bytes: [0; 16] }
181 }
182
183 #[inline]
192 pub fn new_ref(size: u32, prefix: [u8; 4], buffer_index: u32, offset: u32) -> Self {
193 debug_assert!(size as usize > Self::MAX_INLINED_SIZE);
194 Self::from(
197 u128::from(size)
198 | (u128::from(u32::from_le_bytes(prefix)) << 32)
199 | (u128::from(buffer_index) << 64)
200 | (u128::from(offset) << 96),
201 )
202 }
203
204 #[inline]
210 pub fn new_inlined(value: &[u8]) -> Self {
211 assert!(
212 value.len() <= Self::MAX_INLINED_SIZE,
213 "expected inlined value to be <= 12 bytes, was {}",
214 value.len()
215 );
216
217 Self::make_view(value, 0, 0)
218 }
219
220 #[inline]
222 pub fn len(&self) -> u32 {
223 unsafe { self.inlined.size }
224 }
225
226 #[inline]
228 pub fn is_empty(&self) -> bool {
229 self.len() == 0
230 }
231
232 #[inline]
234 #[expect(
235 clippy::cast_possible_truncation,
236 reason = "MAX_INLINED_SIZE is a small constant"
237 )]
238 pub fn is_inlined(&self) -> bool {
239 self.len() <= (Self::MAX_INLINED_SIZE as u32)
240 }
241
242 pub fn as_inlined(&self) -> &Inlined {
244 debug_assert!(self.is_inlined());
245 unsafe { &self.inlined }
246 }
247
248 pub fn as_view(&self) -> &Ref {
250 debug_assert!(!self.is_inlined());
251 unsafe { &self._ref }
252 }
253
254 pub fn as_view_mut(&mut self) -> &mut Ref {
256 unsafe { &mut self._ref }
257 }
258
259 #[inline]
265 pub fn bytes<'a>(&'a self, buffers: &[&'a [u8]]) -> &'a [u8] {
266 if self.is_inlined() {
267 self.as_inlined().value()
268 } else {
269 let view = self.as_view();
270 &buffers[view.buffer_index as usize][view.as_range()]
271 }
272 }
273
274 #[inline]
276 #[expect(clippy::cast_possible_truncation, reason = "intentional bit slicing")]
277 pub fn prefix(&self) -> [u8; 4] {
278 ((self.as_u128() >> 32) as u32).to_le_bytes()
279 }
280
281 #[inline]
283 #[expect(clippy::cast_possible_truncation, reason = "intentional bit slicing")]
284 pub fn head(&self) -> u64 {
285 self.as_u128() as u64
286 }
287
288 #[inline]
290 pub fn order_prefix(&self) -> u32 {
291 u32::from_be_bytes(self.prefix())
292 }
293
294 #[inline]
297 pub fn order_tail(&self) -> u64 {
298 debug_assert!(self.is_inlined(), "order_tail of a reference view");
299 ((self.as_u128() >> 64) as u64).swap_bytes()
300 }
301
302 #[inline]
304 pub fn prefix_of(value: &[u8]) -> [u8; 4] {
305 let mut prefix = [0u8; 4];
306 let len = value.len().min(prefix.len());
307 prefix[..len].copy_from_slice(&value[..len]);
308 prefix
309 }
310
311 #[inline]
314 pub fn head_of(value: &[u8]) -> u64 {
315 let len = u32::try_from(value.len()).unwrap_or(u32::MAX);
316 u64::from(len) | (u64::from(u32::from_le_bytes(Self::prefix_of(value))) << 32)
317 }
318
319 #[inline]
321 pub fn order_prefix_of(value: &[u8]) -> u32 {
322 u32::from_be_bytes(Self::prefix_of(value))
323 }
324
325 #[inline]
327 pub fn order_tail_of(value: &[u8]) -> u64 {
328 let mut tail = [0u8; 8];
329 let value = value.get(4..).unwrap_or_default();
330 let len = value.len().min(tail.len());
331 tail[..len].copy_from_slice(&value[..len]);
332 u64::from_be_bytes(tail)
333 }
334
335 pub fn as_u128(&self) -> u128 {
337 unsafe { u128::from_le_bytes(self.le_bytes) }
339 }
340}
341
342impl From<u128> for BinaryView {
343 fn from(value: u128) -> Self {
344 BinaryView {
345 le_bytes: value.to_le_bytes(),
346 }
347 }
348}
349
350impl From<Ref> for BinaryView {
351 fn from(value: Ref) -> Self {
352 BinaryView { _ref: value }
353 }
354}
355
356impl PartialEq for BinaryView {
357 fn eq(&self, other: &Self) -> bool {
358 let a = unsafe { std::mem::transmute::<&BinaryView, &u128>(self) };
359 let b = unsafe { std::mem::transmute::<&BinaryView, &u128>(other) };
360 a == b
361 }
362}
363impl Eq for BinaryView {}
364
365impl Hash for BinaryView {
366 fn hash<H: Hasher>(&self, state: &mut H) {
367 unsafe { std::mem::transmute::<&BinaryView, &u128>(self) }.hash(state);
368 }
369}
370
371impl Default for BinaryView {
372 fn default() -> Self {
373 Self::make_view(&[], 0, 0)
374 }
375}
376
377impl fmt::Debug for BinaryView {
378 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
379 let mut s = f.debug_struct("BinaryView");
380 if self.is_inlined() {
381 s.field("inline", &self.as_inlined());
382 } else {
383 s.field("ref", &self.as_view());
384 }
385 s.finish()
386 }
387}
388
389#[cfg(test)]
390mod tests;