Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 114 additions & 7 deletions arrow-data/src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1505,12 +1505,47 @@ impl ArrayData {
let child = &self.child_data[0];
self.validate_offsets_full::<i64>(child.len)
}
DataType::Union(_, _) => {
// Validate Union Array as part of implementing new Union semantics
// See comments in `ArrayData::validate()`
// https://github.com/apache/arrow-rs/issues/85
//
// TODO file follow on ticket for full union validation
DataType::Union(fields, mode) => {
let mut child_lens: Vec<Option<usize>> = vec![None; 128];
for (index, (type_id, _)) in fields.iter().enumerate() {
let child = &self.child_data[index];
child_lens[type_id as usize] = Some(child.len());
}

// Type id values must match one of the fields.
let type_ids = self.typed_buffer::<i8>(0, self.len)?;
for (index, &type_id) in type_ids.iter().enumerate() {
if type_id < 0 {
return Err(ArrowError::InvalidArgumentError(format!(
"Type id values cannot be negative, got {type_id} at index {index}"
)));
}
let type_id = type_id as usize;
if child_lens[type_id].is_none() {
return Err(ArrowError::InvalidArgumentError(format!(
"Type id values must match one of the field type ids, got {type_id} at index {index}"
)));
}
}

// Check the value offsets are in bounds.
if mode == &UnionMode::Dense {
let offsets = self.typed_buffer::<i32>(1, self.len)?;

if type_ids
.iter()
.zip(offsets.iter())
.any(|(&type_id, &offset)| {
offset < 0 || offset as usize >= child_lens[type_id as usize].unwrap()
})
{
return Err(ArrowError::InvalidArgumentError(
"Offsets must be non-negative and within the length of the child array"
.to_string(),
));
}
}

Ok(())
}
DataType::Dictionary(key_type, _value_type) => {
Expand Down Expand Up @@ -2345,7 +2380,7 @@ pub(crate) fn get_fixed_size_binary_width(data_type: &DataType) -> usize {
mod tests {
use super::*;
use arrow_buffer::{OffsetBuffer, ScalarBuffer};
use arrow_schema::{Field, Fields};
use arrow_schema::{Field, Fields, UnionFields};

// See arrow/tests/array_data_validation.rs for test of array validation

Expand Down Expand Up @@ -3171,4 +3206,76 @@ mod tests {

[from_builder_res, from_try_new_res]
}

#[test]
fn test_union_validate_type_ids() {
let dt = DataType::Union(
UnionFields::try_new(
vec![5],
vec![Arc::new(Field::new("a", DataType::Int32, true))],
)
.unwrap(),
UnionMode::Sparse,
);

let child = ArrayData::builder(DataType::Int32)
.len(2)
.add_buffer(make_i32_buffer(2))
.build()
.unwrap();

let err = ArrayDataBuilder::new(dt)
.len(2)
.offset(0)
.buffers(vec![vec![5_i8, 2].into()])
.child_data(vec![child])
.build()
.unwrap_err();
assert_eq!(
err.to_string(),
"Invalid argument error: Type id values must match one of the field type ids, got 2 at index 1"
);
}

#[test]
fn test_union_validate_dense_offsets() {
let dt = DataType::Union(
UnionFields::try_new(
vec![5],
vec![Arc::new(Field::new("a", DataType::Int32, true))],
)
.unwrap(),
UnionMode::Dense,
);

let child = ArrayData::builder(DataType::Int32)
.len(2)
.add_buffer(make_i32_buffer(2))
.build()
.unwrap();

let err = ArrayDataBuilder::new(dt.clone())
.len(2)
// 2nd offset '10' is out of bounds
.buffers(vec![vec![5_i8, 5].into(), vec![0_i32, 10].into()])
.child_data(vec![child.clone()])
.build()
.unwrap_err();
assert_eq!(
err.to_string(),
"Invalid argument error: Offsets must be non-negative and within the length of the child array"
);

let err = ArrayDataBuilder::new(dt)
.len(2)
// 2nd offset is negative
.buffers(vec![vec![5_i8, 5].into(), vec![0_i32, -1].into()])
.child_data(vec![child])
.build()
.unwrap_err();
assert_eq!(
err.to_string(),
"Invalid argument error: Offsets must be non-negative and within the length of the child array"
);
}
}
Loading