1use std::fmt::Display;
5use std::fmt::Formatter;
6
7use num_traits::AsPrimitive;
8use vortex_array::arrays::PrimitiveArray;
9use vortex_buffer::ByteBuffer;
10use vortex_error::VortexExpect;
11use vortex_error::VortexResult;
12use vortex_error::vortex_ensure;
13use vortex_error::vortex_err;
14
15use crate::ArrayRef;
16use crate::ArraySlots;
17use crate::VortexSessionExecute;
18use crate::array::Array;
19use crate::array::ArrayParts;
20use crate::array::TypedArrayRef;
21use crate::array::child_to_validity;
22use crate::array::validity_to_child;
23use crate::array_slots;
24use crate::arrays::VarBin;
25use crate::arrays::varbin::builder::VarBinBuilder;
26use crate::buffer::BufferHandle;
27use crate::dtype::DType;
28use crate::dtype::Nullability;
29use crate::dtype::OffsetBuilderPType;
30use crate::legacy_session;
31use crate::match_each_integer_ptype;
32use crate::validity::Validity;
33
34#[array_slots(VarBin)]
35pub struct VarBinSlots {
36 #[slot(0)]
38 pub offsets: ArrayRef,
39 #[slot(1)]
41 pub validity: Option<ArrayRef>,
42}
43
44#[derive(Clone, Debug)]
45pub struct VarBinData {
46 pub(super) bytes: BufferHandle,
47}
48
49impl Display for VarBinData {
50 fn fmt(&self, _f: &mut Formatter<'_>) -> std::fmt::Result {
51 Ok(())
52 }
53}
54
55pub struct VarBinDataParts {
56 pub dtype: DType,
57 pub bytes: BufferHandle,
58 pub offsets: ArrayRef,
59 pub validity: Validity,
60}
61
62impl VarBinData {
63 pub fn build(offsets: ArrayRef, bytes: ByteBuffer, dtype: DType, validity: Validity) -> Self {
70 Self::try_build(offsets, bytes, dtype, validity).vortex_expect("VarBinArray new")
71 }
72
73 pub fn build_from_handle(
80 offset: ArrayRef,
81 bytes: BufferHandle,
82 dtype: DType,
83 validity: Validity,
84 ) -> Self {
85 Self::try_build_from_handle(offset, bytes, dtype, validity).vortex_expect("VarBinArray new")
86 }
87
88 pub(crate) fn make_slots(offsets: ArrayRef, validity: &Validity, len: usize) -> ArraySlots {
89 VarBinSlots {
90 offsets,
91 validity: validity_to_child(validity, len),
92 }
93 .into_slots()
94 }
95
96 pub fn try_build(
105 offsets: ArrayRef,
106 bytes: ByteBuffer,
107 dtype: DType,
108 validity: Validity,
109 ) -> VortexResult<Self> {
110 let bytes = BufferHandle::new_host(bytes);
111 Self::validate(&offsets, &bytes, &dtype, &validity)?;
112
113 Ok(unsafe { Self::new_unchecked_from_handle(bytes) })
115 }
116
117 pub fn try_build_from_handle(
127 offsets: ArrayRef,
128 bytes: BufferHandle,
129 dtype: DType,
130 validity: Validity,
131 ) -> VortexResult<Self> {
132 Self::validate(&offsets, &bytes, &dtype, &validity)?;
133
134 Ok(unsafe { Self::new_unchecked_from_handle(bytes) })
136 }
137
138 pub unsafe fn new_unchecked(bytes: ByteBuffer) -> Self {
167 unsafe { Self::new_unchecked_from_handle(BufferHandle::new_host(bytes)) }
170 }
171
172 pub unsafe fn new_unchecked_from_handle(bytes: BufferHandle) -> Self {
179 Self { bytes }
180 }
181
182 pub fn validate(
186 offsets: &ArrayRef,
187 bytes: &BufferHandle,
188 dtype: &DType,
189 validity: &Validity,
190 ) -> VortexResult<()> {
191 vortex_ensure!(
193 offsets.dtype().is_int() && !offsets.dtype().is_nullable(),
194 MismatchedTypes: "non nullable int", offsets.dtype()
195 );
196
197 vortex_ensure!(
199 matches!(dtype, DType::Binary(_) | DType::Utf8(_)),
200 MismatchedTypes: "utf8 or binary", dtype
201 );
202
203 vortex_ensure!(
205 dtype.is_nullable() != matches!(validity, Validity::NonNullable),
206 InvalidArgument: "incorrect validity {:?} for dtype {}",
207 validity,
208 dtype
209 );
210
211 vortex_ensure!(
213 !offsets.is_empty(),
214 InvalidArgument: "Offsets must have at least one element"
215 );
216
217 if let Some(validity_len) = validity.maybe_len() {
219 vortex_ensure!(
220 validity_len == offsets.len() - 1,
221 "Validity length {} doesn't match array length {}",
222 validity_len,
223 offsets.len() - 1
224 );
225 }
226
227 if offsets.is_host()
229 && bytes.is_on_host()
230 && matches!(dtype, DType::Utf8(_))
231 && let Some(bytes) = bytes.as_host_opt()
232 {
233 Self::validate_utf8(offsets, bytes.as_ref(), validity)?;
234 }
235
236 Ok(())
237 }
238
239 #[allow(clippy::disallowed_methods)]
241 fn validate_utf8(offsets: &ArrayRef, bytes: &[u8], validity: &Validity) -> VortexResult<()> {
242 let validate_at = |i: usize, start: usize, end: usize| -> VortexResult<()> {
243 let string_bytes = &bytes[start..end];
244 simdutf8::basic::from_utf8(string_bytes).map_err(|_| {
245 #[expect(clippy::unwrap_used)]
246 let err = simdutf8::compat::from_utf8(string_bytes).unwrap_err();
248 vortex_err!("invalid utf-8: {err} at index {i}")
249 })?;
250 Ok(())
251 };
252
253 let mut ctx = legacy_session().create_execution_ctx();
254 let primitive_offsets = offsets.clone().execute::<PrimitiveArray>(&mut ctx)?;
256
257 let mask = match validity {
261 Validity::Array(_) => {
262 Some(validity.execute_mask(primitive_offsets.len().saturating_sub(1), &mut ctx)?)
263 }
264 _ => None,
265 };
266 let all_invalid = validity.definitely_all_null();
267
268 match_each_integer_ptype!(primitive_offsets.dtype().as_ptype(), |O| {
269 let offsets_slice = primitive_offsets.as_slice::<O>();
270
271 let last_offset: usize = offsets_slice[offsets_slice.len() - 1].as_();
272 vortex_ensure!(
273 last_offset <= bytes.len(),
274 InvalidArgument: "Last offset {} exceeds bytes length {}",
275 last_offset,
276 bytes.len()
277 );
278
279 for (i, (start, end)) in offsets_slice
280 .windows(2)
281 .map(|o| (o[0].as_(), o[1].as_()))
282 .enumerate()
283 {
284 let valid = mask.as_ref().map_or(!all_invalid, |mask| mask.value(i));
285 if valid {
286 validate_at(i, start, end)?;
287 }
288 }
289 });
290 Ok(())
291 }
292
293 #[inline]
301 pub fn bytes(&self) -> &ByteBuffer {
302 self.bytes.as_host()
303 }
304
305 #[inline]
307 pub fn bytes_handle(&self) -> &BufferHandle {
308 &self.bytes
309 }
310}
311
312pub trait VarBinArrayExt: VarBinArraySlotsExt {
313 fn dtype_parts(&self) -> (bool, Nullability) {
314 match self.as_ref().dtype() {
315 DType::Utf8(nullability) => (true, *nullability),
316 DType::Binary(nullability) => (false, *nullability),
317 _ => unreachable!("VarBinArrayExt requires a utf8 or binary dtype"),
318 }
319 }
320
321 fn is_utf8(&self) -> bool {
322 self.dtype_parts().0
323 }
324
325 fn nullability(&self) -> Nullability {
326 self.dtype_parts().1
327 }
328
329 fn varbin_validity(&self) -> Validity {
330 child_to_validity(
331 self.as_ref().slots()[VarBinSlots::VALIDITY].as_ref(),
332 self.nullability(),
333 )
334 }
335
336 #[allow(clippy::disallowed_methods)]
337 fn offset_at(&self, index: usize) -> usize {
338 assert!(
339 index <= self.as_ref().len(),
340 "Index {index} out of bounds 0..={}",
341 self.as_ref().len()
342 );
343
344 (&self
345 .offsets()
346 .execute_scalar(index, &mut legacy_session().create_execution_ctx())
347 .vortex_expect("offsets must support execute_scalar"))
348 .try_into()
349 .vortex_expect("Failed to convert offset to usize")
350 }
351
352 fn bytes_at(&self, index: usize) -> ByteBuffer {
353 let start = self.offset_at(index);
354 let end = self.offset_at(index + 1);
355 self.bytes().slice(start..end)
356 }
357
358 fn sliced_bytes(&self) -> ByteBuffer {
359 let first_offset: usize = self.offset_at(0);
360 let last_offset = self.offset_at(self.as_ref().len());
361 self.bytes().slice(first_offset..last_offset)
362 }
363}
364impl<T: TypedArrayRef<VarBin>> VarBinArrayExt for T {}
365
366impl Array<VarBin> {
368 pub fn from_vec<T: AsRef<[u8]>>(vec: Vec<T>, dtype: DType) -> Self {
369 let size: usize = vec.iter().map(|v| v.as_ref().len()).sum();
370 if size < u32::MAX as usize {
371 Self::from_vec_sized::<u32, T>(vec, dtype)
372 } else {
373 Self::from_vec_sized::<u64, T>(vec, dtype)
374 }
375 }
376
377 #[expect(
378 clippy::same_name_method,
379 reason = "intentionally named from_iter like Iterator::from_iter"
380 )]
381 pub fn from_iter<T: AsRef<[u8]>, I: IntoIterator<Item = Option<T>>>(
382 iter: I,
383 dtype: DType,
384 ) -> Self {
385 let iter = iter.into_iter();
386 let mut builder = VarBinBuilder::<u32>::with_capacity(dtype, iter.size_hint().0);
387 for v in iter {
388 builder.append(v.as_ref().map(|o| o.as_ref()));
389 }
390 builder.finish_into_varbin()
391 }
392
393 pub fn from_iter_nonnull<T: AsRef<[u8]>, I: IntoIterator<Item = T>>(
394 iter: I,
395 dtype: DType,
396 ) -> Self {
397 let iter = iter.into_iter();
398 let mut builder = VarBinBuilder::<u32>::with_capacity(dtype, iter.size_hint().0);
399 for v in iter {
400 builder.append_value(v);
401 }
402 builder.finish_into_varbin()
403 }
404
405 fn from_vec_sized<O, T>(vec: Vec<T>, dtype: DType) -> Self
406 where
407 O: OffsetBuilderPType,
408 T: AsRef<[u8]>,
409 {
410 let mut builder = VarBinBuilder::<O>::with_capacity(dtype, vec.len());
411 for v in vec {
412 builder.append_value(v.as_ref());
413 }
414 builder.finish_into_varbin()
415 }
416
417 pub fn from_strs(value: Vec<&str>) -> Self {
419 Self::from_vec(value, DType::Utf8(Nullability::NonNullable))
420 }
421
422 pub fn from_nullable_strs(value: Vec<Option<&str>>) -> Self {
424 Self::from_iter(value, DType::Utf8(Nullability::Nullable))
425 }
426
427 pub fn from_bytes(value: Vec<&[u8]>) -> Self {
429 Self::from_vec(value, DType::Binary(Nullability::NonNullable))
430 }
431
432 pub fn from_nullable_bytes(value: Vec<Option<&[u8]>>) -> Self {
434 Self::from_iter(value, DType::Binary(Nullability::Nullable))
435 }
436
437 pub fn into_data_parts(self) -> VarBinDataParts {
438 let dtype = self.dtype().clone();
439 let validity = self.varbin_validity();
440 let offsets = self.offsets().clone();
441 let data = self.into_data();
442 VarBinDataParts {
443 dtype,
444 bytes: data.bytes,
445 offsets,
446 validity,
447 }
448 }
449}
450
451impl Array<VarBin> {
452 pub fn new(offsets: ArrayRef, bytes: ByteBuffer, dtype: DType, validity: Validity) -> Self {
454 let len = offsets.len().saturating_sub(1);
455 let slots = VarBinData::make_slots(offsets, &validity, len);
456 let data = VarBinData::build(
457 slots[VarBinSlots::OFFSETS]
458 .as_ref()
459 .vortex_expect("VarBinArray offsets slot")
460 .clone(),
461 bytes,
462 dtype.clone(),
463 validity,
464 );
465 unsafe {
466 Array::from_parts_unchecked(ArrayParts::new(VarBin, dtype, len, data).with_slots(slots))
467 }
468 }
469
470 pub unsafe fn new_unchecked(
476 offsets: ArrayRef,
477 bytes: ByteBuffer,
478 dtype: DType,
479 validity: Validity,
480 ) -> Self {
481 let len = offsets.len().saturating_sub(1);
482 let slots = VarBinData::make_slots(offsets, &validity, len);
483 let data = unsafe { VarBinData::new_unchecked(bytes) };
484 unsafe {
485 Array::from_parts_unchecked(ArrayParts::new(VarBin, dtype, len, data).with_slots(slots))
486 }
487 }
488
489 pub unsafe fn new_unchecked_from_handle(
495 offsets: ArrayRef,
496 bytes: BufferHandle,
497 dtype: DType,
498 validity: Validity,
499 ) -> Self {
500 let len = offsets.len().saturating_sub(1);
501 let slots = VarBinData::make_slots(offsets, &validity, len);
502 let data = unsafe { VarBinData::new_unchecked_from_handle(bytes) };
503 unsafe {
504 Array::from_parts_unchecked(ArrayParts::new(VarBin, dtype, len, data).with_slots(slots))
505 }
506 }
507
508 pub fn try_new(
510 offsets: ArrayRef,
511 bytes: ByteBuffer,
512 dtype: DType,
513 validity: Validity,
514 ) -> VortexResult<Self> {
515 let len = offsets.len() - 1;
516 let bytes = BufferHandle::new_host(bytes);
517 VarBinData::validate(&offsets, &bytes, &dtype, &validity)?;
518 let slots = VarBinData::make_slots(offsets, &validity, len);
519 let data = unsafe { VarBinData::new_unchecked_from_handle(bytes) };
521 Ok(unsafe {
522 Array::from_parts_unchecked(ArrayParts::new(VarBin, dtype, len, data).with_slots(slots))
523 })
524 }
525}
526
527impl From<Vec<&[u8]>> for Array<VarBin> {
528 fn from(value: Vec<&[u8]>) -> Self {
529 Self::from_vec(value, DType::Binary(Nullability::NonNullable))
530 }
531}
532
533impl From<Vec<Vec<u8>>> for Array<VarBin> {
534 fn from(value: Vec<Vec<u8>>) -> Self {
535 Self::from_vec(value, DType::Binary(Nullability::NonNullable))
536 }
537}
538
539impl From<Vec<String>> for Array<VarBin> {
540 fn from(value: Vec<String>) -> Self {
541 Self::from_vec(value, DType::Utf8(Nullability::NonNullable))
542 }
543}
544
545impl From<Vec<&str>> for Array<VarBin> {
546 fn from(value: Vec<&str>) -> Self {
547 Self::from_vec(value, DType::Utf8(Nullability::NonNullable))
548 }
549}
550
551impl From<Vec<Option<&[u8]>>> for Array<VarBin> {
552 fn from(value: Vec<Option<&[u8]>>) -> Self {
553 Self::from_iter(value, DType::Binary(Nullability::Nullable))
554 }
555}
556
557impl From<Vec<Option<Vec<u8>>>> for Array<VarBin> {
558 fn from(value: Vec<Option<Vec<u8>>>) -> Self {
559 Self::from_iter(value, DType::Binary(Nullability::Nullable))
560 }
561}
562
563impl From<Vec<Option<String>>> for Array<VarBin> {
564 fn from(value: Vec<Option<String>>) -> Self {
565 Self::from_iter(value, DType::Utf8(Nullability::Nullable))
566 }
567}
568
569impl From<Vec<Option<&str>>> for Array<VarBin> {
570 fn from(value: Vec<Option<&str>>) -> Self {
571 Self::from_iter(value, DType::Utf8(Nullability::Nullable))
572 }
573}
574
575impl<'a> FromIterator<Option<&'a [u8]>> for Array<VarBin> {
576 fn from_iter<T: IntoIterator<Item = Option<&'a [u8]>>>(iter: T) -> Self {
577 Self::from_iter(iter, DType::Binary(Nullability::Nullable))
578 }
579}
580
581impl FromIterator<Option<Vec<u8>>> for Array<VarBin> {
582 fn from_iter<T: IntoIterator<Item = Option<Vec<u8>>>>(iter: T) -> Self {
583 Self::from_iter(iter, DType::Binary(Nullability::Nullable))
584 }
585}
586
587impl FromIterator<Option<String>> for Array<VarBin> {
588 fn from_iter<T: IntoIterator<Item = Option<String>>>(iter: T) -> Self {
589 Self::from_iter(iter, DType::Utf8(Nullability::Nullable))
590 }
591}
592
593impl<'a> FromIterator<Option<&'a str>> for Array<VarBin> {
594 fn from_iter<T: IntoIterator<Item = Option<&'a str>>>(iter: T) -> Self {
595 Self::from_iter(iter, DType::Utf8(Nullability::Nullable))
596 }
597}