From 0e8e4045e65c2a5068bf081ef20da0435d32f87d Mon Sep 17 00:00:00 2001 From: Mikhail Kot Date: Wed, 5 Aug 2026 10:29:22 +0100 Subject: [PATCH] initial Signed-off-by: Mikhail Kot --- vortex-array/src/arrays/varbinview/array.rs | 22 ++++++--- vortex-array/src/arrays/varbinview/tests.rs | 54 +++++++++++++++++++++ 2 files changed, 69 insertions(+), 7 deletions(-) diff --git a/vortex-array/src/arrays/varbinview/array.rs b/vortex-array/src/arrays/varbinview/array.rs index 9e97257b3be..5efc1bf5734 100644 --- a/vortex-array/src/arrays/varbinview/array.rs +++ b/vortex-array/src/arrays/varbinview/array.rs @@ -323,8 +323,11 @@ impl VarBinViewData { where F: Fn(&[u8]) -> bool, { - let validate_view = |idx: usize, view: &BinaryView| -> VortexResult<()> { + let validate_view = |idx: usize, view: &BinaryView, valid: bool| -> VortexResult<()> { if view.is_inlined() { + if !valid { + return Ok(()); + } // Validate the inline bytestring let bytes = &view.as_inlined().data[..view.len() as usize]; vortex_ensure!( @@ -354,6 +357,10 @@ impl VarBinViewData { buf.len(), ); + if !valid { + return Ok(()); + } + // Make sure the prefix data matches the buffer data. let bytes = &buf[start_offset..end_offset]; vortex_ensure!( @@ -378,17 +385,18 @@ impl VarBinViewData { let mut ctx = legacy_session().create_execution_ctx(); let mask = validity.execute_mask(views.len(), &mut ctx)?; for ((idx, view), valid) in views.iter().enumerate().zip(mask.iter()) { - if valid { - validate_view(idx, view)?; - } + validate_view(idx, view, valid)?; + } + } + Validity::AllInvalid => { + for (idx, view) in views.iter().enumerate() { + validate_view(idx, view, false)?; } } - // Every entry is null, so there is nothing to validate. - Validity::AllInvalid => {} // No nulls: validate every view. Validity::NonNullable | Validity::AllValid => { for (idx, view) in views.iter().enumerate() { - validate_view(idx, view)?; + validate_view(idx, view, true)?; } } } diff --git a/vortex-array/src/arrays/varbinview/tests.rs b/vortex-array/src/arrays/varbinview/tests.rs index 981d4f602a1..66988e30d27 100644 --- a/vortex-array/src/arrays/varbinview/tests.rs +++ b/vortex-array/src/arrays/varbinview/tests.rs @@ -1,11 +1,24 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors +#![allow(clippy::clone_on_ref_ptr)] +use std::sync::Arc; + +use vortex_buffer::BitBuffer; +use vortex_buffer::Buffer; +use vortex_buffer::ByteBuffer; +use vortex_error::VortexResult; + +use crate::IntoArray; use crate::VortexSessionExecute; use crate::array_session; +use crate::arrays::BoolArray; use crate::arrays::VarBinViewArray; use crate::arrays::varbinview::BinaryView; use crate::assert_arrays_eq; +use crate::dtype::DType; +use crate::dtype::Nullability; +use crate::validity::Validity; #[test] pub fn varbin_view() { @@ -49,3 +62,44 @@ pub fn binary_view_size_and_alignment() { assert_eq!(size_of::(), 16); assert_eq!(align_of::(), 16); } + +// Null views in VarBinView are validated +#[test] +pub fn binary_view_null_view() { + let views = Buffer::::copy_from(vec![ + BinaryView::new_inlined(b"ololo"), + BinaryView::new_ref(14, *b"hell", 0, 0), + BinaryView::new_ref(13, *b"AAAA", 0xDEAD_BEEF, 0xF000_0000), + ]); + let data = b"hello world ololo"; + + let validity = BitBuffer::from_iter([true, true, false]); + let validity = BoolArray::new(validity, Validity::NonNullable); + let validity = Validity::Array(validity.into_array()); + let buffers = Arc::new([ByteBuffer::from(data.to_vec())]); + let dtype = DType::Utf8(Nullability::Nullable); + + let array = VarBinViewArray::try_new(views.clone(), buffers.clone(), dtype.clone(), validity); + assert!(array.is_err()); + let array = VarBinViewArray::try_new(views, buffers, dtype, Validity::AllInvalid); + assert!(array.is_err()); +} + +/// Validation of Null views in VarBinView doesn't check prefix and contents +#[test] +pub fn binary_view_null_view_in_bounds() -> VortexResult<()> { + let data = b"hello world foo\xFF\xFE\xFD"; + let buffers = Arc::new([ByteBuffer::from(data.to_vec())]); + let dtype = DType::Utf8(Nullability::Nullable); + + let validity = BoolArray::new(BitBuffer::from_iter([true, false]), Validity::NonNullable); + let validity = Validity::Array(validity.into_array()); + + let valid_row = BinaryView::new_ref(15, *b"hell", 0, 0); + let garbage_row = BinaryView::new_ref(13, *b"XXXX", 0, 5); + + let views = Buffer::::copy_from(vec![valid_row, garbage_row]); + VarBinViewArray::try_new(views.clone(), buffers.clone(), dtype.clone(), validity)?; + VarBinViewArray::try_new(views, buffers, dtype, Validity::AllInvalid)?; + Ok(()) +}