use std::sync::Arc;
use arrow::array::BooleanBufferBuilder;
use arrow::array::{
Array, ArrayRef, AsArray, ListArray, RecordBatch, RecordBatchOptions, StructArray, make_array,
new_empty_array, new_null_array,
};
use arrow::buffer::{NullBuffer, OffsetBuffer};
use arrow::compute::{concat, is_null, kernels::zip::zip};
use arrow::datatypes::{DataType, Field, FieldRef, Fields, Schema, SchemaRef};
use crate::error::{Error, Result};
pub fn merge_schemas(base: &Schema, other: &Schema) -> Result<Schema> {
let fields = merge_children(base.fields(), other.fields(), "")?;
Ok(Schema::new_with_metadata(fields, base.metadata().clone()))
}
fn merge_children(base: &Fields, other: &Fields, parent: &str) -> Result<Fields> {
let mut fields = base
.iter()
.map(|field| match other.find(field.name()) {
Some((_, o)) => {
merge_field(field, o, &format!("{parent}{}", field.name())).map(Arc::new)
}
None => Ok(field.clone()),
})
.collect::<Result<Vec<FieldRef>>>()?;
fields.extend(
other
.iter()
.filter(|f| base.find(f.name()).is_none())
.cloned(),
);
Ok(fields.into())
}
fn merge_field(base: &Field, other: &Field, path: &str) -> Result<Field> {
let data_type = match (base.data_type(), other.data_type()) {
(a, b) if a == b => return Ok(base.clone()),
(DataType::Struct(a), DataType::Struct(b)) => {
DataType::Struct(merge_children(a, b, &format!("{path}."))?)
}
(DataType::List(a), DataType::List(b)) => {
DataType::List(Arc::new(merge_field(a, b, &format!("{path}[]"))?))
}
(a, b) => {
return Err(Error::SchemaMismatch(format!(
"field '{path}' has type {a} in the table and {b} in the new rows"
)));
}
};
Ok(base.clone().with_data_type(data_type))
}
pub fn missing_fields(existing: &Fields, merged: &Fields) -> Vec<FieldRef> {
merged
.iter()
.filter_map(|field| {
let Some((_, current)) = existing.find(field.name()) else {
return Some(field.clone());
};
match (current.data_type(), field.data_type()) {
(DataType::Struct(current), DataType::Struct(children)) => {
let missing = missing_fields(current, children);
(!missing.is_empty()).then(|| {
Arc::new(
field
.as_ref()
.clone()
.with_data_type(DataType::Struct(missing.into())),
)
})
}
_ => None,
}
})
.collect()
}
pub fn conform_batch(batch: &RecordBatch, schema: &SchemaRef) -> Result<RecordBatch> {
let columns = schema
.fields()
.iter()
.map(|field| match batch.column_by_name(field.name()) {
Some(column) => conform(column, field),
None => absent(field, batch.num_rows()),
})
.collect::<Result<Vec<_>>>()?;
let options = RecordBatchOptions::new().with_row_count(Some(batch.num_rows()));
Ok(RecordBatch::try_new_with_options(
schema.clone(),
columns,
&options,
)?)
}
pub fn concat_conformed(schema: &SchemaRef, batches: &[RecordBatch]) -> Result<RecordBatch> {
let rows: usize = batches.iter().map(|batch| batch.num_rows()).sum();
let columns = schema
.fields()
.iter()
.map(|field| {
let parts: Vec<Part> = batches
.iter()
.map(|batch| {
(
batch.column_by_name(field.name()).cloned(),
batch.num_rows(),
)
})
.collect();
concat_parts(field, &parts)
})
.collect::<Result<Vec<_>>>()?;
let options = RecordBatchOptions::new().with_row_count(Some(rows));
Ok(RecordBatch::try_new_with_options(
schema.clone(),
columns,
&options,
)?)
}
type Part = (Option<ArrayRef>, usize);
fn concat_parts(field: &Field, parts: &[Part]) -> Result<ArrayRef> {
if let DataType::Struct(children) = field.data_type()
&& !children.is_empty()
{
let columns = children
.iter()
.map(|child| {
let child_parts: Vec<Part> = parts
.iter()
.map(|(array, rows)| {
let column = array
.as_ref()
.and_then(|array| array.as_struct().column_by_name(child.name()));
(column.cloned(), *rows)
})
.collect();
concat_parts(child, &child_parts)
})
.collect::<Result<Vec<_>>>()?;
let mut validity = BooleanBufferBuilder::new(parts.iter().map(|(_, rows)| rows).sum());
for (array, rows) in parts {
match array {
Some(array) => match array.logical_nulls() {
Some(nulls) => validity.append_buffer(nulls.inner()),
None => validity.append_n(*rows, true),
},
None => validity.append_n(*rows, !field.is_nullable()),
}
}
let nulls = NullBuffer::new(validity.finish());
let nulls = (nulls.null_count() > 0).then_some(nulls);
return Ok(Arc::new(StructArray::try_new(
children.clone(),
columns,
nulls,
)?));
}
let mut arrays: Vec<ArrayRef> = Vec::with_capacity(parts.len());
let mut missing = 0;
for (array, rows) in parts {
match array {
Some(array) => {
if missing > 0 {
arrays.push(absent(field, missing)?);
missing = 0;
}
arrays.push(conform(array, field)?);
}
None => missing += rows,
}
}
if missing > 0 || arrays.is_empty() {
arrays.push(absent(field, missing)?);
}
let arrays: Vec<&dyn Array> = arrays.iter().map(|array| array.as_ref()).collect();
Ok(match arrays.as_slice() {
[one] => make_array(one.to_data()),
_ => concat(&arrays)?,
})
}
pub fn used_fields(staged: &Schema, schemas: &[SchemaRef]) -> Schema {
let fields: Vec<FieldRef> = staged
.fields()
.iter()
.filter(|field| {
schemas
.iter()
.any(|schema| schema.column_with_name(field.name()).is_some())
})
.cloned()
.collect();
Schema::new_with_metadata(fields, staged.metadata().clone())
}
pub fn holds_schema(staged: &Schema, new: &Schema) -> bool {
new.fields().iter().all(|field| {
staged
.field_with_name(field.name())
.is_ok_and(|staged| holds(staged.data_type(), field.data_type()))
})
}
fn holds(staged: &DataType, new: &DataType) -> bool {
match (staged, new) {
(DataType::Struct(staged), DataType::Struct(new)) => new.iter().all(|field| {
staged
.find(field.name())
.is_some_and(|(_, staged)| holds(staged.data_type(), field.data_type()))
}),
(DataType::List(staged), DataType::List(new)) => holds(staged.data_type(), new.data_type()),
(staged, new) => staged == new,
}
}
fn conform(array: &ArrayRef, target: &Field) -> Result<ArrayRef> {
if array.data_type() == target.data_type() {
return Ok(array.clone());
}
match (array.data_type(), target.data_type()) {
(DataType::Struct(_), DataType::Struct(fields)) => {
let source = array.as_struct();
let children = fields
.iter()
.map(|field| match source.column_by_name(field.name()) {
Some(child) => conform(child, field),
None => absent(field, source.len()),
})
.collect::<Result<Vec<_>>>()?;
let nulls = source.nulls().cloned();
Ok(Arc::new(StructArray::try_new(
fields.clone(),
children,
nulls,
)?))
}
(DataType::List(_), DataType::List(item)) => {
let source = array.as_list::<i32>();
let values = conform(source.values(), item)?;
let offsets = source.offsets().clone();
let nulls = source.nulls().cloned();
Ok(Arc::new(ListArray::try_new(
item.clone(),
offsets,
values,
nulls,
)?))
}
(from, to) => Err(Error::SchemaMismatch(format!(
"field '{}' has type {from} in the new rows and {to} in the table",
target.name()
))),
}
}
pub fn fill_hidden_nulls(array: &ArrayRef, field: &Field) -> Result<ArrayRef> {
let array = if !field.is_nullable() && array.null_count() > 0 {
let mask = is_null(array.as_ref())?;
zip(&mask, &absent(field, array.len())?, array)?
} else {
array.clone()
};
match field.data_type() {
DataType::Struct(fields) if !fields.is_empty() => {
let source = array.as_struct();
let children = fields
.iter()
.zip(source.columns())
.map(|(field, child)| fill_hidden_nulls(child, field))
.collect::<Result<Vec<_>>>()?;
Ok(Arc::new(StructArray::try_new(
fields.clone(),
children,
source.nulls().cloned(),
)?))
}
DataType::List(item) => {
let source = array.as_list::<i32>();
Ok(Arc::new(ListArray::try_new(
item.clone(),
source.offsets().clone(),
fill_hidden_nulls(source.values(), item)?,
source.nulls().cloned(),
)?))
}
_ => Ok(array),
}
}
pub fn absent(field: &Field, len: usize) -> Result<ArrayRef> {
let nulls = field.is_nullable().then(|| NullBuffer::new_null(len));
match field.data_type() {
DataType::Struct(fields) => {
let children = fields
.iter()
.map(|child| absent(child, len))
.collect::<Result<Vec<_>>>()?;
if fields.is_empty() {
return Ok(Arc::new(StructArray::new_empty_fields(len, nulls)));
}
Ok(Arc::new(StructArray::try_new(
fields.clone(),
children,
nulls,
)?))
}
DataType::List(item) => Ok(Arc::new(ListArray::try_new(
item.clone(),
OffsetBuffer::new_zeroed(len),
new_empty_array(item.data_type()),
nulls,
)?)),
_ if field.is_nullable() => Ok(new_null_array(field.data_type(), len)),
data_type => Err(Error::SchemaMismatch(format!(
"the new rows lack the required field '{}' of type {data_type}",
field.name()
))),
}
}