1use itertools::Itertools;
5use num_traits::AsPrimitive;
6use vortex_buffer::Buffer;
7use vortex_buffer::BufferMut;
8use vortex_buffer::ByteBuffer;
9
10pub use crate::arrays::varbinview::BinaryView;
11use crate::dtype::NativePType;
12
13#[inline]
15pub fn offsets_to_lengths<P: NativePType>(offsets: &[P]) -> Buffer<P> {
16 offsets
17 .iter()
18 .tuple_windows::<(_, _)>()
19 .map(|(&start, &end)| end - start)
20 .collect()
21}
22
23pub const MAX_BUFFER_LEN: usize = i32::MAX as usize;
25
26pub fn build_views<P: NativePType + AsPrimitive<usize>>(
40 start_buf_index: u32,
41 max_buffer_len: usize,
42 bytes: ByteBuffer,
43 lens: &[P],
44) -> (Vec<ByteBuffer>, Buffer<BinaryView>) {
45 let mut views = BufferMut::with_capacity(lens.len());
46 let buffers = extend_views(
47 &mut views,
48 start_buf_index,
49 max_buffer_len,
50 &bytes,
51 lens.len(),
52 |i| lens[i].as_(),
53 );
54 (buffers, views.freeze())
55}
56
57pub fn build_views_from_offsets<P: NativePType + AsPrimitive<usize>>(
69 start_buf_index: u32,
70 max_buffer_len: usize,
71 bytes: ByteBuffer,
72 offsets: &[P],
73) -> (Vec<ByteBuffer>, Buffer<BinaryView>) {
74 assert!(!offsets.is_empty(), "offsets must hold at least one entry");
75 let first: usize = offsets[0].as_();
76 let last: usize = offsets[offsets.len() - 1].as_();
77 let bytes = bytes.slice(first..last);
78
79 let count = offsets.len() - 1;
80 let mut views = BufferMut::with_capacity(count);
81 let buffers = extend_views(
84 &mut views,
85 start_buf_index,
86 max_buffer_len,
87 &bytes,
88 count,
89 |i| {
90 AsPrimitive::<usize>::as_(offsets[i + 1])
91 .wrapping_sub(AsPrimitive::<usize>::as_(offsets[i]))
92 },
93 );
94 (buffers, views.freeze())
95}
96
97pub(crate) fn extend_views(
105 views: &mut BufferMut<BinaryView>,
106 start_buf_index: u32,
107 max_buffer_len: usize,
108 bytes: &ByteBuffer,
109 count: usize,
110 len_at: impl Fn(usize) -> usize,
111) -> Vec<ByteBuffer> {
112 assert!(
113 max_buffer_len <= MAX_BUFFER_LEN,
114 "max_buffer_len cannot exceed MAX_BUFFER_LEN, offsets must fit in u32"
115 );
116
117 if bytes.len() <= max_buffer_len {
118 extend_views_single_buffer(views, start_buf_index, bytes, count, len_at);
122 if bytes.is_empty() {
123 Vec::new()
124 } else {
125 vec![bytes.clone()]
126 }
127 } else {
128 extend_views_rolling(views, start_buf_index, max_buffer_len, bytes, count, len_at)
129 }
130}
131
132fn extend_views_single_buffer(
139 views: &mut BufferMut<BinaryView>,
140 buf_index: u32,
141 bytes: &ByteBuffer,
142 count: usize,
143 len_at: impl Fn(usize) -> usize,
144) {
145 views.reserve(count);
146 let base = views.len();
147
148 let data = bytes.as_slice();
149 let mut offset = 0usize;
150 let spare = &mut views.spare_capacity_mut()[..count];
156 for (i, slot) in spare.iter_mut().enumerate() {
157 let len = len_at(i);
158 let value = &data[offset..offset + len];
159 let view = if len > BinaryView::MAX_INLINED_SIZE {
160 let mut prefix = [0u8; 4];
161 prefix.copy_from_slice(&value[..4]);
162 BinaryView::new_ref(len.as_(), prefix, buf_index, offset.as_())
163 } else {
164 BinaryView::make_view(value, buf_index, offset.as_())
165 };
166 slot.write(view);
167 offset += len;
168 }
169 assert_eq!(
170 offset,
171 data.len(),
172 "value lengths must describe the byte heap exactly"
173 );
174 unsafe { views.set_len(base + count) };
177}
178
179fn extend_views_rolling(
184 views: &mut BufferMut<BinaryView>,
185 start_buf_index: u32,
186 max_buffer_len: usize,
187 bytes: &ByteBuffer,
188 count: usize,
189 len_at: impl Fn(usize) -> usize,
190) -> Vec<ByteBuffer> {
191 views.reserve(count);
192 let mut buffers = Vec::new();
193 let mut buf_index = start_buf_index;
194
195 let data = bytes.as_slice();
196 let mut segment_start = 0usize;
198 let mut offset = 0usize;
199 for i in 0..count {
200 let len = len_at(i);
201 assert!(len <= max_buffer_len, "values cannot exceed max_buffer_len");
202
203 if offset + len > max_buffer_len {
204 buffers.push(bytes.slice(segment_start..segment_start + offset));
206 buf_index += 1;
207 segment_start += offset;
208 offset = 0;
209 }
210 let start = segment_start + offset;
211 let view = BinaryView::make_view(&data[start..start + len], buf_index, offset.as_());
212 views.push(view);
213 offset += len;
214 }
215 assert_eq!(
216 segment_start + offset,
217 data.len(),
218 "value lengths must describe the byte heap exactly"
219 );
220
221 if segment_start < data.len() {
222 buffers.push(bytes.slice(segment_start..data.len()));
223 }
224
225 buffers
226}
227
228#[cfg(test)]
229mod tests {
230 use rstest::rstest;
231 use vortex_buffer::ByteBuffer;
232 use vortex_buffer::ByteBufferMut;
233
234 use crate::arrays::varbinview::BinaryView;
235 use crate::arrays::varbinview::build_views::MAX_BUFFER_LEN;
236 use crate::arrays::varbinview::build_views::build_views;
237 use crate::arrays::varbinview::build_views::build_views_from_offsets;
238
239 fn flatten(values: &[&[u8]]) -> (ByteBuffer, Vec<u32>) {
242 let mut bytes = ByteBufferMut::empty();
243 let mut lens = Vec::with_capacity(values.len());
244 for v in values {
245 bytes.extend_from_slice(v);
246 lens.push(u32::try_from(v.len()).unwrap());
247 }
248 (bytes.freeze(), lens)
249 }
250
251 fn reconstruct(
256 buffers: &[ByteBuffer],
257 views: &[BinaryView],
258 start_buf_index: u32,
259 ) -> Vec<Vec<u8>> {
260 views
261 .iter()
262 .map(|view| {
263 if view.is_inlined() {
264 view.as_inlined().value().to_vec()
265 } else {
266 let r = view.as_view();
267 let buf = &buffers[(r.buffer_index - start_buf_index) as usize];
268 buf[r.as_range()].to_vec()
269 }
270 })
271 .collect()
272 }
273
274 #[rstest]
280 #[case::mixed(&[b"a".as_slice(), b"this is a long reference value", b"short", b"another long value here!!"])]
281 #[case::inline_boundary(&[&[b'x'; 12] as &[u8], &[b'y'; 13], &[b'z'; 12], &[b'w'; 13]])]
282 #[case::all_inlined(&[b"".as_slice(), b"a", b"bb", b"ccc", b"dddddddddddd"])]
283 #[case::all_reference(&[&[b'a'; 100] as &[u8], &[b'b'; 50], &[b'c'; 4096]])]
284 #[case::empty_values_interleaved(&[b"".as_slice(), b"a long value that is referenced", b"", b"", b"trailing long reference value"])]
285 #[case::single_long(&[&[7u8; 1 << 16] as &[u8]])]
286 fn fast_path_roundtrip(#[case] values: &[&[u8]]) {
287 let (bytes, lens) = flatten(values);
288 let total = bytes.len();
289 let start_buf_index = 3;
290
291 let (buffers, views) = build_views(start_buf_index, total + 1, bytes, &lens);
293
294 assert_eq!(views.len(), values.len());
295 if total == 0 {
296 assert!(buffers.is_empty(), "empty heap must not allocate a buffer");
297 } else {
298 assert_eq!(buffers.len(), 1, "whole heap must stay in one buffer");
299 let concatenated: Vec<u8> = values.concat();
301 assert_eq!(buffers[0].as_slice(), concatenated.as_slice());
302 }
303 for view in views.iter() {
304 if !view.is_inlined() {
305 assert_eq!(view.as_view().buffer_index, start_buf_index);
306 }
307 }
308
309 let expected: Vec<Vec<u8>> = values.iter().map(|v| v.to_vec()).collect();
310 assert_eq!(reconstruct(&buffers, &views, start_buf_index), expected);
311 }
312
313 #[test]
316 fn output_buffers_are_zero_copy() {
317 let values: &[&[u8]] = &[
318 b"first long reference value",
319 b"tiny",
320 b"second long reference value!!",
321 b"third looooong reference value",
322 ];
323 let (bytes, lens) = flatten(values);
324 let base = bytes.as_ptr();
325
326 let (buffers, _views) = build_views(0, bytes.len() + 1, bytes.clone(), &lens);
328 assert_eq!(buffers.len(), 1);
329 assert_eq!(buffers[0].as_ptr(), base, "fast path must not copy");
330
331 let longest = values.iter().map(|v| v.len()).max().unwrap();
333 let (buffers, _views) = build_views(0, longest, bytes, &lens);
334 assert!(buffers.len() > 1);
335 let mut expected_ptr = base;
336 for buffer in &buffers {
337 assert_eq!(buffer.as_ptr(), expected_ptr, "rolling path must not copy");
338 expected_ptr = unsafe { expected_ptr.add(buffer.len()) };
341 }
342 }
343
344 #[test]
350 fn fast_path_large_offsets() {
351 const N: usize = 9000;
352 const LEN: usize = 1000;
353 const { assert!((N - 1) * LEN > (1 << 23)) };
355
356 let values: Vec<Vec<u8>> = (0..N)
357 .map(|i| {
358 let mut v = vec![0u8; LEN];
359 v[..4].copy_from_slice(&u32::try_from(i).unwrap().to_le_bytes());
360 v
361 })
362 .collect();
363 let refs: Vec<&[u8]> = values.iter().map(|v| v.as_slice()).collect();
364
365 let (bytes, lens) = flatten(&refs);
366 let total = bytes.len();
367
368 let (buffers, views) = build_views(0, total + 1, bytes, &lens);
369
370 assert_eq!(buffers.len(), 1);
371 for (i, view) in views.iter().enumerate() {
373 let r = view.as_view();
374 assert_eq!(r.offset as usize, i * LEN, "wrong offset for view {i}");
375 assert_eq!(r.size as usize, LEN);
376 }
377 assert_eq!(reconstruct(&buffers, &views, 0), values);
378 }
379
380 #[test]
383 fn fast_path_taken_at_exact_boundary() {
384 let (bytes, lens) =
385 flatten(&[b"this value is definitely long", b"and so is this one here"]);
386 let total = bytes.len();
387
388 let (buffers, views) = build_views(0, total, bytes, &lens);
389
390 assert_eq!(
391 buffers.len(),
392 1,
393 "len == max_buffer_len must stay on fast path"
394 );
395 assert_eq!(views.len(), 2);
396 }
397
398 #[test]
402 fn fast_and_slow_paths_agree() {
403 let values: &[&[u8]] = &[
404 b"first long reference value",
405 b"tiny",
406 b"second long reference value!!",
407 b"third looooong reference value",
408 ];
409 let expected: Vec<Vec<u8>> = values.iter().map(|v| v.to_vec()).collect();
410
411 let (fast_bytes, lens) = flatten(values);
412 let total = fast_bytes.len();
413 let (fast_buffers, fast_views) = build_views(0, total + 1, fast_bytes, &lens);
414 assert_eq!(fast_buffers.len(), 1);
415 assert_eq!(reconstruct(&fast_buffers, &fast_views, 0), expected);
416
417 let longest = values.iter().map(|v| v.len()).max().unwrap();
419 let (slow_bytes, _) = flatten(values);
420 let (slow_buffers, slow_views) = build_views(0, longest, slow_bytes, &lens);
421 assert!(
422 slow_buffers.len() > 1,
423 "small cap should split into many buffers"
424 );
425 assert_eq!(reconstruct(&slow_buffers, &slow_views, 0), expected);
426
427 assert_eq!(
429 reconstruct(&fast_buffers, &fast_views, 0),
430 reconstruct(&slow_buffers, &slow_views, 0)
431 );
432 }
433
434 #[test]
436 fn fast_path_empty_input() {
437 let lens: Vec<u32> = Vec::new();
438 let (buffers, views) = build_views(0, 1024, ByteBuffer::empty(), &lens);
439 assert!(buffers.is_empty());
440 assert!(views.is_empty());
441 }
442
443 #[test]
446 fn fast_path_matches_make_view() {
447 let values: &[&[u8]] = &[b"inline", b"this is a long reference value", b""];
448 let (bytes, lens) = flatten(values);
449 let total = bytes.len();
450 let (_buffers, views) = build_views(0, total + 1, bytes, &lens);
451
452 let expected = [
453 BinaryView::make_view(b"inline", 0, 0),
454 BinaryView::make_view(b"this is a long reference value", 0, 6),
455 BinaryView::make_view(b"", 0, 36),
456 ];
457 assert_eq!(views.as_slice(), &expected);
458 }
459
460 #[test]
464 fn from_offsets_matches_lengths_and_is_zero_copy() {
465 let heap = ByteBuffer::copy_from(b"..a long value that is referenced!tiny..".as_slice());
467 let offsets: Vec<u32> = vec![2, 34, 38];
468
469 let (buffers, views) = build_views_from_offsets(5, MAX_BUFFER_LEN, heap.clone(), &offsets);
470
471 assert_eq!(buffers.len(), 1);
472 assert_eq!(buffers[0].as_ptr(), unsafe { heap.as_ptr().add(2) });
475 assert_eq!(buffers[0].len(), 36);
476
477 assert_eq!(
478 reconstruct(&buffers, &views, 5),
479 vec![
480 b"a long value that is referenced!".to_vec(),
481 b"tiny".to_vec()
482 ]
483 );
484 }
485
486 #[test]
489 #[should_panic(expected = "value lengths must describe the byte heap exactly")]
490 fn short_lengths_panic() {
491 let (bytes, _) = flatten(&[b"a long value that is referenced", b"tiny"]);
492 build_views(0, MAX_BUFFER_LEN, bytes, &[31u32]);
493 }
494
495 #[test_with::env(CI)]
517 #[test_with::no_env(VORTEX_SKIP_SLOW_TESTS)]
518 fn build_views_offsets_overflow_i32() {
519 const STRING_LEN: usize = 64 * 1024;
520 const TOTAL_BYTES: usize = (1usize << 31) + (256 << 20); const N: usize = TOTAL_BYTES / STRING_LEN;
523
524 let nth_string = |i: usize| {
526 let mut s = vec![b'x'; STRING_LEN];
527 s[..8].copy_from_slice(&(i as u64).to_le_bytes());
528 s
529 };
530
531 let mut bytes = ByteBufferMut::with_capacity(N * STRING_LEN);
532 let mut value = vec![b'x'; STRING_LEN];
533 for i in 0..N {
534 value[..8].copy_from_slice(&(i as u64).to_le_bytes());
535 bytes.extend_from_slice(&value);
536 }
537
538 let lens = vec![u32::try_from(STRING_LEN).unwrap(); N];
539 let (buffers, views) = build_views(0, MAX_BUFFER_LEN, bytes.freeze(), &lens);
540
541 assert_eq!(views.len(), N);
542 assert!(
543 buffers.len() >= 2,
544 "heap exceeding MAX_BUFFER_LEN must roll over into multiple buffers, got {}",
545 buffers.len()
546 );
547 for (i, b) in buffers.iter().enumerate() {
548 assert!(
549 b.len() <= MAX_BUFFER_LEN,
550 "buffer {i} of {} bytes exceeds MAX_BUFFER_LEN",
551 b.len()
552 );
553 }
554
555 let boundary = MAX_BUFFER_LEN / STRING_LEN;
557 for i in [0, boundary - 1, boundary, boundary + 1, N / 2, N - 1] {
558 let view = &views[i];
559 let r = view.as_view();
560 let got = &buffers[r.buffer_index as usize][r.as_range()];
561 assert_eq!(got, nth_string(i).as_slice(), "value mismatch at row {i}");
562 assert_eq!(r.size as usize, STRING_LEN);
563 }
564 }
565
566 #[test]
567 fn test_to_canonical_large() {
568 let raw_data =
578 ByteBuffer::copy_from("aaaaaaaaaaaaabbbbbbbbbbbbbcccccccccccccddddddddddddd");
579 let lens = vec![13u8; 4];
580
581 let (buffers, views) = build_views(0, 26, raw_data, &lens);
582
583 assert_eq!(
584 buffers,
585 vec![
586 ByteBuffer::copy_from("aaaaaaaaaaaaabbbbbbbbbbbbb"),
587 ByteBuffer::copy_from("cccccccccccccddddddddddddd"),
588 ]
589 );
590
591 assert_eq!(
592 views.as_slice(),
593 &[
594 BinaryView::make_view(b"aaaaaaaaaaaaa", 0, 0),
595 BinaryView::make_view(b"bbbbbbbbbbbbb", 0, 13),
596 BinaryView::make_view(b"ccccccccccccc", 1, 0),
597 BinaryView::make_view(b"ddddddddddddd", 1, 13),
598 ]
599 )
600 }
601
602 #[test]
603 #[should_panic(expected = "max_buffer_len cannot exceed MAX_BUFFER_LEN")]
604 fn test_max_buffer_len_too_large_panics() {
605 build_views(0, MAX_BUFFER_LEN + 1, ByteBuffer::copy_from("abc"), &[3u32]);
606 }
607}