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, |array, index, f| {
882 std::fmt::Debug::fmt(&array.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 = match offsets.last() {
1041 Some(offset) => offset.as_usize() < u32::MAX as usize,
1042 None => true,
1043 };
1044
1045 if can_reuse_buffer {
1046 // build views directly pointing to the existing buffer
1047 let len = byte_array.len();
1048 let mut views_builder = GenericByteViewBuilder::<V>::with_capacity(len);
1049 let str_values_buf = byte_array.values().clone();
1050 let block = views_builder.append_block(str_values_buf);
1051 for (i, w) in offsets.windows(2).enumerate() {
1052 let offset = w[0].as_usize();
1053 let end = w[1].as_usize();
1054 let length = end - offset;
1055
1056 if byte_array.is_null(i) {
1057 views_builder.append_null();
1058 } else {
1059 // Safety: the input was a valid array so it valid UTF8 (if string). And
1060 // all offsets were valid
1061 unsafe {
1062 views_builder.append_view_unchecked(block, offset as u32, length as u32)
1063 }
1064 }
1065 }
1066 assert_eq!(views_builder.len(), len);
1067 views_builder.finish()
1068 } else {
1069 // Otherwise, create a new buffer for large strings
1070 // TODO: the original buffer could still be used
1071 // by making multiple slices of u32::MAX length
1072 GenericByteViewArray::<V>::from_iter(byte_array.iter())
1073 }
1074 }
1075}
1076
1077impl<T: ByteViewType + ?Sized> From<GenericByteViewArray<T>> for ArrayData {
1078 fn from(array: GenericByteViewArray<T>) -> Self {
1079 let len = array.len();
1080
1081 let mut buffers = array.buffers.to_vec();
1082 buffers.insert(0, array.views.into_inner());
1083
1084 let builder = ArrayDataBuilder::new(T::DATA_TYPE)
1085 .len(len)
1086 .buffers(buffers)
1087 .nulls(array.nulls);
1088
1089 unsafe { builder.build_unchecked() }
1090 }
1091}
1092
1093impl<'a, Ptr, T> FromIterator<&'a Option<Ptr>> for GenericByteViewArray<T>
1094where
1095 Ptr: AsRef<T::Native> + 'a,
1096 T: ByteViewType + ?Sized,
1097{
1098 fn from_iter<I: IntoIterator<Item = &'a Option<Ptr>>>(iter: I) -> Self {
1099 iter.into_iter()
1100 .map(|o| o.as_ref().map(|p| p.as_ref()))
1101 .collect()
1102 }
1103}
1104
1105impl<Ptr, T: ByteViewType + ?Sized> FromIterator<Option<Ptr>> for GenericByteViewArray<T>
1106where
1107 Ptr: AsRef<T::Native>,
1108{
1109 fn from_iter<I: IntoIterator<Item = Option<Ptr>>>(iter: I) -> Self {
1110 let iter = iter.into_iter();
1111 let mut builder = GenericByteViewBuilder::<T>::with_capacity(iter.size_hint().0);
1112 builder.extend(iter);
1113 builder.finish()
1114 }
1115}
1116
1117/// A [`GenericByteViewArray`] of `[u8]`
1118///
1119/// See [`GenericByteViewArray`] for format and layout details.
1120///
1121/// # Example
1122/// ```
1123/// use arrow_array::BinaryViewArray;
1124/// let array = BinaryViewArray::from_iter_values(vec![b"hello" as &[u8], b"world", b"lulu", b"large payload over 12 bytes"]);
1125/// assert_eq!(array.value(0), b"hello");
1126/// assert_eq!(array.value(3), b"large payload over 12 bytes");
1127/// ```
1128pub type BinaryViewArray = GenericByteViewArray<BinaryViewType>;
1129
1130impl BinaryViewArray {
1131 /// Convert the [`BinaryViewArray`] to [`StringViewArray`]
1132 /// If items not utf8 data, validate will fail and error returned.
1133 pub fn to_string_view(self) -> Result<StringViewArray, ArrowError> {
1134 StringViewType::validate(self.views(), self.data_buffers())?;
1135 unsafe { Ok(self.to_string_view_unchecked()) }
1136 }
1137
1138 /// Convert the [`BinaryViewArray`] to [`StringViewArray`]
1139 /// # Safety
1140 /// Caller is responsible for ensuring that items in array are utf8 data.
1141 pub unsafe fn to_string_view_unchecked(self) -> StringViewArray {
1142 unsafe { StringViewArray::new_unchecked(self.views, self.buffers, self.nulls) }
1143 }
1144}
1145
1146impl From<Vec<&[u8]>> for BinaryViewArray {
1147 fn from(v: Vec<&[u8]>) -> Self {
1148 Self::from_iter_values(v)
1149 }
1150}
1151
1152impl From<Vec<Option<&[u8]>>> for BinaryViewArray {
1153 fn from(v: Vec<Option<&[u8]>>) -> Self {
1154 v.into_iter().collect()
1155 }
1156}
1157
1158/// A [`GenericByteViewArray`] that stores utf8 data
1159///
1160/// See [`GenericByteViewArray`] for format and layout details.
1161///
1162/// # Example
1163/// ```
1164/// use arrow_array::StringViewArray;
1165/// let array = StringViewArray::from_iter_values(vec!["hello", "world", "lulu", "large payload over 12 bytes"]);
1166/// assert_eq!(array.value(0), "hello");
1167/// assert_eq!(array.value(3), "large payload over 12 bytes");
1168/// ```
1169pub type StringViewArray = GenericByteViewArray<StringViewType>;
1170
1171impl StringViewArray {
1172 /// Convert the [`StringViewArray`] to [`BinaryViewArray`]
1173 pub fn to_binary_view(self) -> BinaryViewArray {
1174 unsafe { BinaryViewArray::new_unchecked(self.views, self.buffers, self.nulls) }
1175 }
1176
1177 /// Returns true if all data within this array is ASCII
1178 pub fn is_ascii(&self) -> bool {
1179 // Alternative (but incorrect): directly check the underlying buffers
1180 // (1) Our string view might be sparse, i.e., a subset of the buffers,
1181 // so even if the buffer is not ascii, we can still be ascii.
1182 // (2) It is quite difficult to know the range of each buffer (unlike StringArray)
1183 // This means that this operation is quite expensive, shall we cache the result?
1184 // i.e. track `is_ascii` in the builder.
1185 self.iter().all(|v| match v {
1186 Some(v) => v.is_ascii(),
1187 None => true,
1188 })
1189 }
1190}
1191
1192impl From<Vec<&str>> for StringViewArray {
1193 fn from(v: Vec<&str>) -> Self {
1194 Self::from_iter_values(v)
1195 }
1196}
1197
1198impl From<Vec<Option<&str>>> for StringViewArray {
1199 fn from(v: Vec<Option<&str>>) -> Self {
1200 v.into_iter().collect()
1201 }
1202}
1203
1204impl From<Vec<String>> for StringViewArray {
1205 fn from(v: Vec<String>) -> Self {
1206 Self::from_iter_values(v)
1207 }
1208}
1209
1210impl From<Vec<Option<String>>> for StringViewArray {
1211 fn from(v: Vec<Option<String>>) -> Self {
1212 v.into_iter().collect()
1213 }
1214}
1215
1216#[cfg(test)]
1217mod tests {
1218 use crate::builder::{BinaryViewBuilder, StringViewBuilder};
1219 use crate::types::BinaryViewType;
1220 use crate::{
1221 Array, BinaryViewArray, GenericBinaryArray, GenericByteViewArray, StringViewArray,
1222 };
1223 use arrow_buffer::{Buffer, NullBuffer, ScalarBuffer};
1224 use arrow_data::{ArrayDataBuilder, ByteView, MAX_INLINE_VIEW_LEN};
1225 use arrow_schema::DataType;
1226 use rand::prelude::StdRng;
1227 use rand::{RngExt, SeedableRng};
1228 use std::str::from_utf8;
1229
1230 const BLOCK_SIZE: u32 = 8;
1231
1232 #[test]
1233 fn try_new_string() {
1234 let array = StringViewArray::from_iter_values(vec![
1235 "hello",
1236 "world",
1237 "lulu",
1238 "large payload over 12 bytes",
1239 ]);
1240 assert_eq!(array.value(0), "hello");
1241 assert_eq!(array.value(3), "large payload over 12 bytes");
1242 }
1243
1244 #[test]
1245 fn try_new_binary() {
1246 let array = BinaryViewArray::from_iter_values(vec![
1247 b"hello".as_slice(),
1248 b"world".as_slice(),
1249 b"lulu".as_slice(),
1250 b"large payload over 12 bytes".as_slice(),
1251 ]);
1252 assert_eq!(array.value(0), b"hello");
1253 assert_eq!(array.value(3), b"large payload over 12 bytes");
1254 }
1255
1256 #[test]
1257 fn try_new_empty_string() {
1258 // test empty array
1259 let array = {
1260 let mut builder = StringViewBuilder::new();
1261 builder.finish()
1262 };
1263 assert!(array.is_empty());
1264 }
1265
1266 #[test]
1267 fn try_new_empty_binary() {
1268 // test empty array
1269 let array = {
1270 let mut builder = BinaryViewBuilder::new();
1271 builder.finish()
1272 };
1273 assert!(array.is_empty());
1274 }
1275
1276 #[test]
1277 fn test_append_string() {
1278 // test builder append
1279 let array = {
1280 let mut builder = StringViewBuilder::new();
1281 builder.append_value("hello");
1282 builder.append_null();
1283 builder.append_option(Some("large payload over 12 bytes"));
1284 builder.finish()
1285 };
1286 assert_eq!(array.value(0), "hello");
1287 assert!(array.is_null(1));
1288 assert_eq!(array.value(2), "large payload over 12 bytes");
1289 }
1290
1291 #[test]
1292 fn test_append_binary() {
1293 // test builder append
1294 let array = {
1295 let mut builder = BinaryViewBuilder::new();
1296 builder.append_value(b"hello");
1297 builder.append_null();
1298 builder.append_option(Some(b"large payload over 12 bytes"));
1299 builder.finish()
1300 };
1301 assert_eq!(array.value(0), b"hello");
1302 assert!(array.is_null(1));
1303 assert_eq!(array.value(2), b"large payload over 12 bytes");
1304 }
1305
1306 #[test]
1307 fn test_in_progress_recreation() {
1308 let array = {
1309 // make a builder with small block size.
1310 let mut builder = StringViewBuilder::new().with_fixed_block_size(14);
1311 builder.append_value("large payload over 12 bytes");
1312 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"));
1313 builder.finish()
1314 };
1315 assert_eq!(array.value(0), "large payload over 12 bytes");
1316 assert_eq!(
1317 array.value(1),
1318 "another large payload over 12 bytes that double than the first one, so that we can trigger the in_progress in builder re-created"
1319 );
1320 assert_eq!(2, array.buffers.len());
1321 }
1322
1323 #[test]
1324 #[should_panic(expected = "Invalid buffer index at 0: got index 3 but only has 1 buffers")]
1325 fn new_with_invalid_view_data() {
1326 let v = "large payload over 12 bytes";
1327 let view = ByteView::new(13, &v.as_bytes()[0..4])
1328 .with_buffer_index(3)
1329 .with_offset(1);
1330 let views = ScalarBuffer::from(vec![view.into()]);
1331 let buffers = vec![Buffer::from_slice_ref(v)];
1332 StringViewArray::new(views, buffers, None);
1333 }
1334
1335 #[test]
1336 #[should_panic(
1337 expected = "Encountered non-UTF-8 data at index 0: invalid utf-8 sequence of 1 bytes from index 0"
1338 )]
1339 fn new_with_invalid_utf8_data() {
1340 let v: Vec<u8> = vec![
1341 // invalid UTF8
1342 0xf0, 0x80, 0x80, 0x80, // more bytes to make it larger than 12
1343 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1344 ];
1345 let view = ByteView::new(v.len() as u32, &v[0..4]);
1346 let views = ScalarBuffer::from(vec![view.into()]);
1347 let buffers = vec![Buffer::from_slice_ref(v)];
1348 StringViewArray::new(views, buffers, None);
1349 }
1350
1351 #[test]
1352 #[should_panic(expected = "View at index 0 contained non-zero padding for string of length 1")]
1353 fn new_with_invalid_zero_padding() {
1354 let mut data = [0; 12];
1355 data[0] = b'H';
1356 data[11] = 1; // no zero padding
1357
1358 let mut view_buffer = [0; 16];
1359 view_buffer[0..4].copy_from_slice(&1u32.to_le_bytes());
1360 view_buffer[4..].copy_from_slice(&data);
1361
1362 let view = ByteView::from(u128::from_le_bytes(view_buffer));
1363 let views = ScalarBuffer::from(vec![view.into()]);
1364 let buffers = vec![];
1365 StringViewArray::new(views, buffers, None);
1366 }
1367
1368 #[test]
1369 #[should_panic(expected = "Mismatch between embedded prefix and data")]
1370 fn test_mismatch_between_embedded_prefix_and_data() {
1371 let input_str_1 = "Hello, Rustaceans!";
1372 let input_str_2 = "Hallo, Rustaceans!";
1373 let length = input_str_1.len() as u32;
1374 assert!(input_str_1.len() > 12);
1375
1376 let mut view_buffer = [0; 16];
1377 view_buffer[0..4].copy_from_slice(&length.to_le_bytes());
1378 view_buffer[4..8].copy_from_slice(&input_str_1.as_bytes()[0..4]);
1379 view_buffer[8..12].copy_from_slice(&0u32.to_le_bytes());
1380 view_buffer[12..].copy_from_slice(&0u32.to_le_bytes());
1381 let view = ByteView::from(u128::from_le_bytes(view_buffer));
1382 let views = ScalarBuffer::from(vec![view.into()]);
1383 let buffers = vec![Buffer::from_slice_ref(input_str_2.as_bytes())];
1384
1385 StringViewArray::new(views, buffers, None);
1386 }
1387
1388 #[test]
1389 fn test_gc() {
1390 let test_data = [
1391 Some("longer than 12 bytes"),
1392 Some("short"),
1393 Some("t"),
1394 Some("longer than 12 bytes"),
1395 None,
1396 Some("short"),
1397 ];
1398
1399 let array = {
1400 let mut builder = StringViewBuilder::new().with_fixed_block_size(8); // create multiple buffers
1401 test_data.into_iter().for_each(|v| builder.append_option(v));
1402 builder.finish()
1403 };
1404 assert!(array.buffers.len() > 1);
1405
1406 fn check_gc(to_test: &StringViewArray) {
1407 let gc = to_test.gc();
1408 assert_ne!(to_test.data_buffers().len(), gc.data_buffers().len());
1409
1410 to_test.iter().zip(gc.iter()).for_each(|(a, b)| {
1411 assert_eq!(a, b);
1412 });
1413 assert_eq!(to_test.len(), gc.len());
1414 }
1415
1416 check_gc(&array);
1417 check_gc(&array.slice(1, 3));
1418 check_gc(&array.slice(2, 1));
1419 check_gc(&array.slice(2, 2));
1420 check_gc(&array.slice(3, 1));
1421 }
1422
1423 /// 1) Empty array: no elements, expect gc to return empty with no data buffers
1424 #[test]
1425 fn test_gc_empty_array() {
1426 let array = StringViewBuilder::new()
1427 .with_fixed_block_size(BLOCK_SIZE)
1428 .finish();
1429 let gced = array.gc();
1430 // length and null count remain zero
1431 assert_eq!(gced.len(), 0);
1432 assert_eq!(gced.null_count(), 0);
1433 // no underlying data buffers should be allocated
1434 assert!(
1435 gced.data_buffers().is_empty(),
1436 "Expected no data buffers for empty array"
1437 );
1438 }
1439
1440 /// 2) All inline values (<= INLINE_LEN): capacity-only data buffer, same values
1441 #[test]
1442 fn test_gc_all_inline() {
1443 let mut builder = StringViewBuilder::new().with_fixed_block_size(BLOCK_SIZE);
1444 // append many short strings, each exactly INLINE_LEN long
1445 for _ in 0..100 {
1446 let s = "A".repeat(MAX_INLINE_VIEW_LEN as usize);
1447 builder.append_option(Some(&s));
1448 }
1449 let array = builder.finish();
1450 let gced = array.gc();
1451 // Since all views fit inline, data buffer is empty
1452 assert_eq!(
1453 gced.data_buffers().len(),
1454 0,
1455 "Should have no data buffers for inline values"
1456 );
1457 assert_eq!(gced.len(), 100);
1458 // verify element-wise equality
1459 array.iter().zip(gced.iter()).for_each(|(orig, got)| {
1460 assert_eq!(orig, got, "Inline value mismatch after gc");
1461 });
1462 }
1463
1464 /// 3) All large values (> INLINE_LEN): each must be copied into the new data buffer
1465 #[test]
1466 fn test_gc_all_large() {
1467 let mut builder = StringViewBuilder::new().with_fixed_block_size(BLOCK_SIZE);
1468 let large_str = "X".repeat(MAX_INLINE_VIEW_LEN as usize + 5);
1469 // append multiple large strings
1470 for _ in 0..50 {
1471 builder.append_option(Some(&large_str));
1472 }
1473 let array = builder.finish();
1474 let gced = array.gc();
1475 // New data buffers should be populated (one or more blocks)
1476 assert!(
1477 !gced.data_buffers().is_empty(),
1478 "Expected data buffers for large values"
1479 );
1480 assert_eq!(gced.len(), 50);
1481 // verify that every large string emerges unchanged
1482 array.iter().zip(gced.iter()).for_each(|(orig, got)| {
1483 assert_eq!(orig, got, "Large view mismatch after gc");
1484 });
1485 }
1486
1487 /// 4) All null elements: ensure null bitmap handling path is correct
1488 #[test]
1489 fn test_gc_all_nulls() {
1490 let mut builder = StringViewBuilder::new().with_fixed_block_size(BLOCK_SIZE);
1491 for _ in 0..20 {
1492 builder.append_null();
1493 }
1494 let array = builder.finish();
1495 let gced = array.gc();
1496 // length and null count match
1497 assert_eq!(gced.len(), 20);
1498 assert_eq!(gced.null_count(), 20);
1499 // data buffers remain empty for null-only array
1500 assert!(
1501 gced.data_buffers().is_empty(),
1502 "No data should be stored for nulls"
1503 );
1504 }
1505
1506 /// 5) Random mix of inline, large, and null values with slicing tests
1507 #[test]
1508 fn test_gc_random_mixed_and_slices() {
1509 let mut rng = StdRng::seed_from_u64(42);
1510 let mut builder = StringViewBuilder::new().with_fixed_block_size(BLOCK_SIZE);
1511 // Keep a Vec of original Option<String> for later comparison
1512 let mut original: Vec<Option<String>> = Vec::new();
1513
1514 for _ in 0..200 {
1515 if rng.random_bool(0.1) {
1516 // 10% nulls
1517 builder.append_null();
1518 original.push(None);
1519 } else {
1520 // random length between 0 and twice the inline limit
1521 let len = rng.random_range(0..(MAX_INLINE_VIEW_LEN * 2));
1522 let s = "A".repeat(len as usize);
1523 builder.append_option(Some(&s));
1524 original.push(Some(s));
1525 }
1526 }
1527
1528 let array = builder.finish();
1529 // Test multiple slice ranges to ensure offset logic is correct
1530 for (offset, slice_len) in &[(0, 50), (10, 100), (150, 30)] {
1531 let sliced = array.slice(*offset, *slice_len);
1532 let gced = sliced.gc();
1533 // Build expected slice of Option<&str>
1534 let expected: Vec<Option<&str>> = original[*offset..(*offset + *slice_len)]
1535 .iter()
1536 .map(|opt| opt.as_deref())
1537 .collect();
1538
1539 assert_eq!(gced.len(), *slice_len, "Slice length mismatch");
1540 // Compare element-wise
1541 gced.iter().zip(expected.iter()).for_each(|(got, expect)| {
1542 assert_eq!(got, *expect, "Value mismatch in mixed slice after gc");
1543 });
1544 }
1545 }
1546
1547 #[test]
1548 #[cfg_attr(miri, ignore)] // Takes too long
1549 fn test_gc_huge_array() {
1550 // Construct multiple 128 MiB BinaryView entries so total > 4 GiB
1551 let block_len: usize = 128 * 1024 * 1024; // 128 MiB per view
1552 let num_views: usize = 36;
1553
1554 // Create a single 128 MiB data block with a simple byte pattern
1555 let buffer = Buffer::from_vec(vec![0xAB; block_len]);
1556 let buffer2 = Buffer::from_vec(vec![0xFF; block_len]);
1557
1558 // Append this block and then add many views pointing to it
1559 let mut builder = BinaryViewBuilder::new();
1560 let block_id = builder.append_block(buffer);
1561 for _ in 0..num_views / 2 {
1562 builder
1563 .try_append_view(block_id, 0, block_len as u32)
1564 .expect("append view into 128MiB block");
1565 }
1566 let block_id2 = builder.append_block(buffer2);
1567 for _ in 0..num_views / 2 {
1568 builder
1569 .try_append_view(block_id2, 0, block_len as u32)
1570 .expect("append view into 128MiB block");
1571 }
1572
1573 let array = builder.finish();
1574 let total = array.total_buffer_bytes_used();
1575 assert!(
1576 total > u32::MAX as usize,
1577 "Expected total non-inline bytes to exceed 4 GiB, got {total}"
1578 );
1579
1580 // Run gc and verify correctness
1581 let gced = array.gc();
1582 assert_eq!(gced.len(), num_views, "Length mismatch after gc");
1583 assert_eq!(gced.null_count(), 0, "Null count mismatch after gc");
1584 assert_ne!(
1585 gced.data_buffers().len(),
1586 1,
1587 "gc with huge buffer should not consolidate data into a single buffer"
1588 );
1589
1590 // Element-wise equality check across the entire array
1591 array.iter().zip(gced.iter()).for_each(|(orig, got)| {
1592 assert_eq!(orig, got, "Value mismatch after gc on huge array");
1593 });
1594 }
1595
1596 #[test]
1597 fn test_gc_slow_path_preserves_inline_views() {
1598 // Regression test for a bug in the multi-buffer "slow path" of `gc()`,
1599 // which `gc` only reaches once a view column references more than
1600 // `i32::MAX` (~2.1 GiB) of non-inline data. That path grouped and copied
1601 // only the *non-inline* views and dropped every inline (short) view,
1602 // returning an array shorter than its input. GC is a pure size
1603 // optimisation and must never change the row count.
1604 //
1605 // Rather than allocate gigabytes to hit the real threshold, drive the
1606 // same code via `gc_with_max_buffer_size` with a tiny cap so a handful
1607 // of bytes forces the buffer split. Inline, large, and null views are
1608 // interspersed so the split lands between and around inline entries.
1609 let long = "this is definitely longer than twelve bytes"; // non-inline
1610 let test_data = [
1611 Some("short"), // inline
1612 Some(long), // large
1613 Some("s"), // inline
1614 Some(long), // large
1615 None, // null
1616 Some(long), // large
1617 Some("also short"), // inline
1618 Some(long), // large
1619 Some("tail"), // inline
1620 ];
1621 let array: StringViewArray = test_data.into_iter().collect();
1622
1623 // Cap each output buffer at ~1.5 large values so the copy is forced to
1624 // split across several buffers, exercising the slow path with no
1625 // multi-GiB allocation.
1626 let max_buffer_size = long.len() + long.len() / 2;
1627 let gced = array.gc_with_max_buffer_size(max_buffer_size);
1628
1629 // The core invariant: gc preserves the row count.
1630 assert_eq!(gced.len(), array.len(), "gc changed the row count");
1631 // The split must actually have happened, otherwise we'd be exercising
1632 // the fast path and could not catch the inline-dropping regression.
1633 assert!(
1634 gced.data_buffers().len() > 1,
1635 "expected output split across multiple buffers, got {}",
1636 gced.data_buffers().len()
1637 );
1638 // No output buffer may exceed the cap.
1639 for buf in gced.data_buffers().iter() {
1640 assert!(buf.len() <= max_buffer_size, "buffer exceeded max size");
1641 }
1642 // Every value (inline, large, and null) is unchanged and in order.
1643 array
1644 .iter()
1645 .zip(gced.iter())
1646 .enumerate()
1647 .for_each(|(i, (a, b))| {
1648 assert_eq!(a, b, "value mismatch at index {i} after gc");
1649 });
1650 // The result round-trips through full validation.
1651 gced.to_data().validate_full().unwrap();
1652 }
1653
1654 #[test]
1655 fn test_gc_slow_path_value_larger_than_buffer() {
1656 // A single non-inline value can be larger than one output buffer (a view
1657 // length is a `u32`, so a value may exceed `max_buffer_size`). Such a
1658 // value cannot be split, so it occupies a buffer of its own; gc must
1659 // still preserve every row and produce a valid array.
1660 //
1661 // The real threshold needs a multi-GiB value, so drive the same logic
1662 // with a `max_buffer_size` smaller than the value length.
1663 let big = "x".repeat(64); // non-inline, larger than the cap below
1664 let test_data = [
1665 Some("short"), // inline
1666 Some(big.as_str()), // larger than max_buffer_size
1667 None, // null
1668 Some("tail"), // inline
1669 Some(big.as_str()), // larger than max_buffer_size
1670 ];
1671 let array: StringViewArray = test_data.into_iter().collect();
1672
1673 let max_buffer_size = 16; // smaller than a single `big` value
1674 let gced = array.gc_with_max_buffer_size(max_buffer_size);
1675
1676 // Row count is preserved.
1677 assert_eq!(gced.len(), array.len(), "gc changed the row count");
1678 // Each oversized value lands in its own buffer, so the copy splits.
1679 assert!(
1680 gced.data_buffers().len() > 1,
1681 "expected output split across multiple buffers, got {}",
1682 gced.data_buffers().len()
1683 );
1684 // Every value is unchanged and in order.
1685 array
1686 .iter()
1687 .zip(gced.iter())
1688 .enumerate()
1689 .for_each(|(i, (a, b))| {
1690 assert_eq!(a, b, "value mismatch at index {i} after gc");
1691 });
1692 // The result round-trips through full validation.
1693 gced.to_data().validate_full().unwrap();
1694 }
1695
1696 #[test]
1697 fn test_eq() {
1698 let test_data = [
1699 Some("longer than 12 bytes"),
1700 None,
1701 Some("short"),
1702 Some("again, this is longer than 12 bytes"),
1703 ];
1704
1705 let array1 = {
1706 let mut builder = StringViewBuilder::new().with_fixed_block_size(8);
1707 test_data.into_iter().for_each(|v| builder.append_option(v));
1708 builder.finish()
1709 };
1710 let array2 = {
1711 // create a new array with the same data but different layout
1712 let mut builder = StringViewBuilder::new().with_fixed_block_size(100);
1713 test_data.into_iter().for_each(|v| builder.append_option(v));
1714 builder.finish()
1715 };
1716 assert_eq!(array1, array1.clone());
1717 assert_eq!(array2, array2.clone());
1718 assert_eq!(array1, array2);
1719 }
1720
1721 /// Integration tests for `inline_key_fast` covering:
1722 ///
1723 /// 1. Monotonic ordering across increasing lengths and lexical variations.
1724 /// 2. Cross-check against `GenericBinaryArray` comparison to ensure semantic equivalence.
1725 ///
1726 /// This also includes a specific test for the “bar” vs. “bar\0” case, demonstrating why
1727 /// the length field is required even when all inline bytes fit in 12 bytes.
1728 ///
1729 /// The test includes strings that verify correct byte order (prevent reversal bugs),
1730 /// and length-based tie-breaking in the composite key.
1731 ///
1732 /// The test confirms that `inline_key_fast` produces keys which sort consistently
1733 /// with the expected lexicographical order of the raw byte arrays.
1734 #[test]
1735 fn test_inline_key_fast_various_lengths_and_lexical() {
1736 /// Helper to create a raw u128 value representing an inline ByteView:
1737 /// - `length`: number of meaningful bytes (must be ≤ 12)
1738 /// - `data`: the actual inline data bytes
1739 ///
1740 /// The first 4 bytes encode length in little-endian,
1741 /// the following 12 bytes contain the inline string data (unpadded).
1742 fn make_raw_inline(length: u32, data: &[u8]) -> u128 {
1743 assert!(length as usize <= 12, "Inline length must be ≤ 12");
1744 assert!(
1745 data.len() == length as usize,
1746 "Data length must match `length`"
1747 );
1748
1749 let mut raw_bytes = [0u8; 16];
1750 raw_bytes[0..4].copy_from_slice(&length.to_le_bytes()); // length stored little-endian
1751 raw_bytes[4..(4 + data.len())].copy_from_slice(data); // inline data
1752 u128::from_le_bytes(raw_bytes)
1753 }
1754
1755 // Test inputs: various lengths and lexical orders,
1756 // plus special cases for byte order and length tie-breaking
1757 let test_inputs: Vec<&[u8]> = vec![
1758 b"a",
1759 b"aa",
1760 b"aaa",
1761 b"aab",
1762 b"abcd",
1763 b"abcde",
1764 b"abcdef",
1765 b"abcdefg",
1766 b"abcdefgh",
1767 b"abcdefghi",
1768 b"abcdefghij",
1769 b"abcdefghijk",
1770 b"abcdefghijkl",
1771 // Tests for byte-order reversal bug:
1772 // Without the fix, "backend one" would compare as "eno dnekcab",
1773 // causing incorrect sort order relative to "backend two".
1774 b"backend one",
1775 b"backend two",
1776 // Tests length-tiebreaker logic:
1777 // "bar" (3 bytes) and "bar\0" (4 bytes) have identical inline data,
1778 // so only the length differentiates their ordering.
1779 b"bar",
1780 b"bar\0",
1781 // Additional lexical and length tie-breaking cases with same prefix, in correct lex order:
1782 b"than12Byt",
1783 b"than12Bytes",
1784 b"than12Bytes\0",
1785 b"than12Bytesx",
1786 b"than12Bytex",
1787 b"than12Bytez",
1788 // Additional lexical tests
1789 b"xyy",
1790 b"xyz",
1791 b"xza",
1792 ];
1793
1794 // Create a GenericBinaryArray for cross-comparison of lex order
1795 let array: GenericBinaryArray<i32> =
1796 GenericBinaryArray::from(test_inputs.iter().map(|s| Some(*s)).collect::<Vec<_>>());
1797
1798 for i in 0..array.len() - 1 {
1799 let v1 = array.value(i);
1800 let v2 = array.value(i + 1);
1801
1802 // Assert the array's natural lexical ordering is correct
1803 assert!(v1 < v2, "Array compare failed: {v1:?} !< {v2:?}");
1804
1805 // Assert the keys produced by inline_key_fast reflect the same ordering
1806 let key1 = GenericByteViewArray::<BinaryViewType>::inline_key_fast(make_raw_inline(
1807 v1.len() as u32,
1808 v1,
1809 ));
1810 let key2 = GenericByteViewArray::<BinaryViewType>::inline_key_fast(make_raw_inline(
1811 v2.len() as u32,
1812 v2,
1813 ));
1814
1815 assert!(
1816 key1 < key2,
1817 "Key compare failed: key({v1:?})=0x{key1:032x} !< key({v2:?})=0x{key2:032x}",
1818 );
1819 }
1820 }
1821
1822 #[test]
1823 fn empty_array_should_return_empty_lengths_iterator() {
1824 let empty = GenericByteViewArray::<BinaryViewType>::from(Vec::<&[u8]>::new());
1825
1826 let mut lengths_iter = empty.lengths();
1827 assert_eq!(lengths_iter.len(), 0);
1828 assert_eq!(lengths_iter.next(), None);
1829 }
1830
1831 #[test]
1832 fn array_lengths_should_return_correct_length_for_both_inlined_and_non_inlined() {
1833 let cases = GenericByteViewArray::<BinaryViewType>::from(vec![
1834 // Not inlined as longer than 12 bytes
1835 b"Supercalifragilisticexpialidocious" as &[u8],
1836 // Inlined as shorter than 12 bytes
1837 b"Hello",
1838 // Empty value
1839 b"",
1840 // Exactly 12 bytes
1841 b"abcdefghijkl",
1842 ]);
1843
1844 let mut lengths_iter = cases.lengths();
1845
1846 assert_eq!(lengths_iter.len(), cases.len());
1847
1848 let cases_iter = cases.iter();
1849
1850 for case in cases_iter {
1851 let case_value = case.unwrap();
1852 let length = lengths_iter.next().expect("Should have a length");
1853
1854 assert_eq!(case_value.len(), length as usize);
1855 }
1856
1857 assert_eq!(lengths_iter.next(), None, "Should not have more lengths");
1858 }
1859
1860 #[test]
1861 fn array_lengths_should_return_the_underlying_length_for_null_values() {
1862 let cases = GenericByteViewArray::<BinaryViewType>::from(vec![
1863 // Not inlined as longer than 12 bytes
1864 b"Supercalifragilisticexpialidocious" as &[u8],
1865 // Inlined as shorter than 12 bytes
1866 b"Hello",
1867 // Empty value
1868 b"",
1869 // Exactly 12 bytes
1870 b"abcdefghijkl",
1871 ]);
1872
1873 let (views, buffer, _) = cases.clone().into_parts();
1874
1875 // Keeping the values but just adding nulls on top
1876 let cases_with_all_nulls = GenericByteViewArray::<BinaryViewType>::new(
1877 views,
1878 buffer,
1879 Some(NullBuffer::new_null(cases.len())),
1880 );
1881
1882 let lengths_iter = cases.lengths();
1883 let mut all_nulls_lengths_iter = cases_with_all_nulls.lengths();
1884
1885 assert_eq!(lengths_iter.len(), all_nulls_lengths_iter.len());
1886
1887 for expected_length in lengths_iter {
1888 let actual_length = all_nulls_lengths_iter.next().expect("Should have a length");
1889
1890 assert_eq!(expected_length, actual_length);
1891 }
1892
1893 assert_eq!(
1894 all_nulls_lengths_iter.next(),
1895 None,
1896 "Should not have more lengths"
1897 );
1898 }
1899
1900 #[test]
1901 fn array_lengths_on_sliced_should_only_return_lengths_for_sliced_data() {
1902 let array = GenericByteViewArray::<BinaryViewType>::from(vec![
1903 b"aaaaaaaaaaaaaaaaaaaaaaaaaaa" as &[u8],
1904 b"Hello",
1905 b"something great",
1906 b"is",
1907 b"coming soon!",
1908 b"when you find what it is",
1909 b"let me know",
1910 b"cause",
1911 b"I",
1912 b"have no idea",
1913 b"what it",
1914 b"is",
1915 ]);
1916
1917 let sliced_array = array.slice(2, array.len() - 3);
1918
1919 let mut lengths_iter = sliced_array.lengths();
1920
1921 assert_eq!(lengths_iter.len(), sliced_array.len());
1922
1923 let values_iter = sliced_array.iter();
1924
1925 for value in values_iter {
1926 let value = value.unwrap();
1927 let length = lengths_iter.next().expect("Should have a length");
1928
1929 assert_eq!(value.len(), length as usize);
1930 }
1931
1932 assert_eq!(lengths_iter.next(), None, "Should not have more lengths");
1933 }
1934
1935 #[should_panic(expected = "Mismatched data type, expected Utf8View, got BinaryView")]
1936 #[test]
1937 fn invalid_casting_from_array_data() {
1938 // Should not be able to cast to StringViewArray due to invalid UTF-8
1939 let array_data = binary_view_array_with_invalid_utf8_data().into_data();
1940 let _ = StringViewArray::from(array_data);
1941 }
1942
1943 #[should_panic(expected = "invalid utf-8 sequence")]
1944 #[test]
1945 fn invalid_array_data() {
1946 let (views, buffers, nulls) = binary_view_array_with_invalid_utf8_data().into_parts();
1947
1948 // manually try and add invalid array data with Utf8View data type
1949 let mut builder = ArrayDataBuilder::new(DataType::Utf8View)
1950 .add_buffer(views.into_inner())
1951 .len(3);
1952 for buffer in buffers.iter() {
1953 builder = builder.add_buffer(buffer.clone())
1954 }
1955 builder = builder.nulls(nulls);
1956
1957 let data = builder.build().unwrap(); // should fail validation
1958 let _arr = StringViewArray::from(data);
1959 }
1960
1961 /// Returns a BinaryViewArray with one invalid UTF-8 value
1962 fn binary_view_array_with_invalid_utf8_data() -> BinaryViewArray {
1963 let array = GenericByteViewArray::<BinaryViewType>::from(vec![
1964 b"aaaaaaaaaaaaaaaaaaaaaaaaaaa" as &[u8],
1965 &[
1966 0xf0, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1967 0x00, 0x00,
1968 ],
1969 b"good",
1970 ]);
1971 assert!(from_utf8(array.value(0)).is_ok());
1972 assert!(from_utf8(array.value(1)).is_err()); // value 1 is invalid utf8
1973 assert!(from_utf8(array.value(2)).is_ok());
1974 array
1975 }
1976
1977 #[test]
1978 fn test_total_bytes_len() {
1979 // inlined: "hello"=5, "world"=5, "lulu"=4 → 14
1980 // non-inlined: "large payload over 12 bytes"=27
1981 // null: should not count
1982 let mut builder = StringViewBuilder::new();
1983 builder.append_value("hello");
1984 builder.append_value("world");
1985 builder.append_value("lulu");
1986 builder.append_null();
1987 builder.append_value("large payload over 12 bytes");
1988 let array = builder.finish();
1989 assert_eq!(array.total_bytes_len(), 5 + 5 + 4 + 27);
1990 }
1991
1992 #[test]
1993 fn test_total_bytes_len_empty() {
1994 let array = StringViewArray::from_iter::<Vec<Option<&str>>>(vec![]);
1995 assert_eq!(array.total_bytes_len(), 0);
1996 }
1997
1998 #[test]
1999 fn test_total_bytes_len_all_nulls() {
2000 let array = StringViewArray::new_null(5);
2001 assert_eq!(array.total_bytes_len(), 0);
2002 }
2003
2004 #[test]
2005 fn test_total_bytes_len_binary_view() {
2006 let array = BinaryViewArray::from_iter(vec![
2007 Some(b"hi".as_ref()),
2008 None,
2009 Some(b"large payload over 12 bytes".as_ref()),
2010 ]);
2011 assert_eq!(array.total_bytes_len(), 2 + 27);
2012 }
2013}