arrow_array/array/byte_view_array.rs
1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements. See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership. The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License. You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied. See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use crate::array::print_long_array;
19use crate::builder::{ArrayBuilder, GenericByteViewBuilder};
20use crate::iterator::ArrayIter;
21use crate::types::bytes::ByteArrayNativeType;
22use crate::types::{BinaryViewType, ByteViewType, StringViewType};
23use crate::{Array, ArrayAccessor, ArrayRef, GenericByteArray, OffsetSizeTrait, Scalar};
24use arrow_buffer::{ArrowNativeType, Buffer, NullBuffer, ScalarBuffer};
25use arrow_data::{ArrayData, ArrayDataBuilder, ByteView, MAX_INLINE_VIEW_LEN};
26use arrow_schema::{ArrowError, DataType};
27use core::str;
28use num_traits::ToPrimitive;
29use std::any::Any;
30use std::cmp::Ordering;
31use std::fmt::Debug;
32use std::marker::PhantomData;
33use std::sync::Arc;
34
35use super::ByteArrayType;
36
37/// [Variable-size Binary View Layout]: An array of variable length bytes views.
38///
39/// This array type is used to store variable length byte data (e.g. Strings, Binary)
40/// and has efficient operations such as `take`, `filter`, and comparison.
41///
42/// [Variable-size Binary View Layout]: https://arrow.apache.org/docs/format/Columnar.html#variable-size-binary-view-layout
43///
44/// This is different from [`GenericByteArray`], which also stores variable
45/// length byte data, as it represents strings with an offset and length. `take`
46/// and `filter` like operations are implemented by manipulating the "views"
47/// (`u128`) without modifying the bytes. Each view also stores an inlined
48/// prefix which speed up comparisons.
49///
50/// # See Also
51///
52/// * [`StringViewArray`] for storing utf8 encoded string data
53/// * [`BinaryViewArray`] for storing bytes
54/// * [`ByteView`] to interpret `u128`s layout of the views.
55///
56/// [`ByteView`]: arrow_data::ByteView
57///
58/// # Layout: "views" and buffers
59///
60/// A `GenericByteViewArray` stores variable length byte strings. An array of
61/// `N` elements is stored as `N` fixed length "views" and a variable number
62/// of variable length "buffers".
63///
64/// Each view is a `u128` value whose layout is different depending on the
65/// length of the string stored at that location:
66///
67/// ```text
68/// ┌──────┬────────────────────────┐
69/// │length│ string value │
70/// Strings (len <= 12) │ │ (padded with 0) │
71/// └──────┴────────────────────────┘
72/// 0 31 127
73///
74/// ┌───────┬───────┬───────┬───────┐
75/// │length │prefix │ buf │offset │
76/// Strings (len > 12) │ │ │ index │ │
77/// └───────┴───────┴───────┴───────┘
78/// 0 31 63 95 127
79/// ```
80///
81/// * Strings with length <= 12 ([`MAX_INLINE_VIEW_LEN`]) are stored directly in
82/// the view. See [`Self::inline_value`] to access the inlined prefix from a
83/// short view.
84///
85/// * Strings with length > 12: The first four bytes are stored inline in the
86/// view and the entire string is stored in one of the buffers. See [`ByteView`]
87/// to access the fields of the these views.
88///
89/// As with other arrays, the optimized kernels in [`arrow_compute`] are likely
90/// the easiest and fastest way to work with this data. However, it is possible
91/// to access the views and buffers directly for more control.
92///
93/// For example
94///
95/// ```rust
96/// # use arrow_array::StringViewArray;
97/// # use arrow_array::Array;
98/// use arrow_data::ByteView;
99/// let array = StringViewArray::from(vec![
100/// "hello",
101/// "this string is longer than 12 bytes",
102/// "this string is also longer than 12 bytes"
103/// ]);
104///
105/// // ** Examine the first view (short string) **
106/// assert!(array.is_valid(0)); // Check for nulls
107/// let short_view: u128 = array.views()[0]; // "hello"
108/// // get length of the string
109/// let len = short_view as u32;
110/// assert_eq!(len, 5); // strings less than 12 bytes are stored in the view
111/// // SAFETY: `view` is a valid view
112/// let value = unsafe {
113/// StringViewArray::inline_value(&short_view, len as usize)
114/// };
115/// assert_eq!(value, b"hello");
116///
117/// // ** Examine the third view (long string) **
118/// assert!(array.is_valid(12)); // Check for nulls
119/// let long_view: u128 = array.views()[2]; // "this string is also longer than 12 bytes"
120/// let len = long_view as u32;
121/// assert_eq!(len, 40); // strings longer than 12 bytes are stored in the buffer
122/// let view = ByteView::from(long_view); // use ByteView to access the fields
123/// assert_eq!(view.length, 40);
124/// assert_eq!(view.buffer_index, 0);
125/// assert_eq!(view.offset, 35); // data starts after the first long string
126/// // Views for long strings store a 4 byte prefix
127/// let prefix = view.prefix.to_le_bytes();
128/// assert_eq!(&prefix, b"this");
129/// let value = array.value(2); // get the string value (see `value` implementation for how to access the bytes directly)
130/// assert_eq!(value, "this string is also longer than 12 bytes");
131/// ```
132///
133/// [`MAX_INLINE_VIEW_LEN`]: arrow_data::MAX_INLINE_VIEW_LEN
134/// [`arrow_compute`]: https://docs.rs/arrow/latest/arrow/compute/index.html
135///
136/// Unlike [`GenericByteArray`], there are no constraints on the offsets other
137/// than they must point into a valid buffer. However, they can be out of order,
138/// non continuous and overlapping.
139///
140/// For example, in the following diagram, the strings "FishWasInTownToday" and
141/// "CrumpleFacedFish" are both longer than 12 bytes and thus are stored in a
142/// separate buffer while the string "LavaMonster" is stored inlined in the
143/// view. In this case, the same bytes for "Fish" are used to store both strings.
144///
145/// [`ByteView`]: arrow_data::ByteView
146///
147/// ```text
148/// ┌───┐
149/// ┌──────┬──────┬──────┬──────┐ offset │...│
150/// "FishWasInTownTodayYay" │ 21 │ Fish │ 0 │ 115 │─ ─ 103 │Mr.│
151/// └──────┴──────┴──────┴──────┘ │ ┌ ─ ─ ─ ─ ▶ │Cru│
152/// ┌──────┬──────┬──────┬──────┐ │mpl│
153/// "CrumpleFacedFish" │ 16 │ Crum │ 0 │ 103 │─ ─│─ ─ ─ ┘ │eFa│
154/// └──────┴──────┴──────┴──────┘ │ced│
155/// ┌──────┬────────────────────┐ └ ─ ─ ─ ─ ─ ─ ─ ─ ▶│Fis│
156/// "LavaMonster" │ 11 │ LavaMonster │ │hWa│
157/// └──────┴────────────────────┘ offset │sIn│
158/// 115 │Tow│
159/// │nTo│
160/// │day│
161/// u128 "views" │Yay│
162/// buffer 0 │...│
163/// └───┘
164/// ```
165pub struct GenericByteViewArray<T: ByteViewType + ?Sized> {
166 data_type: DataType,
167 views: ScalarBuffer<u128>,
168 buffers: Arc<[Buffer]>,
169 phantom: PhantomData<T>,
170 nulls: Option<NullBuffer>,
171}
172
173impl<T: ByteViewType + ?Sized> Clone for GenericByteViewArray<T> {
174 fn clone(&self) -> Self {
175 Self {
176 data_type: T::DATA_TYPE,
177 views: self.views.clone(),
178 buffers: self.buffers.clone(),
179 nulls: self.nulls.clone(),
180 phantom: Default::default(),
181 }
182 }
183}
184
185impl<T: ByteViewType + ?Sized> GenericByteViewArray<T> {
186 /// Create a new [`GenericByteViewArray`] from the provided parts, panicking on failure
187 ///
188 /// # Panics
189 ///
190 /// Panics if [`GenericByteViewArray::try_new`] returns an error
191 pub fn new<U>(views: ScalarBuffer<u128>, buffers: U, nulls: Option<NullBuffer>) -> Self
192 where
193 U: Into<Arc<[Buffer]>>,
194 {
195 Self::try_new(views, buffers, nulls).unwrap()
196 }
197
198 /// Create a new [`GenericByteViewArray`] from the provided parts, returning an error on failure
199 ///
200 /// # Errors
201 ///
202 /// * `views.len() != nulls.len()`
203 /// * [ByteViewType::validate] fails
204 pub fn try_new<U>(
205 views: ScalarBuffer<u128>,
206 buffers: U,
207 nulls: Option<NullBuffer>,
208 ) -> Result<Self, ArrowError>
209 where
210 U: Into<Arc<[Buffer]>>,
211 {
212 let buffers: Arc<[Buffer]> = buffers.into();
213
214 T::validate(&views, &buffers)?;
215
216 if let Some(n) = nulls.as_ref()
217 && n.len() != views.len()
218 {
219 return Err(ArrowError::InvalidArgumentError(format!(
220 "Incorrect length of null buffer for {}ViewArray, expected {} got {}",
221 T::PREFIX,
222 views.len(),
223 n.len(),
224 )));
225 }
226
227 Ok(Self {
228 data_type: T::DATA_TYPE,
229 views,
230 buffers,
231 nulls,
232 phantom: Default::default(),
233 })
234 }
235
236 /// Create a new [`GenericByteViewArray`] from the provided parts, without validation
237 ///
238 /// # Safety
239 ///
240 /// Safe if [`Self::try_new`] would not error
241 pub unsafe fn new_unchecked(
242 views: ScalarBuffer<u128>,
243 buffers: Arc<[Buffer]>,
244 nulls: Option<NullBuffer>,
245 ) -> Self {
246 if cfg!(feature = "force_validate") {
247 return Self::new(views, buffers, nulls);
248 }
249
250 Self {
251 data_type: T::DATA_TYPE,
252 phantom: Default::default(),
253 views,
254 buffers,
255 nulls,
256 }
257 }
258
259 /// Create a new [`GenericByteViewArray`] of length `len` where all values are null
260 pub fn new_null(len: usize) -> Self {
261 Self {
262 data_type: T::DATA_TYPE,
263 views: vec![0; len].into(),
264 buffers: vec![].into(),
265 nulls: Some(NullBuffer::new_null(len)),
266 phantom: Default::default(),
267 }
268 }
269
270 /// Create a new [`Scalar`] from `value`
271 pub fn new_scalar(value: impl AsRef<T::Native>) -> Scalar<Self> {
272 Scalar::new(Self::from_iter_values(std::iter::once(value)))
273 }
274
275 /// Creates a [`GenericByteViewArray`] based on an iterator of values without nulls
276 pub fn from_iter_values<Ptr, I>(iter: I) -> Self
277 where
278 Ptr: AsRef<T::Native>,
279 I: IntoIterator<Item = Ptr>,
280 {
281 let iter = iter.into_iter();
282 let mut builder = GenericByteViewBuilder::<T>::with_capacity(iter.size_hint().0);
283 for v in iter {
284 builder.append_value(v);
285 }
286 builder.finish()
287 }
288
289 /// Deconstruct this array into its constituent parts
290 pub fn into_parts(self) -> (ScalarBuffer<u128>, Arc<[Buffer]>, Option<NullBuffer>) {
291 (self.views, self.buffers, self.nulls)
292 }
293
294 /// Returns the views buffer
295 #[inline]
296 pub fn views(&self) -> &ScalarBuffer<u128> {
297 &self.views
298 }
299
300 /// Returns the shared collection of buffers storing non-inline string or binary data.
301 ///
302 /// The returned `Arc` can be cloned to share the buffers with another array without
303 /// allocating a new collection or cloning the individual buffers. To consume this
304 /// array and take ownership of its buffers, use [`Self::into_parts`].
305 #[inline]
306 pub fn data_buffers(&self) -> &Arc<[Buffer]> {
307 &self.buffers
308 }
309
310 /// Returns the element at index `i`
311 ///
312 /// Note: This method does not check for nulls and the value is arbitrary
313 /// (but still well-defined) if [`is_null`](Self::is_null) returns true for the index.
314 ///
315 /// # Panics
316 /// Panics if index `i` is out of bounds.
317 pub fn value(&self, i: usize) -> &T::Native {
318 assert!(
319 i < self.len(),
320 "Trying to access an element at index {} from a {}ViewArray of length {}",
321 i,
322 T::PREFIX,
323 self.len()
324 );
325
326 unsafe { self.value_unchecked(i) }
327 }
328
329 /// Returns the element at index `i` without bounds checking
330 ///
331 /// Note: This method does not check for nulls and the value is arbitrary
332 /// if [`is_null`](Self::is_null) returns true for the index.
333 ///
334 /// # Safety
335 ///
336 /// Caller is responsible for ensuring that the index is within the bounds
337 /// of the array
338 pub unsafe fn value_unchecked(&self, idx: usize) -> &T::Native {
339 let v = unsafe { self.views.get_unchecked(idx) };
340 let len = *v as u32;
341 let b = if len <= MAX_INLINE_VIEW_LEN {
342 unsafe { Self::inline_value(v, len as usize) }
343 } else {
344 let view = ByteView::from(*v);
345 let data = unsafe { self.buffers.get_unchecked(view.buffer_index as usize) };
346 let offset = view.offset as usize;
347 unsafe { data.get_unchecked(offset..offset + len as usize) }
348 };
349 unsafe { T::Native::from_bytes_unchecked(b) }
350 }
351
352 /// Returns the first `len` bytes the inline value of the view.
353 ///
354 /// # Safety
355 /// - The `view` must be a valid element from `Self::views()` that adheres to the view layout.
356 /// - The `len` must be the length of the inlined value. It should never be larger than [`MAX_INLINE_VIEW_LEN`].
357 #[inline(always)]
358 pub unsafe fn inline_value(view: &u128, len: usize) -> &[u8] {
359 debug_assert!(len <= MAX_INLINE_VIEW_LEN as usize);
360 unsafe {
361 std::slice::from_raw_parts(
362 std::ptr::from_ref::<u128>(view)
363 .cast::<u8>()
364 .wrapping_add(4),
365 len,
366 )
367 }
368 }
369
370 /// Constructs a new iterator for iterating over the values of this array
371 pub fn iter(&self) -> ArrayIter<&Self> {
372 ArrayIter::new(self)
373 }
374
375 /// Returns an iterator over the bytes of this array, including null values
376 pub fn bytes_iter(&self) -> impl Iterator<Item = &[u8]> {
377 self.views.iter().map(move |v| {
378 let len = *v as u32;
379 if len <= MAX_INLINE_VIEW_LEN {
380 unsafe { Self::inline_value(v, len as usize) }
381 } else {
382 let view = ByteView::from(*v);
383 let data = &self.buffers[view.buffer_index as usize];
384 let offset = view.offset as usize;
385 unsafe { data.get_unchecked(offset..offset + len as usize) }
386 }
387 })
388 }
389
390 /// Returns an iterator over the first `prefix_len` bytes of each array
391 /// element, including null values.
392 ///
393 /// If `prefix_len` is larger than the element's length, the iterator will
394 /// return an empty slice (`&[]`).
395 pub fn prefix_bytes_iter(&self, prefix_len: usize) -> impl Iterator<Item = &[u8]> {
396 self.views().into_iter().map(move |v| {
397 let len = (*v as u32) as usize;
398
399 if len < prefix_len {
400 return &[] as &[u8];
401 }
402
403 if prefix_len <= 4 || len as u32 <= MAX_INLINE_VIEW_LEN {
404 unsafe { StringViewArray::inline_value(v, prefix_len) }
405 } else {
406 let view = ByteView::from(*v);
407 let data = unsafe {
408 self.data_buffers()
409 .get_unchecked(view.buffer_index as usize)
410 };
411 let offset = view.offset as usize;
412 unsafe { data.get_unchecked(offset..offset + prefix_len) }
413 }
414 })
415 }
416
417 /// Returns an iterator over the last `suffix_len` bytes of each array
418 /// element, including null values.
419 ///
420 /// Note that for [`StringViewArray`] the last bytes may start in the middle
421 /// of a UTF-8 codepoint, and thus may not be a valid `&str`.
422 ///
423 /// If `suffix_len` is larger than the element's length, the iterator will
424 /// return an empty slice (`&[]`).
425 pub fn suffix_bytes_iter(&self, suffix_len: usize) -> impl Iterator<Item = &[u8]> {
426 self.views().into_iter().map(move |v| {
427 let len = (*v as u32) as usize;
428
429 if len < suffix_len {
430 return &[] as &[u8];
431 }
432
433 if len as u32 <= MAX_INLINE_VIEW_LEN {
434 unsafe { &StringViewArray::inline_value(v, len)[len - suffix_len..] }
435 } else {
436 let view = ByteView::from(*v);
437 let data = unsafe {
438 self.data_buffers()
439 .get_unchecked(view.buffer_index as usize)
440 };
441 let offset = view.offset as usize;
442 unsafe { data.get_unchecked(offset + len - suffix_len..offset + len) }
443 }
444 })
445 }
446
447 /// Return an iterator over the length of each array element, including null values.
448 ///
449 /// Null values length would equal to the underlying bytes length and NOT 0
450 ///
451 /// Example of getting 0 for null values
452 /// ```rust
453 /// # use arrow_array::StringViewArray;
454 /// # use arrow_array::Array;
455 /// use arrow_data::ByteView;
456 ///
457 /// fn lengths_with_zero_for_nulls(view: &StringViewArray) -> impl Iterator<Item = u32> {
458 /// view.lengths()
459 /// .enumerate()
460 /// .map(|(index, length)| if view.is_null(index) { 0 } else { length })
461 /// }
462 /// ```
463 pub fn lengths(&self) -> impl ExactSizeIterator<Item = u32> + Clone {
464 self.views().iter().map(|v| *v as u32)
465 }
466
467 /// Returns a zero-copy slice of this array with the indicated offset and length.
468 ///
469 /// # Panics
470 /// Panics if `offset + length > self.len()`
471 pub fn slice(&self, offset: usize, length: usize) -> Self {
472 Self {
473 data_type: T::DATA_TYPE,
474 views: self.views.slice(offset, length),
475 buffers: self.buffers.clone(),
476 nulls: self.nulls.as_ref().map(|n| n.slice(offset, length)),
477 phantom: Default::default(),
478 }
479 }
480
481 /// Returns a "compacted" version of this array
482 ///
483 /// The original array will *not* be modified
484 ///
485 /// # Garbage Collection
486 ///
487 /// Before GC:
488 /// ```text
489 /// ┌──────┐
490 /// │......│
491 /// │......│
492 /// ┌────────────────────┐ ┌ ─ ─ ─ ▶ │Data1 │ Large buffer
493 /// │ View 1 │─ ─ ─ ─ │......│ with data that
494 /// ├────────────────────┤ │......│ is not referred
495 /// │ View 2 │─ ─ ─ ─ ─ ─ ─ ─▶ │Data2 │ to by View 1 or
496 /// └────────────────────┘ │......│ View 2
497 /// │......│
498 /// 2 views, refer to │......│
499 /// small portions of a └──────┘
500 /// large buffer
501 /// ```
502 ///
503 /// After GC:
504 ///
505 /// ```text
506 /// ┌────────────────────┐ ┌─────┐ After gc, only
507 /// │ View 1 │─ ─ ─ ─ ─ ─ ─ ─▶ │Data1│ data that is
508 /// ├────────────────────┤ ┌ ─ ─ ─ ▶ │Data2│ pointed to by
509 /// │ View 2 │─ ─ ─ ─ └─────┘ the views is
510 /// └────────────────────┘ left
511 ///
512 ///
513 /// 2 views
514 /// ```
515 /// This method will compact the data buffers by recreating the view array and only include the data
516 /// that is pointed to by the views.
517 ///
518 /// Note that it will copy the array regardless of whether the original array is compact.
519 /// Use with caution as this can be an expensive operation, only use it when you are sure that the view
520 /// array is significantly smaller than when it is originally created, e.g., after filtering or slicing.
521 ///
522 /// Note: this function does not attempt to canonicalize / deduplicate values. For this
523 /// feature see [`GenericByteViewBuilder::with_deduplicate_strings`].
524 pub fn gc(&self) -> Self {
525 // A single Arrow data buffer is addressed by a `u32` offset, so no
526 // output buffer may exceed `i32::MAX` bytes. Above that the data is
527 // split across multiple buffers (the "slow path").
528 self.gc_with_max_buffer_size(i32::MAX as usize)
529 }
530
531 /// Like [`Self::gc`], but with a configurable maximum output buffer size.
532 ///
533 /// `gc` always uses `i32::MAX` (the largest a single Arrow buffer can be).
534 /// This method exists so the multi-buffer split path — which `gc` only
535 /// reaches once a column references more than 2 GiB of non-inline data —
536 /// can be exercised in tests with a small threshold and tiny inputs.
537 fn gc_with_max_buffer_size(&self, max_buffer_size: usize) -> Self {
538 // 1) Read basic properties once
539 let len = self.len(); // number of elements
540 let nulls = self.nulls().cloned(); // reuse & clone existing null bitmap
541
542 // 1.5) Fast path: if there are no buffers, just reuse original views and no data blocks
543 if self.data_buffers().is_empty() {
544 return unsafe {
545 GenericByteViewArray::new_unchecked(
546 self.views().clone(),
547 Arc::from([]), // empty data blocks
548 nulls,
549 )
550 };
551 }
552
553 // 2) Calculate total size of all non-inline data and detect if any exists
554 let total_large = self.total_buffer_bytes_used();
555
556 // 2.5) Fast path: if there is no non-inline data, avoid buffer allocation & processing
557 if total_large == 0 {
558 // Views are inline-only or all null; just reuse original views and no data blocks
559 return unsafe {
560 GenericByteViewArray::new_unchecked(
561 self.views().clone(),
562 Arc::from([]), // empty data blocks
563 nulls,
564 )
565 };
566 }
567
568 let (views_buf, data_blocks) = if total_large <= max_buffer_size {
569 // fast path, the entire data fits in a single buffer
570 // 3) Allocate exactly capacity for all non-inline data
571 let mut data_buf = Vec::with_capacity(total_large);
572
573 // 4) Iterate over views and process each inline/non-inline view
574 let views_buf: Vec<u128> = (0..len)
575 .map(|i| unsafe { self.copy_view_to_buffer(i, 0, &mut data_buf) })
576 .collect();
577 let data_block = Buffer::from_vec(data_buf);
578 let data_blocks = vec![data_block];
579 (views_buf, data_blocks)
580 } else {
581 // slow path: the non-inline data does not fit in a single buffer
582 // (a buffer offset is a `u32`, so no buffer may exceed `i32::MAX`),
583 // so it must be split across several buffers.
584
585 // A contiguous run of views destined for one output buffer.
586 struct GcCopyGroup {
587 total_buffer_bytes: usize,
588 total_len: usize,
589 }
590
591 // First pass: partition the views into contiguous groups, each of
592 // which fits within one output buffer. `total_len` counts *all*
593 // views in the group, not just the non-inline ones — this is
594 // essential for correctness, as gc must preserve the row count.
595 // Inline views reference no buffer and are copied unchanged, but
596 // they still belong to a group and must be accounted for. Recording
597 // each group's exact byte size lets the second pass size every
598 // buffer precisely, with no over-reservation or trailing waste.
599 let mut groups: Vec<GcCopyGroup> = Vec::new();
600 // Bytes accumulated for the current group. A view length is a `u32`
601 // and a single value may be larger than `max_buffer_size`, so the
602 // running sum is kept in `u64` to stay within the addressable
603 // buffer-offset space without wrapping.
604 let mut current_length: u64 = 0;
605 let mut current_elements = 0;
606
607 for view in self.views() {
608 let view_len = *view as u32;
609 if view_len > MAX_INLINE_VIEW_LEN {
610 // Seal the current group before adding this view would push
611 // its buffer past `max_buffer_size`.
612 if current_length + view_len as u64 > max_buffer_size as u64 {
613 groups.push(GcCopyGroup {
614 total_buffer_bytes: current_length as usize,
615 total_len: current_elements,
616 });
617 current_length = 0;
618 current_elements = 0;
619 }
620 current_length += view_len as u64;
621 }
622 current_elements += 1;
623 }
624 if current_elements != 0 {
625 groups.push(GcCopyGroup {
626 total_buffer_bytes: current_length as usize,
627 total_len: current_elements,
628 });
629 }
630 debug_assert!(i32::try_from(groups.len()).is_ok());
631
632 // Second pass: copy each group into an exactly-sized buffer.
633 let mut views_buf = Vec::with_capacity(len);
634 let mut data_blocks = Vec::with_capacity(groups.len());
635 let mut current_view_idx = 0;
636
637 for (group_idx, group) in groups.iter().enumerate() {
638 let mut data_buf = Vec::with_capacity(group.total_buffer_bytes);
639
640 // Directly push views to avoid an intermediate Vec allocation.
641 let new_views =
642 (current_view_idx..current_view_idx + group.total_len).map(|view_idx| {
643 // SAFETY: `view_idx` came from iterating a valid range
644 // within `len`, and every view refers to valid data.
645 unsafe {
646 self.copy_view_to_buffer(view_idx, group_idx as i32, &mut data_buf)
647 }
648 });
649 views_buf.extend(new_views);
650
651 data_blocks.push(Buffer::from_vec(data_buf));
652 current_view_idx += group.total_len;
653 }
654 (views_buf, data_blocks)
655 };
656
657 // 5) Wrap up views buffer
658 let views_scalar = ScalarBuffer::from(views_buf);
659
660 // SAFETY: views_scalar, data_blocks, and nulls are correctly aligned and sized
661 unsafe { GenericByteViewArray::new_unchecked(views_scalar, data_blocks.into(), nulls) }
662 }
663
664 /// Copy the i‑th view into `data_buf` if it refers to an out‑of‑line buffer.
665 ///
666 /// # Safety
667 ///
668 /// - `i < self.len()`.
669 /// - Every element in `self.views()` must currently refer to a valid slice
670 /// inside one of `self.buffers`.
671 /// - `data_buf` must be ready to have additional bytes appended.
672 /// - After this call, the returned view will have its
673 /// `buffer_index` reset to `buffer_idx` and its `offset` updated so that it points
674 /// into the bytes just appended at the end of `data_buf`.
675 #[inline(always)]
676 unsafe fn copy_view_to_buffer(
677 &self,
678 i: usize,
679 buffer_idx: i32,
680 data_buf: &mut Vec<u8>,
681 ) -> u128 {
682 // SAFETY: `i < self.len()` ensures this is in‑bounds.
683 let raw_view = unsafe { *self.views().get_unchecked(i) };
684 let mut bv = ByteView::from(raw_view);
685
686 // Inline‑small views stay as‑is.
687 if bv.length <= MAX_INLINE_VIEW_LEN {
688 raw_view
689 } else {
690 // SAFETY: `bv.buffer_index` and `bv.offset..bv.offset+bv.length`
691 // must both lie within valid ranges for `self.buffers`.
692 let buffer = unsafe { self.buffers.get_unchecked(bv.buffer_index as usize) };
693 let start = bv.offset as usize;
694 let end = start + bv.length as usize;
695 let slice = unsafe { buffer.get_unchecked(start..end) };
696
697 // Copy out‑of‑line data into our single “0” buffer.
698 let new_offset = data_buf.len() as u32;
699 data_buf.extend_from_slice(slice);
700
701 bv.buffer_index = buffer_idx as u32;
702 bv.offset = new_offset;
703 bv.into()
704 }
705 }
706
707 /// Returns the total number of bytes of all non-null values in this array.
708 ///
709 /// Unlike [`Self::total_buffer_bytes_used`], this method includes inlined strings
710 /// (those with length ≤ [`MAX_INLINE_VIEW_LEN`]), making it suitable as a
711 /// capacity hint when pre-allocating output buffers.
712 ///
713 /// Null values are excluded from the sum.
714 ///
715 /// # Example
716 ///
717 /// ```rust
718 /// # use arrow_array::StringViewArray;
719 /// let array = StringViewArray::from_iter(vec![
720 /// Some("hello"), // 5 bytes, inlined
721 /// None, // excluded
722 /// Some("large payload over 12 bytes"), // 27 bytes, non-inlined
723 /// ]);
724 /// assert_eq!(array.total_bytes_len(), 5 + 27);
725 /// ```
726 pub fn total_bytes_len(&self) -> usize {
727 match self.nulls() {
728 None => self.views().iter().map(|v| (*v as u32) as usize).sum(),
729 Some(nulls) => self
730 .views()
731 .iter()
732 .zip(nulls.iter())
733 .map(|(v, is_valid)| if is_valid { (*v as u32) as usize } else { 0 })
734 .sum(),
735 }
736 }
737
738 /// Returns the total number of bytes used by all non inlined views in all
739 /// buffers.
740 ///
741 /// Note this does not account for views that point at the same underlying
742 /// data in buffers
743 ///
744 /// For example, if the array has three strings views:
745 /// * View with length = 9 (inlined)
746 /// * View with length = 32 (non inlined)
747 /// * View with length = 16 (non inlined)
748 ///
749 /// Then this method would report 48
750 pub fn total_buffer_bytes_used(&self) -> usize {
751 self.views()
752 .iter()
753 .map(|v| {
754 let len = *v as u32;
755 if len > MAX_INLINE_VIEW_LEN {
756 len as usize
757 } else {
758 0
759 }
760 })
761 .sum()
762 }
763
764 /// Compare two [`GenericByteViewArray`] at index `left_idx` and `right_idx`
765 ///
766 /// Comparing two ByteView types are non-trivial.
767 /// It takes a bit of patience to understand why we don't just compare two &[u8] directly.
768 ///
769 /// ByteView types give us the following two advantages, and we need to be careful not to lose them:
770 /// (1) For string/byte smaller than [`MAX_INLINE_VIEW_LEN`] bytes, the entire data is inlined in the view.
771 /// Meaning that reading one array element requires only one memory access
772 /// (two memory access required for StringArray, one for offset buffer, the other for value buffer).
773 ///
774 /// (2) For string/byte larger than [`MAX_INLINE_VIEW_LEN`] bytes, we can still be faster than (for certain operations) StringArray/ByteArray,
775 /// thanks to the inlined 4 bytes.
776 /// Consider equality check:
777 /// If the first four bytes of the two strings are different, we can return false immediately (with just one memory access).
778 ///
779 /// If we directly compare two &[u8], we materialize the entire string (i.e., make multiple memory accesses), which might be unnecessary.
780 /// - Most of the time (eq, ord), we only need to look at the first 4 bytes to know the answer,
781 /// e.g., if the inlined 4 bytes are different, we can directly return unequal without looking at the full string.
782 ///
783 /// # Order check flow
784 /// (1) if both string are smaller than [`MAX_INLINE_VIEW_LEN`] bytes, we can directly compare the data inlined to the view.
785 /// (2) if any of the string is larger than [`MAX_INLINE_VIEW_LEN`] bytes, we need to compare the full string.
786 /// (2.1) if the inlined 4 bytes are different, we can return the result immediately.
787 /// (2.2) o.w., we need to compare the full string.
788 ///
789 /// # Safety
790 /// The left/right_idx must within range of each array
791 pub unsafe fn compare_unchecked(
792 left: &GenericByteViewArray<T>,
793 left_idx: usize,
794 right: &GenericByteViewArray<T>,
795 right_idx: usize,
796 ) -> Ordering {
797 let l_view = unsafe { left.views().get_unchecked(left_idx) };
798 let l_byte_view = ByteView::from(*l_view);
799
800 let r_view = unsafe { right.views().get_unchecked(right_idx) };
801 let r_byte_view = ByteView::from(*r_view);
802
803 let l_len = l_byte_view.length;
804 let r_len = r_byte_view.length;
805
806 if l_len <= 12 && r_len <= 12 {
807 return Self::inline_key_fast(*l_view).cmp(&Self::inline_key_fast(*r_view));
808 }
809
810 // one of the string is larger than 12 bytes,
811 // we then try to compare the inlined data first
812
813 // Note: In theory, ByteView is only used for string which is larger than 12 bytes,
814 // but we can still use it to get the inlined prefix for shorter strings.
815 // The prefix is always the first 4 bytes of the view, for both short and long strings.
816 let l_inlined_be = l_byte_view.prefix.swap_bytes();
817 let r_inlined_be = r_byte_view.prefix.swap_bytes();
818 if l_inlined_be != r_inlined_be {
819 return l_inlined_be.cmp(&r_inlined_be);
820 }
821
822 // unfortunately, we need to compare the full data
823 let l_full_data: &[u8] = unsafe { left.value_unchecked(left_idx).as_ref() };
824 let r_full_data: &[u8] = unsafe { right.value_unchecked(right_idx).as_ref() };
825
826 l_full_data.cmp(r_full_data)
827 }
828
829 /// Builds a 128-bit composite key for an inline value:
830 ///
831 /// - High 96 bits: the inline data in big-endian byte order (for correct lexicographical sorting).
832 /// - Low 32 bits: the length in big-endian byte order, acting as a tiebreaker so shorter strings
833 /// (or those with fewer meaningful bytes) always numerically sort before longer ones.
834 ///
835 /// This function extracts the length and the 12-byte inline string data from the raw
836 /// little-endian `u128` representation, converts them to big-endian ordering, and packs them
837 /// into a single `u128` value suitable for fast, branchless comparisons.
838 ///
839 /// # Why include length?
840 ///
841 /// A pure 96-bit content comparison can’t distinguish between two values whose inline bytes
842 /// compare equal—either because one is a true prefix of the other or because zero-padding
843 /// hides extra bytes. By tucking the 32-bit length into the lower bits, a single `u128` compare
844 /// handles both content and length in one go.
845 ///
846 /// Example: comparing "bar" (3 bytes) vs "bar\0" (4 bytes)
847 ///
848 /// | String | Bytes 0–4 (length LE) | Bytes 4–16 (data + padding) |
849 /// |------------|-----------------------|---------------------------------|
850 /// | `"bar"` | `03 00 00 00` | `62 61 72` + 9 × `00` |
851 /// | `"bar\0"`| `04 00 00 00` | `62 61 72 00` + 8 × `00` |
852 ///
853 /// Both inline parts become `62 61 72 00…00`, so they tie on content. The length field
854 /// then differentiates:
855 ///
856 /// ```text
857 /// key("bar") = 0x0000000000000000000062617200000003
858 /// key("bar\0") = 0x0000000000000000000062617200000004
859 /// ⇒ key("bar") < key("bar\0")
860 /// ```
861 /// - `raw` is treated as a 128-bit integer with its bits laid out as follows:
862 /// - bits 0–31: length (little-endian)
863 /// - bits 32–127: data (little-endian)
864 ///
865 /// # Inlining and Endianness
866 ///
867 /// This function uses platform-independent bitwise operations to construct a 128-bit key:
868 /// - `raw.swap_bytes() << 32` effectively clears the length bits and shifts the 12-byte inline data
869 /// into the high 96 bits in Big-Endian order. This ensures the first byte of the string
870 /// is the most significant byte of the resulting `u128`.
871 /// - `raw as u32` extracts the length as a numeric integer, which is then placed in the low 32 bits.
872 #[inline(always)]
873 pub fn inline_key_fast(raw: u128) -> u128 {
874 (raw.swap_bytes() << 32) | (raw as u32 as u128)
875 }
876}
877
878impl<T: ByteViewType + ?Sized> Debug for GenericByteViewArray<T> {
879 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
880 write!(f, "{}ViewArray\n[\n", T::PREFIX)?;
881 print_long_array(self, f, &mut |index, f| {
882 std::fmt::Debug::fmt(&self.value(index), f)
883 })?;
884 write!(f, "]")
885 }
886}
887
888/// SAFETY: Correctly implements the contract of Arrow Arrays
889unsafe impl<T: ByteViewType + ?Sized> Array for GenericByteViewArray<T> {
890 fn as_any(&self) -> &dyn Any {
891 self
892 }
893
894 fn to_data(&self) -> ArrayData {
895 self.clone().into()
896 }
897
898 fn into_data(self) -> ArrayData {
899 self.into()
900 }
901
902 fn data_type(&self) -> &DataType {
903 &self.data_type
904 }
905
906 fn slice(&self, offset: usize, length: usize) -> ArrayRef {
907 Arc::new(self.slice(offset, length))
908 }
909
910 fn len(&self) -> usize {
911 self.views.len()
912 }
913
914 fn is_empty(&self) -> bool {
915 self.views.is_empty()
916 }
917
918 fn shrink_to_fit(&mut self) {
919 self.views.shrink_to_fit();
920
921 // The goal of `shrink_to_fit` is to minimize the space used by any of
922 // its allocations. The use of `Arc::get_mut` over `Arc::make_mut` is
923 // because if the reference count is greater than 1, `Arc::make_mut`
924 // will first clone its contents. So, any large allocations will first
925 // be cloned before being shrunk, leaving the pre-cloned allocations
926 // intact, before adding the extra (used) space of the new clones.
927 if let Some(buffers) = Arc::get_mut(&mut self.buffers) {
928 buffers.iter_mut().for_each(|b| b.shrink_to_fit());
929 }
930
931 // With the assumption that this is a best-effort function, no attempt
932 // is made to shrink `self.buffers`, which it can't because it's type
933 // does not expose a `shrink_to_fit` method.
934
935 if let Some(nulls) = &mut self.nulls {
936 nulls.shrink_to_fit();
937 }
938 }
939
940 fn offset(&self) -> usize {
941 0
942 }
943
944 fn nulls(&self) -> Option<&NullBuffer> {
945 self.nulls.as_ref()
946 }
947
948 fn logical_null_count(&self) -> usize {
949 // More efficient that the default implementation
950 self.null_count()
951 }
952
953 fn get_buffer_memory_size(&self) -> usize {
954 let mut sum = self.buffers.iter().map(|b| b.capacity()).sum::<usize>();
955 sum += self.views.inner().capacity();
956 if let Some(x) = &self.nulls {
957 sum += x.buffer().capacity()
958 }
959 sum
960 }
961
962 fn get_array_memory_size(&self) -> usize {
963 std::mem::size_of::<Self>() + self.get_buffer_memory_size()
964 }
965
966 #[cfg(feature = "pool")]
967 fn claim(&self, pool: &dyn arrow_buffer::MemoryPool) {
968 self.views.claim(pool);
969 for buffer in self.buffers.iter() {
970 buffer.claim(pool);
971 }
972 if let Some(nulls) = &self.nulls {
973 nulls.claim(pool);
974 }
975 }
976}
977
978impl<'a, T: ByteViewType + ?Sized> ArrayAccessor for &'a GenericByteViewArray<T> {
979 type Item = &'a T::Native;
980
981 fn value(&self, index: usize) -> Self::Item {
982 GenericByteViewArray::value(self, index)
983 }
984
985 unsafe fn value_unchecked(&self, index: usize) -> Self::Item {
986 unsafe { GenericByteViewArray::value_unchecked(self, index) }
987 }
988}
989
990impl<'a, T: ByteViewType + ?Sized> IntoIterator for &'a GenericByteViewArray<T> {
991 type Item = Option<&'a T::Native>;
992 type IntoIter = ArrayIter<Self>;
993
994 fn into_iter(self) -> Self::IntoIter {
995 ArrayIter::new(self)
996 }
997}
998
999impl<T: ByteViewType + ?Sized> From<ArrayData> for GenericByteViewArray<T> {
1000 fn from(data: ArrayData) -> Self {
1001 let (data_type, len, nulls, offset, buffers, _child_data) = data.into_parts();
1002 assert_eq!(
1003 data_type,
1004 T::DATA_TYPE,
1005 "Mismatched data type, expected {}, got {data_type}",
1006 T::DATA_TYPE
1007 );
1008 let mut buffers = buffers.into_iter();
1009 // first buffer is views, remaining are data buffers
1010 let views = ScalarBuffer::new(buffers.next().unwrap(), offset, len);
1011 Self {
1012 data_type,
1013 views,
1014 buffers: Arc::from_iter(buffers),
1015 nulls,
1016 phantom: Default::default(),
1017 }
1018 }
1019}
1020
1021/// Efficiently convert a [`GenericByteArray`] to a [`GenericByteViewArray`]
1022///
1023/// For example this method can convert a [`StringArray`] to a
1024/// [`StringViewArray`].
1025///
1026/// If the offsets are all less than u32::MAX, the new [`GenericByteViewArray`]
1027/// is built without copying the underlying string data (views are created
1028/// directly into the existing buffer)
1029///
1030/// [`StringArray`]: crate::StringArray
1031impl<FROM, V> From<&GenericByteArray<FROM>> for GenericByteViewArray<V>
1032where
1033 FROM: ByteArrayType,
1034 FROM::Offset: OffsetSizeTrait + ToPrimitive,
1035 V: ByteViewType<Native = FROM::Native>,
1036{
1037 fn from(byte_array: &GenericByteArray<FROM>) -> Self {
1038 let offsets = byte_array.offsets();
1039
1040 let can_reuse_buffer = offsets.last().as_usize() < u32::MAX as usize;
1041
1042 if can_reuse_buffer {
1043 // build views directly pointing to the existing buffer
1044 let len = byte_array.len();
1045 let mut views_builder = GenericByteViewBuilder::<V>::with_capacity(len);
1046 let str_values_buf = byte_array.values().clone();
1047 let block = views_builder.append_block(str_values_buf);
1048 for (i, w) in offsets.windows(2).enumerate() {
1049 let offset = w[0].as_usize();
1050 let end = w[1].as_usize();
1051 let length = end - offset;
1052
1053 if byte_array.is_null(i) {
1054 views_builder.append_null();
1055 } else {
1056 // Safety: the input was a valid array so it valid UTF8 (if string). And
1057 // all offsets were valid
1058 unsafe {
1059 views_builder.append_view_unchecked(block, offset as u32, length as u32)
1060 }
1061 }
1062 }
1063 assert_eq!(views_builder.len(), len);
1064 views_builder.finish()
1065 } else {
1066 // Otherwise, create a new buffer for large strings
1067 // TODO: the original buffer could still be used
1068 // by making multiple slices of u32::MAX length
1069 GenericByteViewArray::<V>::from_iter(byte_array.iter())
1070 }
1071 }
1072}
1073
1074impl<T: ByteViewType + ?Sized> From<GenericByteViewArray<T>> for ArrayData {
1075 fn from(array: GenericByteViewArray<T>) -> Self {
1076 let len = array.len();
1077
1078 let mut buffers = array.buffers.to_vec();
1079 buffers.insert(0, array.views.into_inner());
1080
1081 let builder = ArrayDataBuilder::new(T::DATA_TYPE)
1082 .len(len)
1083 .buffers(buffers)
1084 .nulls(array.nulls);
1085
1086 unsafe { builder.build_unchecked() }
1087 }
1088}
1089
1090impl<'a, Ptr, T> FromIterator<&'a Option<Ptr>> for GenericByteViewArray<T>
1091where
1092 Ptr: AsRef<T::Native> + 'a,
1093 T: ByteViewType + ?Sized,
1094{
1095 fn from_iter<I: IntoIterator<Item = &'a Option<Ptr>>>(iter: I) -> Self {
1096 iter.into_iter()
1097 .map(|o| o.as_ref().map(|p| p.as_ref()))
1098 .collect()
1099 }
1100}
1101
1102impl<Ptr, T: ByteViewType + ?Sized> FromIterator<Option<Ptr>> for GenericByteViewArray<T>
1103where
1104 Ptr: AsRef<T::Native>,
1105{
1106 fn from_iter<I: IntoIterator<Item = Option<Ptr>>>(iter: I) -> Self {
1107 let iter = iter.into_iter();
1108 let mut builder = GenericByteViewBuilder::<T>::with_capacity(iter.size_hint().0);
1109 builder.extend(iter);
1110 builder.finish()
1111 }
1112}
1113
1114/// A [`GenericByteViewArray`] of `[u8]`
1115///
1116/// See [`GenericByteViewArray`] for format and layout details.
1117///
1118/// # Example
1119/// ```
1120/// use arrow_array::BinaryViewArray;
1121/// let array = BinaryViewArray::from_iter_values(vec![b"hello" as &[u8], b"world", b"lulu", b"large payload over 12 bytes"]);
1122/// assert_eq!(array.value(0), b"hello");
1123/// assert_eq!(array.value(3), b"large payload over 12 bytes");
1124/// ```
1125pub type BinaryViewArray = GenericByteViewArray<BinaryViewType>;
1126
1127impl BinaryViewArray {
1128 /// Convert the [`BinaryViewArray`] to [`StringViewArray`]
1129 /// If items not utf8 data, validate will fail and error returned.
1130 pub fn to_string_view(self) -> Result<StringViewArray, ArrowError> {
1131 StringViewType::validate(self.views(), self.data_buffers())?;
1132 unsafe { Ok(self.to_string_view_unchecked()) }
1133 }
1134
1135 /// Convert the [`BinaryViewArray`] to [`StringViewArray`]
1136 /// # Safety
1137 /// Caller is responsible for ensuring that items in array are utf8 data.
1138 pub unsafe fn to_string_view_unchecked(self) -> StringViewArray {
1139 unsafe { StringViewArray::new_unchecked(self.views, self.buffers, self.nulls) }
1140 }
1141}
1142
1143impl From<Vec<&[u8]>> for BinaryViewArray {
1144 fn from(v: Vec<&[u8]>) -> Self {
1145 Self::from_iter_values(v)
1146 }
1147}
1148
1149impl From<Vec<Option<&[u8]>>> for BinaryViewArray {
1150 fn from(v: Vec<Option<&[u8]>>) -> Self {
1151 v.into_iter().collect()
1152 }
1153}
1154
1155/// A [`GenericByteViewArray`] that stores utf8 data
1156///
1157/// See [`GenericByteViewArray`] for format and layout details.
1158///
1159/// # Example
1160/// ```
1161/// use arrow_array::StringViewArray;
1162/// let array = StringViewArray::from_iter_values(vec!["hello", "world", "lulu", "large payload over 12 bytes"]);
1163/// assert_eq!(array.value(0), "hello");
1164/// assert_eq!(array.value(3), "large payload over 12 bytes");
1165/// ```
1166pub type StringViewArray = GenericByteViewArray<StringViewType>;
1167
1168impl StringViewArray {
1169 /// Convert the [`StringViewArray`] to [`BinaryViewArray`]
1170 pub fn to_binary_view(self) -> BinaryViewArray {
1171 unsafe { BinaryViewArray::new_unchecked(self.views, self.buffers, self.nulls) }
1172 }
1173
1174 /// Returns true if all data within this array is ASCII
1175 pub fn is_ascii(&self) -> bool {
1176 // Alternative (but incorrect): directly check the underlying buffers
1177 // (1) Our string view might be sparse, i.e., a subset of the buffers,
1178 // so even if the buffer is not ascii, we can still be ascii.
1179 // (2) It is quite difficult to know the range of each buffer (unlike StringArray)
1180 // This means that this operation is quite expensive, shall we cache the result?
1181 // i.e. track `is_ascii` in the builder.
1182 self.iter().all(|v| match v {
1183 Some(v) => v.is_ascii(),
1184 None => true,
1185 })
1186 }
1187}
1188
1189impl From<Vec<&str>> for StringViewArray {
1190 fn from(v: Vec<&str>) -> Self {
1191 Self::from_iter_values(v)
1192 }
1193}
1194
1195impl From<Vec<Option<&str>>> for StringViewArray {
1196 fn from(v: Vec<Option<&str>>) -> Self {
1197 v.into_iter().collect()
1198 }
1199}
1200
1201impl From<Vec<String>> for StringViewArray {
1202 fn from(v: Vec<String>) -> Self {
1203 Self::from_iter_values(v)
1204 }
1205}
1206
1207impl From<Vec<Option<String>>> for StringViewArray {
1208 fn from(v: Vec<Option<String>>) -> Self {
1209 v.into_iter().collect()
1210 }
1211}
1212
1213#[cfg(test)]
1214mod tests {
1215 use crate::builder::{BinaryViewBuilder, StringViewBuilder};
1216 use crate::types::BinaryViewType;
1217 use crate::{
1218 Array, BinaryViewArray, GenericBinaryArray, GenericByteViewArray, StringViewArray,
1219 };
1220 use arrow_buffer::{Buffer, NullBuffer, ScalarBuffer};
1221 use arrow_data::{ArrayDataBuilder, ByteView, MAX_INLINE_VIEW_LEN};
1222 use arrow_schema::DataType;
1223 use rand::prelude::StdRng;
1224 use rand::{RngExt, SeedableRng};
1225 use std::str::from_utf8;
1226
1227 const BLOCK_SIZE: u32 = 8;
1228
1229 #[test]
1230 fn try_new_string() {
1231 let array = StringViewArray::from_iter_values(vec![
1232 "hello",
1233 "world",
1234 "lulu",
1235 "large payload over 12 bytes",
1236 ]);
1237 assert_eq!(array.value(0), "hello");
1238 assert_eq!(array.value(3), "large payload over 12 bytes");
1239 }
1240
1241 #[test]
1242 fn try_new_binary() {
1243 let array = BinaryViewArray::from_iter_values(vec![
1244 b"hello".as_slice(),
1245 b"world".as_slice(),
1246 b"lulu".as_slice(),
1247 b"large payload over 12 bytes".as_slice(),
1248 ]);
1249 assert_eq!(array.value(0), b"hello");
1250 assert_eq!(array.value(3), b"large payload over 12 bytes");
1251 }
1252
1253 #[test]
1254 fn try_new_empty_string() {
1255 // test empty array
1256 let array = {
1257 let mut builder = StringViewBuilder::new();
1258 builder.finish()
1259 };
1260 assert!(array.is_empty());
1261 }
1262
1263 #[test]
1264 fn try_new_empty_binary() {
1265 // test empty array
1266 let array = {
1267 let mut builder = BinaryViewBuilder::new();
1268 builder.finish()
1269 };
1270 assert!(array.is_empty());
1271 }
1272
1273 #[test]
1274 fn test_append_string() {
1275 // test builder append
1276 let array = {
1277 let mut builder = StringViewBuilder::new();
1278 builder.append_value("hello");
1279 builder.append_null();
1280 builder.append_option(Some("large payload over 12 bytes"));
1281 builder.finish()
1282 };
1283 assert_eq!(array.value(0), "hello");
1284 assert!(array.is_null(1));
1285 assert_eq!(array.value(2), "large payload over 12 bytes");
1286 }
1287
1288 #[test]
1289 fn test_append_binary() {
1290 // test builder append
1291 let array = {
1292 let mut builder = BinaryViewBuilder::new();
1293 builder.append_value(b"hello");
1294 builder.append_null();
1295 builder.append_option(Some(b"large payload over 12 bytes"));
1296 builder.finish()
1297 };
1298 assert_eq!(array.value(0), b"hello");
1299 assert!(array.is_null(1));
1300 assert_eq!(array.value(2), b"large payload over 12 bytes");
1301 }
1302
1303 #[test]
1304 fn test_in_progress_recreation() {
1305 let array = {
1306 // make a builder with small block size.
1307 let mut builder = StringViewBuilder::new().with_fixed_block_size(14);
1308 builder.append_value("large payload over 12 bytes");
1309 builder.append_option(Some("another large payload over 12 bytes that double than the first one, so that we can trigger the in_progress in builder re-created"));
1310 builder.finish()
1311 };
1312 assert_eq!(array.value(0), "large payload over 12 bytes");
1313 assert_eq!(
1314 array.value(1),
1315 "another large payload over 12 bytes that double than the first one, so that we can trigger the in_progress in builder re-created"
1316 );
1317 assert_eq!(2, array.buffers.len());
1318 }
1319
1320 #[test]
1321 #[should_panic(expected = "Invalid buffer index at 0: got index 3 but only has 1 buffers")]
1322 fn new_with_invalid_view_data() {
1323 let v = "large payload over 12 bytes";
1324 let view = ByteView::new(13, &v.as_bytes()[0..4])
1325 .with_buffer_index(3)
1326 .with_offset(1);
1327 let views = ScalarBuffer::from(vec![view.into()]);
1328 let buffers = vec![Buffer::from_slice_ref(v)];
1329 StringViewArray::new(views, buffers, None);
1330 }
1331
1332 #[test]
1333 #[should_panic(
1334 expected = "Encountered non-UTF-8 data at index 0: invalid utf-8 sequence of 1 bytes from index 0"
1335 )]
1336 fn new_with_invalid_utf8_data() {
1337 let v: Vec<u8> = vec![
1338 // invalid UTF8
1339 0xf0, 0x80, 0x80, 0x80, // more bytes to make it larger than 12
1340 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1341 ];
1342 let view = ByteView::new(v.len() as u32, &v[0..4]);
1343 let views = ScalarBuffer::from(vec![view.into()]);
1344 let buffers = vec![Buffer::from_slice_ref(v)];
1345 StringViewArray::new(views, buffers, None);
1346 }
1347
1348 #[test]
1349 #[should_panic(expected = "View at index 0 contained non-zero padding for string of length 1")]
1350 fn new_with_invalid_zero_padding() {
1351 let mut data = [0; 12];
1352 data[0] = b'H';
1353 data[11] = 1; // no zero padding
1354
1355 let mut view_buffer = [0; 16];
1356 view_buffer[0..4].copy_from_slice(&1u32.to_le_bytes());
1357 view_buffer[4..].copy_from_slice(&data);
1358
1359 let view = ByteView::from(u128::from_le_bytes(view_buffer));
1360 let views = ScalarBuffer::from(vec![view.into()]);
1361 let buffers = vec![];
1362 StringViewArray::new(views, buffers, None);
1363 }
1364
1365 #[test]
1366 #[should_panic(expected = "Mismatch between embedded prefix and data")]
1367 fn test_mismatch_between_embedded_prefix_and_data() {
1368 let input_str_1 = "Hello, Rustaceans!";
1369 let input_str_2 = "Hallo, Rustaceans!";
1370 let length = input_str_1.len() as u32;
1371 assert!(input_str_1.len() > 12);
1372
1373 let mut view_buffer = [0; 16];
1374 view_buffer[0..4].copy_from_slice(&length.to_le_bytes());
1375 view_buffer[4..8].copy_from_slice(&input_str_1.as_bytes()[0..4]);
1376 view_buffer[8..12].copy_from_slice(&0u32.to_le_bytes());
1377 view_buffer[12..].copy_from_slice(&0u32.to_le_bytes());
1378 let view = ByteView::from(u128::from_le_bytes(view_buffer));
1379 let views = ScalarBuffer::from(vec![view.into()]);
1380 let buffers = vec![Buffer::from_slice_ref(input_str_2.as_bytes())];
1381
1382 StringViewArray::new(views, buffers, None);
1383 }
1384
1385 #[test]
1386 fn test_gc() {
1387 let test_data = [
1388 Some("longer than 12 bytes"),
1389 Some("short"),
1390 Some("t"),
1391 Some("longer than 12 bytes"),
1392 None,
1393 Some("short"),
1394 ];
1395
1396 let array = {
1397 let mut builder = StringViewBuilder::new().with_fixed_block_size(8); // create multiple buffers
1398 test_data.into_iter().for_each(|v| builder.append_option(v));
1399 builder.finish()
1400 };
1401 assert!(array.buffers.len() > 1);
1402
1403 fn check_gc(to_test: &StringViewArray) {
1404 let gc = to_test.gc();
1405 assert_ne!(to_test.data_buffers().len(), gc.data_buffers().len());
1406
1407 to_test.iter().zip(gc.iter()).for_each(|(a, b)| {
1408 assert_eq!(a, b);
1409 });
1410 assert_eq!(to_test.len(), gc.len());
1411 }
1412
1413 check_gc(&array);
1414 check_gc(&array.slice(1, 3));
1415 check_gc(&array.slice(2, 1));
1416 check_gc(&array.slice(2, 2));
1417 check_gc(&array.slice(3, 1));
1418 }
1419
1420 /// 1) Empty array: no elements, expect gc to return empty with no data buffers
1421 #[test]
1422 fn test_gc_empty_array() {
1423 let array = StringViewBuilder::new()
1424 .with_fixed_block_size(BLOCK_SIZE)
1425 .finish();
1426 let gced = array.gc();
1427 // length and null count remain zero
1428 assert_eq!(gced.len(), 0);
1429 assert_eq!(gced.null_count(), 0);
1430 // no underlying data buffers should be allocated
1431 assert!(
1432 gced.data_buffers().is_empty(),
1433 "Expected no data buffers for empty array"
1434 );
1435 }
1436
1437 /// 2) All inline values (<= INLINE_LEN): capacity-only data buffer, same values
1438 #[test]
1439 fn test_gc_all_inline() {
1440 let mut builder = StringViewBuilder::new().with_fixed_block_size(BLOCK_SIZE);
1441 // append many short strings, each exactly INLINE_LEN long
1442 for _ in 0..100 {
1443 let s = "A".repeat(MAX_INLINE_VIEW_LEN as usize);
1444 builder.append_option(Some(&s));
1445 }
1446 let array = builder.finish();
1447 let gced = array.gc();
1448 // Since all views fit inline, data buffer is empty
1449 assert_eq!(
1450 gced.data_buffers().len(),
1451 0,
1452 "Should have no data buffers for inline values"
1453 );
1454 assert_eq!(gced.len(), 100);
1455 // verify element-wise equality
1456 array.iter().zip(gced.iter()).for_each(|(orig, got)| {
1457 assert_eq!(orig, got, "Inline value mismatch after gc");
1458 });
1459 }
1460
1461 /// 3) All large values (> INLINE_LEN): each must be copied into the new data buffer
1462 #[test]
1463 fn test_gc_all_large() {
1464 let mut builder = StringViewBuilder::new().with_fixed_block_size(BLOCK_SIZE);
1465 let large_str = "X".repeat(MAX_INLINE_VIEW_LEN as usize + 5);
1466 // append multiple large strings
1467 for _ in 0..50 {
1468 builder.append_option(Some(&large_str));
1469 }
1470 let array = builder.finish();
1471 let gced = array.gc();
1472 // New data buffers should be populated (one or more blocks)
1473 assert!(
1474 !gced.data_buffers().is_empty(),
1475 "Expected data buffers for large values"
1476 );
1477 assert_eq!(gced.len(), 50);
1478 // verify that every large string emerges unchanged
1479 array.iter().zip(gced.iter()).for_each(|(orig, got)| {
1480 assert_eq!(orig, got, "Large view mismatch after gc");
1481 });
1482 }
1483
1484 /// 4) All null elements: ensure null bitmap handling path is correct
1485 #[test]
1486 fn test_gc_all_nulls() {
1487 let mut builder = StringViewBuilder::new().with_fixed_block_size(BLOCK_SIZE);
1488 for _ in 0..20 {
1489 builder.append_null();
1490 }
1491 let array = builder.finish();
1492 let gced = array.gc();
1493 // length and null count match
1494 assert_eq!(gced.len(), 20);
1495 assert_eq!(gced.null_count(), 20);
1496 // data buffers remain empty for null-only array
1497 assert!(
1498 gced.data_buffers().is_empty(),
1499 "No data should be stored for nulls"
1500 );
1501 }
1502
1503 /// 5) Random mix of inline, large, and null values with slicing tests
1504 #[test]
1505 fn test_gc_random_mixed_and_slices() {
1506 let mut rng = StdRng::seed_from_u64(42);
1507 let mut builder = StringViewBuilder::new().with_fixed_block_size(BLOCK_SIZE);
1508 // Keep a Vec of original Option<String> for later comparison
1509 let mut original: Vec<Option<String>> = Vec::new();
1510
1511 for _ in 0..200 {
1512 if rng.random_bool(0.1) {
1513 // 10% nulls
1514 builder.append_null();
1515 original.push(None);
1516 } else {
1517 // random length between 0 and twice the inline limit
1518 let len = rng.random_range(0..(MAX_INLINE_VIEW_LEN * 2));
1519 let s = "A".repeat(len as usize);
1520 builder.append_option(Some(&s));
1521 original.push(Some(s));
1522 }
1523 }
1524
1525 let array = builder.finish();
1526 // Test multiple slice ranges to ensure offset logic is correct
1527 for (offset, slice_len) in &[(0, 50), (10, 100), (150, 30)] {
1528 let sliced = array.slice(*offset, *slice_len);
1529 let gced = sliced.gc();
1530 // Build expected slice of Option<&str>
1531 let expected: Vec<Option<&str>> = original[*offset..(*offset + *slice_len)]
1532 .iter()
1533 .map(|opt| opt.as_deref())
1534 .collect();
1535
1536 assert_eq!(gced.len(), *slice_len, "Slice length mismatch");
1537 // Compare element-wise
1538 gced.iter().zip(expected.iter()).for_each(|(got, expect)| {
1539 assert_eq!(got, *expect, "Value mismatch in mixed slice after gc");
1540 });
1541 }
1542 }
1543
1544 #[test]
1545 #[cfg_attr(miri, ignore)] // Takes too long
1546 fn test_gc_huge_array() {
1547 // Construct multiple 128 MiB BinaryView entries so total > 4 GiB
1548 let block_len: usize = 128 * 1024 * 1024; // 128 MiB per view
1549 let num_views: usize = 36;
1550
1551 // Create a single 128 MiB data block with a simple byte pattern
1552 let buffer = Buffer::from_vec(vec![0xAB; block_len]);
1553 let buffer2 = Buffer::from_vec(vec![0xFF; block_len]);
1554
1555 // Append this block and then add many views pointing to it
1556 let mut builder = BinaryViewBuilder::new();
1557 let block_id = builder.append_block(buffer);
1558 for _ in 0..num_views / 2 {
1559 builder
1560 .try_append_view(block_id, 0, block_len as u32)
1561 .expect("append view into 128MiB block");
1562 }
1563 let block_id2 = builder.append_block(buffer2);
1564 for _ in 0..num_views / 2 {
1565 builder
1566 .try_append_view(block_id2, 0, block_len as u32)
1567 .expect("append view into 128MiB block");
1568 }
1569
1570 let array = builder.finish();
1571 let total = array.total_buffer_bytes_used();
1572 assert!(
1573 total > u32::MAX as usize,
1574 "Expected total non-inline bytes to exceed 4 GiB, got {total}"
1575 );
1576
1577 // Run gc and verify correctness
1578 let gced = array.gc();
1579 assert_eq!(gced.len(), num_views, "Length mismatch after gc");
1580 assert_eq!(gced.null_count(), 0, "Null count mismatch after gc");
1581 assert_ne!(
1582 gced.data_buffers().len(),
1583 1,
1584 "gc with huge buffer should not consolidate data into a single buffer"
1585 );
1586
1587 // Element-wise equality check across the entire array
1588 array.iter().zip(gced.iter()).for_each(|(orig, got)| {
1589 assert_eq!(orig, got, "Value mismatch after gc on huge array");
1590 });
1591 }
1592
1593 #[test]
1594 fn test_gc_slow_path_preserves_inline_views() {
1595 // Regression test for a bug in the multi-buffer "slow path" of `gc()`,
1596 // which `gc` only reaches once a view column references more than
1597 // `i32::MAX` (~2.1 GiB) of non-inline data. That path grouped and copied
1598 // only the *non-inline* views and dropped every inline (short) view,
1599 // returning an array shorter than its input. GC is a pure size
1600 // optimisation and must never change the row count.
1601 //
1602 // Rather than allocate gigabytes to hit the real threshold, drive the
1603 // same code via `gc_with_max_buffer_size` with a tiny cap so a handful
1604 // of bytes forces the buffer split. Inline, large, and null views are
1605 // interspersed so the split lands between and around inline entries.
1606 let long = "this is definitely longer than twelve bytes"; // non-inline
1607 let test_data = [
1608 Some("short"), // inline
1609 Some(long), // large
1610 Some("s"), // inline
1611 Some(long), // large
1612 None, // null
1613 Some(long), // large
1614 Some("also short"), // inline
1615 Some(long), // large
1616 Some("tail"), // inline
1617 ];
1618 let array: StringViewArray = test_data.into_iter().collect();
1619
1620 // Cap each output buffer at ~1.5 large values so the copy is forced to
1621 // split across several buffers, exercising the slow path with no
1622 // multi-GiB allocation.
1623 let max_buffer_size = long.len() + long.len() / 2;
1624 let gced = array.gc_with_max_buffer_size(max_buffer_size);
1625
1626 // The core invariant: gc preserves the row count.
1627 assert_eq!(gced.len(), array.len(), "gc changed the row count");
1628 // The split must actually have happened, otherwise we'd be exercising
1629 // the fast path and could not catch the inline-dropping regression.
1630 assert!(
1631 gced.data_buffers().len() > 1,
1632 "expected output split across multiple buffers, got {}",
1633 gced.data_buffers().len()
1634 );
1635 // No output buffer may exceed the cap.
1636 for buf in gced.data_buffers().iter() {
1637 assert!(buf.len() <= max_buffer_size, "buffer exceeded max size");
1638 }
1639 // Every value (inline, large, and null) is unchanged and in order.
1640 array
1641 .iter()
1642 .zip(gced.iter())
1643 .enumerate()
1644 .for_each(|(i, (a, b))| {
1645 assert_eq!(a, b, "value mismatch at index {i} after gc");
1646 });
1647 // The result round-trips through full validation.
1648 gced.to_data().validate_full().unwrap();
1649 }
1650
1651 #[test]
1652 fn test_gc_slow_path_value_larger_than_buffer() {
1653 // A single non-inline value can be larger than one output buffer (a view
1654 // length is a `u32`, so a value may exceed `max_buffer_size`). Such a
1655 // value cannot be split, so it occupies a buffer of its own; gc must
1656 // still preserve every row and produce a valid array.
1657 //
1658 // The real threshold needs a multi-GiB value, so drive the same logic
1659 // with a `max_buffer_size` smaller than the value length.
1660 let big = "x".repeat(64); // non-inline, larger than the cap below
1661 let test_data = [
1662 Some("short"), // inline
1663 Some(big.as_str()), // larger than max_buffer_size
1664 None, // null
1665 Some("tail"), // inline
1666 Some(big.as_str()), // larger than max_buffer_size
1667 ];
1668 let array: StringViewArray = test_data.into_iter().collect();
1669
1670 let max_buffer_size = 16; // smaller than a single `big` value
1671 let gced = array.gc_with_max_buffer_size(max_buffer_size);
1672
1673 // Row count is preserved.
1674 assert_eq!(gced.len(), array.len(), "gc changed the row count");
1675 // Each oversized value lands in its own buffer, so the copy splits.
1676 assert!(
1677 gced.data_buffers().len() > 1,
1678 "expected output split across multiple buffers, got {}",
1679 gced.data_buffers().len()
1680 );
1681 // Every value is unchanged and in order.
1682 array
1683 .iter()
1684 .zip(gced.iter())
1685 .enumerate()
1686 .for_each(|(i, (a, b))| {
1687 assert_eq!(a, b, "value mismatch at index {i} after gc");
1688 });
1689 // The result round-trips through full validation.
1690 gced.to_data().validate_full().unwrap();
1691 }
1692
1693 #[test]
1694 fn test_eq() {
1695 let test_data = [
1696 Some("longer than 12 bytes"),
1697 None,
1698 Some("short"),
1699 Some("again, this is longer than 12 bytes"),
1700 ];
1701
1702 let array1 = {
1703 let mut builder = StringViewBuilder::new().with_fixed_block_size(8);
1704 test_data.into_iter().for_each(|v| builder.append_option(v));
1705 builder.finish()
1706 };
1707 let array2 = {
1708 // create a new array with the same data but different layout
1709 let mut builder = StringViewBuilder::new().with_fixed_block_size(100);
1710 test_data.into_iter().for_each(|v| builder.append_option(v));
1711 builder.finish()
1712 };
1713 assert_eq!(array1, array1.clone());
1714 assert_eq!(array2, array2.clone());
1715 assert_eq!(array1, array2);
1716 }
1717
1718 /// Integration tests for `inline_key_fast` covering:
1719 ///
1720 /// 1. Monotonic ordering across increasing lengths and lexical variations.
1721 /// 2. Cross-check against `GenericBinaryArray` comparison to ensure semantic equivalence.
1722 ///
1723 /// This also includes a specific test for the “bar” vs. “bar\0” case, demonstrating why
1724 /// the length field is required even when all inline bytes fit in 12 bytes.
1725 ///
1726 /// The test includes strings that verify correct byte order (prevent reversal bugs),
1727 /// and length-based tie-breaking in the composite key.
1728 ///
1729 /// The test confirms that `inline_key_fast` produces keys which sort consistently
1730 /// with the expected lexicographical order of the raw byte arrays.
1731 #[test]
1732 fn test_inline_key_fast_various_lengths_and_lexical() {
1733 /// Helper to create a raw u128 value representing an inline ByteView:
1734 /// - `length`: number of meaningful bytes (must be ≤ 12)
1735 /// - `data`: the actual inline data bytes
1736 ///
1737 /// The first 4 bytes encode length in little-endian,
1738 /// the following 12 bytes contain the inline string data (unpadded).
1739 fn make_raw_inline(length: u32, data: &[u8]) -> u128 {
1740 assert!(length as usize <= 12, "Inline length must be ≤ 12");
1741 assert!(
1742 data.len() == length as usize,
1743 "Data length must match `length`"
1744 );
1745
1746 let mut raw_bytes = [0u8; 16];
1747 raw_bytes[0..4].copy_from_slice(&length.to_le_bytes()); // length stored little-endian
1748 raw_bytes[4..(4 + data.len())].copy_from_slice(data); // inline data
1749 u128::from_le_bytes(raw_bytes)
1750 }
1751
1752 // Test inputs: various lengths and lexical orders,
1753 // plus special cases for byte order and length tie-breaking
1754 let test_inputs: Vec<&[u8]> = vec![
1755 b"a",
1756 b"aa",
1757 b"aaa",
1758 b"aab",
1759 b"abcd",
1760 b"abcde",
1761 b"abcdef",
1762 b"abcdefg",
1763 b"abcdefgh",
1764 b"abcdefghi",
1765 b"abcdefghij",
1766 b"abcdefghijk",
1767 b"abcdefghijkl",
1768 // Tests for byte-order reversal bug:
1769 // Without the fix, "backend one" would compare as "eno dnekcab",
1770 // causing incorrect sort order relative to "backend two".
1771 b"backend one",
1772 b"backend two",
1773 // Tests length-tiebreaker logic:
1774 // "bar" (3 bytes) and "bar\0" (4 bytes) have identical inline data,
1775 // so only the length differentiates their ordering.
1776 b"bar",
1777 b"bar\0",
1778 // Additional lexical and length tie-breaking cases with same prefix, in correct lex order:
1779 b"than12Byt",
1780 b"than12Bytes",
1781 b"than12Bytes\0",
1782 b"than12Bytesx",
1783 b"than12Bytex",
1784 b"than12Bytez",
1785 // Additional lexical tests
1786 b"xyy",
1787 b"xyz",
1788 b"xza",
1789 ];
1790
1791 // Create a GenericBinaryArray for cross-comparison of lex order
1792 let array: GenericBinaryArray<i32> =
1793 GenericBinaryArray::from(test_inputs.iter().map(|s| Some(*s)).collect::<Vec<_>>());
1794
1795 for i in 0..array.len() - 1 {
1796 let v1 = array.value(i);
1797 let v2 = array.value(i + 1);
1798
1799 // Assert the array's natural lexical ordering is correct
1800 assert!(v1 < v2, "Array compare failed: {v1:?} !< {v2:?}");
1801
1802 // Assert the keys produced by inline_key_fast reflect the same ordering
1803 let key1 = GenericByteViewArray::<BinaryViewType>::inline_key_fast(make_raw_inline(
1804 v1.len() as u32,
1805 v1,
1806 ));
1807 let key2 = GenericByteViewArray::<BinaryViewType>::inline_key_fast(make_raw_inline(
1808 v2.len() as u32,
1809 v2,
1810 ));
1811
1812 assert!(
1813 key1 < key2,
1814 "Key compare failed: key({v1:?})=0x{key1:032x} !< key({v2:?})=0x{key2:032x}",
1815 );
1816 }
1817 }
1818
1819 #[test]
1820 fn empty_array_should_return_empty_lengths_iterator() {
1821 let empty = GenericByteViewArray::<BinaryViewType>::from(Vec::<&[u8]>::new());
1822
1823 let mut lengths_iter = empty.lengths();
1824 assert_eq!(lengths_iter.len(), 0);
1825 assert_eq!(lengths_iter.next(), None);
1826 }
1827
1828 #[test]
1829 fn array_lengths_should_return_correct_length_for_both_inlined_and_non_inlined() {
1830 let cases = GenericByteViewArray::<BinaryViewType>::from(vec![
1831 // Not inlined as longer than 12 bytes
1832 b"Supercalifragilisticexpialidocious" as &[u8],
1833 // Inlined as shorter than 12 bytes
1834 b"Hello",
1835 // Empty value
1836 b"",
1837 // Exactly 12 bytes
1838 b"abcdefghijkl",
1839 ]);
1840
1841 let mut lengths_iter = cases.lengths();
1842
1843 assert_eq!(lengths_iter.len(), cases.len());
1844
1845 let cases_iter = cases.iter();
1846
1847 for case in cases_iter {
1848 let case_value = case.unwrap();
1849 let length = lengths_iter.next().expect("Should have a length");
1850
1851 assert_eq!(case_value.len(), length as usize);
1852 }
1853
1854 assert_eq!(lengths_iter.next(), None, "Should not have more lengths");
1855 }
1856
1857 #[test]
1858 fn array_lengths_should_return_the_underlying_length_for_null_values() {
1859 let cases = GenericByteViewArray::<BinaryViewType>::from(vec![
1860 // Not inlined as longer than 12 bytes
1861 b"Supercalifragilisticexpialidocious" as &[u8],
1862 // Inlined as shorter than 12 bytes
1863 b"Hello",
1864 // Empty value
1865 b"",
1866 // Exactly 12 bytes
1867 b"abcdefghijkl",
1868 ]);
1869
1870 let (views, buffer, _) = cases.clone().into_parts();
1871
1872 // Keeping the values but just adding nulls on top
1873 let cases_with_all_nulls = GenericByteViewArray::<BinaryViewType>::new(
1874 views,
1875 buffer,
1876 Some(NullBuffer::new_null(cases.len())),
1877 );
1878
1879 let lengths_iter = cases.lengths();
1880 let mut all_nulls_lengths_iter = cases_with_all_nulls.lengths();
1881
1882 assert_eq!(lengths_iter.len(), all_nulls_lengths_iter.len());
1883
1884 for expected_length in lengths_iter {
1885 let actual_length = all_nulls_lengths_iter.next().expect("Should have a length");
1886
1887 assert_eq!(expected_length, actual_length);
1888 }
1889
1890 assert_eq!(
1891 all_nulls_lengths_iter.next(),
1892 None,
1893 "Should not have more lengths"
1894 );
1895 }
1896
1897 #[test]
1898 fn array_lengths_on_sliced_should_only_return_lengths_for_sliced_data() {
1899 let array = GenericByteViewArray::<BinaryViewType>::from(vec![
1900 b"aaaaaaaaaaaaaaaaaaaaaaaaaaa" as &[u8],
1901 b"Hello",
1902 b"something great",
1903 b"is",
1904 b"coming soon!",
1905 b"when you find what it is",
1906 b"let me know",
1907 b"cause",
1908 b"I",
1909 b"have no idea",
1910 b"what it",
1911 b"is",
1912 ]);
1913
1914 let sliced_array = array.slice(2, array.len() - 3);
1915
1916 let mut lengths_iter = sliced_array.lengths();
1917
1918 assert_eq!(lengths_iter.len(), sliced_array.len());
1919
1920 let values_iter = sliced_array.iter();
1921
1922 for value in values_iter {
1923 let value = value.unwrap();
1924 let length = lengths_iter.next().expect("Should have a length");
1925
1926 assert_eq!(value.len(), length as usize);
1927 }
1928
1929 assert_eq!(lengths_iter.next(), None, "Should not have more lengths");
1930 }
1931
1932 #[should_panic(expected = "Mismatched data type, expected Utf8View, got BinaryView")]
1933 #[test]
1934 fn invalid_casting_from_array_data() {
1935 // Should not be able to cast to StringViewArray due to invalid UTF-8
1936 let array_data = binary_view_array_with_invalid_utf8_data().into_data();
1937 let _ = StringViewArray::from(array_data);
1938 }
1939
1940 #[should_panic(expected = "invalid utf-8 sequence")]
1941 #[test]
1942 fn invalid_array_data() {
1943 let (views, buffers, nulls) = binary_view_array_with_invalid_utf8_data().into_parts();
1944
1945 // manually try and add invalid array data with Utf8View data type
1946 let mut builder = ArrayDataBuilder::new(DataType::Utf8View)
1947 .add_buffer(views.into_inner())
1948 .len(3);
1949 for buffer in buffers.iter() {
1950 builder = builder.add_buffer(buffer.clone())
1951 }
1952 builder = builder.nulls(nulls);
1953
1954 let data = builder.build().unwrap(); // should fail validation
1955 let _arr = StringViewArray::from(data);
1956 }
1957
1958 /// Returns a BinaryViewArray with one invalid UTF-8 value
1959 fn binary_view_array_with_invalid_utf8_data() -> BinaryViewArray {
1960 let array = GenericByteViewArray::<BinaryViewType>::from(vec![
1961 b"aaaaaaaaaaaaaaaaaaaaaaaaaaa" as &[u8],
1962 &[
1963 0xf0, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1964 0x00, 0x00,
1965 ],
1966 b"good",
1967 ]);
1968 assert!(from_utf8(array.value(0)).is_ok());
1969 assert!(from_utf8(array.value(1)).is_err()); // value 1 is invalid utf8
1970 assert!(from_utf8(array.value(2)).is_ok());
1971 array
1972 }
1973
1974 #[test]
1975 fn test_total_bytes_len() {
1976 // inlined: "hello"=5, "world"=5, "lulu"=4 → 14
1977 // non-inlined: "large payload over 12 bytes"=27
1978 // null: should not count
1979 let mut builder = StringViewBuilder::new();
1980 builder.append_value("hello");
1981 builder.append_value("world");
1982 builder.append_value("lulu");
1983 builder.append_null();
1984 builder.append_value("large payload over 12 bytes");
1985 let array = builder.finish();
1986 assert_eq!(array.total_bytes_len(), 5 + 5 + 4 + 27);
1987 }
1988
1989 #[test]
1990 fn test_total_bytes_len_empty() {
1991 let array = StringViewArray::from_iter::<Vec<Option<&str>>>(vec![]);
1992 assert_eq!(array.total_bytes_len(), 0);
1993 }
1994
1995 #[test]
1996 fn test_total_bytes_len_all_nulls() {
1997 let array = StringViewArray::new_null(5);
1998 assert_eq!(array.total_bytes_len(), 0);
1999 }
2000
2001 #[test]
2002 fn test_total_bytes_len_binary_view() {
2003 let array = BinaryViewArray::from_iter(vec![
2004 Some(b"hi".as_ref()),
2005 None,
2006 Some(b"large payload over 12 bytes".as_ref()),
2007 ]);
2008 assert_eq!(array.total_bytes_len(), 2 + 27);
2009 }
2010}