|
fn parquet_convert_array( |
|
array: ArrayRef, |
|
to_type: &DataType, |
|
parquet_options: &SparkParquetOptions, |
|
) -> DataFusionResult<ArrayRef> { |
|
use DataType::*; |
|
let from_type = array.data_type().clone(); |
|
|
|
let array = match &from_type { |
|
Dictionary(key_type, value_type) |
|
if key_type.as_ref() == &Int32 |
|
&& (value_type.as_ref() == &Utf8 || value_type.as_ref() == &LargeUtf8) => |
|
{ |
|
let dict_array = array |
|
.as_any() |
|
.downcast_ref::<DictionaryArray<Int32Type>>() |
|
.expect("Expected a dictionary array"); |
|
|
|
let casted_dictionary = DictionaryArray::<Int32Type>::new( |
|
dict_array.keys().clone(), |
|
parquet_convert_array(Arc::clone(dict_array.values()), to_type, parquet_options)?, |
|
); |
|
|
|
let casted_result = match to_type { |
|
Dictionary(_, _) => Arc::new(casted_dictionary.clone()), |
|
_ => take(casted_dictionary.values().as_ref(), dict_array.keys(), None)?, |
|
}; |
|
return Ok(casted_result); |
|
} |
|
_ => array, |
|
}; |
|
let from_type = array.data_type(); |
|
|
|
// Try Comet specific handlers first, then arrow-rs cast if supported, |
|
// return uncasted data otherwise |
|
match (from_type, to_type) { |
|
(Struct(_), Struct(_)) => Ok(parquet_convert_struct_to_struct( |
|
array.as_struct(), |
|
from_type, |
|
to_type, |
|
parquet_options, |
|
)?), |
|
(List(_), List(to_inner_type)) => { |
|
let list_arr: &ListArray = array.as_list(); |
|
let cast_field = parquet_convert_array( |
|
Arc::clone(list_arr.values()), |
|
to_inner_type.data_type(), |
|
parquet_options, |
|
)?; |
|
|
|
Ok(Arc::new(ListArray::new( |
|
Arc::clone(to_inner_type), |
|
list_arr.offsets().clone(), |
|
cast_field, |
|
list_arr.nulls().cloned(), |
|
))) |
|
} |
|
(Timestamp(TimeUnit::Microsecond, None), Timestamp(TimeUnit::Microsecond, Some(tz))) => { |
|
Ok(Arc::new( |
|
array |
|
.as_primitive::<TimestampMicrosecondType>() |
|
.reinterpret_cast::<TimestampMicrosecondType>() |
|
.with_timezone(Arc::clone(tz)), |
|
)) |
|
} |
|
(Map(_, ordered_from), Map(_, ordered_to)) if ordered_from == ordered_to => |
|
parquet_convert_map_to_map(array.as_map(), to_type, parquet_options, *ordered_to) |
|
, |
|
// Iceberg stores UUIDs as 16-byte fixed binary but Spark expects string representation. |
|
// Arrow doesn't support casting FixedSizeBinary to Utf8, so we handle it manually. |
|
(FixedSizeBinary(16), Utf8) => { |
|
let binary_array = array |
|
.as_any() |
|
.downcast_ref::<FixedSizeBinaryArray>() |
|
.expect("Expected a FixedSizeBinaryArray"); |
|
|
|
let string_array: StringArray = binary_array |
|
.iter() |
|
.map(|opt_bytes| { |
|
opt_bytes.map(|bytes| { |
|
let uuid = uuid::Uuid::from_bytes( |
|
bytes.try_into().expect("Expected 16 bytes") |
|
); |
|
uuid.to_string() |
|
}) |
|
}) |
|
.collect(); |
|
|
|
Ok(Arc::new(string_array)) |
|
} |
|
// If Arrow cast supports the cast, delegate the cast to Arrow |
|
_ if can_cast_types(from_type, to_type) => { |
|
Ok(cast_with_options(&array, to_type, &PARQUET_OPTIONS)?) |
|
} |
|
_ => Ok(array), |
Describe the bug
When an explicit Parquet read schema requests an ANSI interval type that does not match the file column's logical type, Comet's native scan differs from Spark in two directions:
DATEcolumn requested asINTERVAL YEAR TO MONTHsucceeds in Spark but fails in Comet.INTcolumn requested asINTERVAL DAY TO SECONDfails in Spark but succeeds in Comet, reinterpreting the integers as microseconds.The second case is a correctness gap because Comet returns rows for a read that Spark rejects with
PARQUET_COLUMN_DATA_TYPE_MISMATCH.Steps to reproduce
Write a one-column Parquet file, then read it with an explicit schema. Run each read once with Comet disabled for the Spark reference and once with the native Parquet scan enabled.
The full observed matrix on Spark 4.1 is:
dateinterval year to monthexpected Interval(YearMonth) but found Date32intinterval day to secondPARQUET_COLUMN_DATA_TYPE_MISMATCHtimestampinterval day to secondlonginterval year to monthExpected behavior
Comet should match Spark's
ParquetVectorUpdaterFactorycompatibility rules:DATE, forYearMonthIntervalTypeand interpret the raw value as total months.DayTimeIntervalType.INTforDayTimeIntervalTypewith Spark-compatible schema-mismatch behavior.Additional context
The compatibility decision belongs in the native Parquet schema adapter. Conversion then reaches
parquet_convert_array, whose Arrowcan_cast_typespath accepts numeric-to-Duration, while its fallback can leaveDate32unconverted for anInterval(YearMonth)target.datafusion-comet/native/core/src/parquet/schema_adapter.rs
Lines 773 to 829 in 2963b08
datafusion-comet/native/core/src/parquet/parquet_support.rs
Lines 166 to 260 in 2963b08
This issue is limited to schema-on-read compatibility for ANSI interval targets. Matching-schema interval scans are covered by #5060 / #5161. Add focused coverage beside the existing primitive schema-conversion cases in
ParquetReadSuite.