use std::fmt;
use std::sync::Arc;
use arrow_array::{ArrayRef, RecordBatch};
use arrow_schema::{DataType, Field as ArrowField, FieldRef, SchemaRef};
use lance_arrow::BLOB_V2_EXT_NAME;
use lance_core::{Error, Result, datatypes::Schema};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FieldTypeMismatch {
pub field_path: String,
pub expected: FieldRef,
pub actual: FieldRef,
}
impl fmt::Display for FieldTypeMismatch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"field `{}` does not match the file schema: expected {}, got {}",
self.field_path,
FieldType(&self.expected),
FieldType(&self.actual)
)
}
}
impl std::error::Error for FieldTypeMismatch {}
struct FieldType<'a>(&'a ArrowField);
impl fmt::Display for FieldType<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0.data_type())?;
match (
self.0.extension_type_name(),
self.0.extension_type_metadata(),
) {
(Some(name), Some(metadata)) => write!(f, " (extension {name}, metadata {metadata})"),
(Some(name), None) => write!(f, " (extension {name})"),
_ => write!(f, " (no extension)"),
}
}
}
pub struct ExpectedTypes {
fields: Vec<FieldRef>,
accepted: Option<SchemaRef>,
}
impl ExpectedTypes {
pub fn new(schema: &Schema) -> Self {
Self {
fields: schema
.fields
.iter()
.map(|field| Arc::new(ArrowField::from(field)))
.collect(),
accepted: None,
}
}
pub fn check_batch(&mut self, batch: &RecordBatch) -> Result<()> {
let batch_schema = batch.schema();
if self.accepted.as_ref() == Some(&batch_schema) {
return Ok(());
}
for (index, expected) in self.fields.iter().enumerate() {
let actual = batch_schema
.fields()
.get(index)
.filter(|actual| actual.name() == expected.name())
.or_else(|| {
let (index, _) = batch_schema.column_with_name(expected.name())?;
batch_schema.fields().get(index)
});
if let Some(actual) = actual {
check_field(expected, actual, true).map_err(into_error)?;
}
}
self.accepted = Some(batch_schema);
Ok(())
}
pub fn check_column(&self, index: usize, array: &ArrayRef) -> Result<()> {
let expected = &self.fields[index];
let actual = Arc::new(ArrowField::new(
expected.name(),
array.data_type().clone(),
true,
));
check_field(expected, &actual, false).map_err(into_error)
}
}
fn into_error(mismatch: FieldTypeMismatch) -> Error {
Error::invalid_input_source(Box::new(mismatch))
}
fn check_field(
expected: &FieldRef,
actual: &FieldRef,
compare_extension: bool,
) -> std::result::Result<(), FieldTypeMismatch> {
let mismatch = || FieldTypeMismatch {
field_path: expected.name().clone(),
expected: expected.clone(),
actual: actual.clone(),
};
if compare_extension
&& (expected.extension_type_name() != actual.extension_type_name()
|| expected.extension_type_metadata() != actual.extension_type_metadata())
{
return Err(mismatch());
}
let children = match (expected.data_type(), actual.data_type()) {
(DataType::Struct(_), DataType::Struct(_))
if expected.extension_type_name() == Some(BLOB_V2_EXT_NAME) =>
{
return Ok(());
}
(DataType::Struct(expected_children), DataType::Struct(actual_children))
if expected_children.len() == actual_children.len() =>
{
expected_children
.iter()
.zip(actual_children.iter())
.collect()
}
(DataType::List(expected_item), DataType::List(actual_item))
| (DataType::LargeList(expected_item), DataType::LargeList(actual_item))
| (DataType::Map(expected_item, _), DataType::Map(actual_item, _)) => {
vec![(expected_item, actual_item)]
}
(
DataType::FixedSizeList(expected_item, expected_size),
DataType::FixedSizeList(actual_item, actual_size),
) if expected_size == actual_size => vec![(expected_item, actual_item)],
(DataType::Utf8, DataType::Utf8View) | (DataType::Binary, DataType::BinaryView) => {
return Ok(());
}
(expected_type, actual_type) if expected_type == actual_type => return Ok(()),
_ => return Err(mismatch()),
};
for (expected_child, actual_child) in children {
check_field(expected_child, actual_child, true).map_err(|mut mismatch| {
mismatch.field_path = format!("{}.{}", expected.name(), mismatch.field_path);
mismatch
})?;
}
Ok(())
}