Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
9 changes: 7 additions & 2 deletions native/core/src/execution/datafusion/planner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

use super::expressions::EvalMode;
use crate::execution::datafusion::expressions::comet_scalar_funcs::create_comet_physical_fun;
use crate::execution::operators::{CopyMode, FilterExec};
use crate::execution::operators::{CopyMode, FilterExec as CometFilterExec};
use crate::{
errors::ExpressionError,
execution::{
Expand Down Expand Up @@ -55,6 +55,7 @@ use datafusion::functions_aggregate::bit_and_or_xor::{bit_and_udaf, bit_or_udaf,
use datafusion::functions_aggregate::min_max::max_udaf;
use datafusion::functions_aggregate::min_max::min_udaf;
use datafusion::functions_aggregate::sum::sum_udaf;
use datafusion::physical_plan::filter::FilterExec;
use datafusion::physical_plan::windows::BoundedWindowAggExec;
use datafusion::physical_plan::InputOrderMode;
use datafusion::{
Expand Down Expand Up @@ -851,7 +852,11 @@ impl PhysicalPlanner {
let predicate =
self.create_expr(filter.predicate.as_ref().unwrap(), child.schema())?;

Ok((scans, Arc::new(FilterExec::try_new(predicate, child)?)))
if can_reuse_input_batch(&child) {
Ok((scans, Arc::new(CometFilterExec::try_new(predicate, child)?)))
} else {
Ok((scans, Arc::new(FilterExec::try_new(predicate, child)?)))
}
}
OpStruct::HashAgg(agg) => {
assert!(children.len() == 1);
Expand Down
85 changes: 52 additions & 33 deletions native/core/src/execution/datafusion/schema_adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@

use arrow::compute::can_cast_types;
use arrow_array::{new_null_array, Array, RecordBatch, RecordBatchOptions};
use arrow_schema::{DataType, Schema, SchemaRef};
use arrow_schema::{DataType, Schema, SchemaRef, TimeUnit};
use datafusion::datasource::schema_adapter::{SchemaAdapter, SchemaAdapterFactory, SchemaMapper};
use datafusion_comet_spark_expr::{spark_cast, EvalMode};
use datafusion_common::plan_err;
use datafusion_common::{plan_err, DataFusionError};
use datafusion_expr::ColumnarValue;
use std::sync::Arc;

Expand All @@ -38,11 +38,11 @@ impl SchemaAdapterFactory for CometSchemaAdapterFactory {
/// schema.
fn create(
&self,
projected_table_schema: SchemaRef,
required_schema: SchemaRef,
table_schema: SchemaRef,
) -> Box<dyn SchemaAdapter> {
Box::new(CometSchemaAdapter {
projected_table_schema,
required_schema,
table_schema,
})
}
Expand All @@ -54,7 +54,7 @@ impl SchemaAdapterFactory for CometSchemaAdapterFactory {
pub struct CometSchemaAdapter {
/// The schema for the table, projected to include only the fields being output (projected) by the
/// associated ParquetExec
projected_table_schema: SchemaRef,
required_schema: SchemaRef,
/// The entire table schema for the table we're using this to adapt.
///
/// This is used to evaluate any filters pushed down into the scan
Expand All @@ -69,7 +69,7 @@ impl SchemaAdapter for CometSchemaAdapter {
///
/// Panics if index is not in range for the table schema
fn map_column_index(&self, index: usize, file_schema: &Schema) -> Option<usize> {
let field = self.projected_table_schema.field(index);
let field = self.required_schema.field(index);
Some(file_schema.fields.find(field.name())?.0)
}

Expand All @@ -87,40 +87,29 @@ impl SchemaAdapter for CometSchemaAdapter {
file_schema: &Schema,
) -> datafusion_common::Result<(Arc<dyn SchemaMapper>, Vec<usize>)> {
let mut projection = Vec::with_capacity(file_schema.fields().len());
let mut field_mappings = vec![None; self.projected_table_schema.fields().len()];
let mut field_mappings = vec![None; self.required_schema.fields().len()];

for (file_idx, file_field) in file_schema.fields.iter().enumerate() {
if let Some((table_idx, table_field)) =
self.projected_table_schema.fields().find(file_field.name())
self.required_schema.fields().find(file_field.name())
{
// workaround for struct casting
match (file_field.data_type(), table_field.data_type()) {
// TODO need to use Comet cast logic to determine which casts are supported,
// but for now just add a hack to support casting between struct types
(DataType::Struct(_), DataType::Struct(_)) => {
field_mappings[table_idx] = Some(projection.len());
projection.push(file_idx);
}
_ => {
if can_cast_types(file_field.data_type(), table_field.data_type()) {
field_mappings[table_idx] = Some(projection.len());
projection.push(file_idx);
} else {
return plan_err!(
"Cannot cast file schema field {} of type {:?} to table schema field of type {:?}",
file_field.name(),
file_field.data_type(),
table_field.data_type()
);
}
}
if comet_can_cast_types(file_field.data_type(), table_field.data_type()) {
field_mappings[table_idx] = Some(projection.len());
projection.push(file_idx);
} else {
return plan_err!(
"Cannot cast file schema field {} of type {:?} to required schema field of type {:?}",
file_field.name(),
file_field.data_type(),
table_field.data_type()
);
}
}
}

Ok((
Arc::new(SchemaMapping {
projected_table_schema: self.projected_table_schema.clone(),
required_schema: self.required_schema.clone(),
field_mappings,
table_schema: self.table_schema.clone(),
}),
Expand Down Expand Up @@ -161,7 +150,7 @@ impl SchemaAdapter for CometSchemaAdapter {
pub struct SchemaMapping {
/// The schema of the table. This is the expected schema after conversion
/// and it should match the schema of the query result.
projected_table_schema: SchemaRef,
required_schema: SchemaRef,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 :)

/// Mapping from field index in `projected_table_schema` to index in
/// projected file_schema.
///
Expand All @@ -185,7 +174,7 @@ impl SchemaMapper for SchemaMapping {
let batch_cols = batch.columns().to_vec();

let cols = self
.projected_table_schema
.required_schema
// go through each field in the projected schema
.fields()
.iter()
Expand All @@ -208,6 +197,7 @@ impl SchemaMapper for SchemaMapping {
EvalMode::Legacy,
"UTC",
false,
true,
)?
.into_array(batch_rows)
},
Expand All @@ -218,7 +208,7 @@ impl SchemaMapper for SchemaMapping {
// Necessary to handle empty batches
let options = RecordBatchOptions::new().with_row_count(Some(batch.num_rows()));

let schema = self.projected_table_schema.clone();
let schema = self.required_schema.clone();
let record_batch = RecordBatch::try_new_with_options(schema, cols, &options)?;
Ok(record_batch)
}
Expand Down Expand Up @@ -259,6 +249,7 @@ impl SchemaMapper for SchemaMapping {
EvalMode::Legacy,
"UTC",
false,
true,
)?
.into_array(batch_col.len())
// and if that works, return the field and column.
Expand All @@ -277,3 +268,31 @@ impl SchemaMapper for SchemaMapping {
Ok(record_batch)
}
}



fn comet_can_cast_types(from_type: &DataType, to_type: &DataType) -> bool {
// TODO this is just a quick hack to get tests passing
match (from_type, to_type) {
(DataType::Struct(_), DataType::Struct(_)) => {
// workaround for struct casting
true
}
// TODO this is maybe no longer needed
(_, DataType::Timestamp(TimeUnit::Nanosecond, _)) => false,
_ => can_cast_types(from_type, to_type),
}
}

pub fn comet_cast(
arg: ColumnarValue,
data_type: &DataType,
eval_mode: EvalMode,
timezone: &str,
allow_incompat: bool,
) -> Result<ColumnarValue, DataFusionError> {
// TODO for now we are re-using the spark cast rules, with a hack to override
// unsupported cases and let those fall through to arrow. This is just a short term
// hack and we need to implement specific Parquet to Spark conversions here instead
spark_cast(arg, data_type, eval_mode, timezone, allow_incompat, true)
}
2 changes: 1 addition & 1 deletion native/core/src/execution/operators/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ impl DisplayAs for FilterExec {

impl ExecutionPlan for FilterExec {
fn name(&self) -> &'static str {
"FilterExec"
"CometFilterExec"
}

/// Return a reference to Any that can be used for downcasting
Expand Down
18 changes: 17 additions & 1 deletion native/spark-expr/src/cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,7 @@ pub fn spark_cast(
eval_mode: EvalMode,
timezone: &str,
allow_incompat: bool,
ugly_hack_for_poc: bool, // TODO we definitely don't want to do this
) -> DataFusionResult<ColumnarValue> {
match arg {
ColumnarValue::Array(array) => Ok(ColumnarValue::Array(cast_array(
Expand All @@ -593,6 +594,7 @@ pub fn spark_cast(
eval_mode,
timezone.to_owned(),
allow_incompat,
ugly_hack_for_poc,
)?)),
ColumnarValue::Scalar(scalar) => {
// Note that normally CAST(scalar) should be fold in Spark JVM side. However, for
Expand All @@ -606,6 +608,7 @@ pub fn spark_cast(
eval_mode,
timezone.to_owned(),
allow_incompat,
ugly_hack_for_poc,
)?,
0,
)?;
Expand All @@ -620,6 +623,7 @@ fn cast_array(
eval_mode: EvalMode,
timezone: String,
allow_incompat: bool,
ugly_hack_for_poc: bool,
) -> DataFusionResult<ArrayRef> {
let array = array_with_timezone(array, timezone.clone(), Some(to_type))?;
let from_type = array.data_type().clone();
Expand All @@ -643,6 +647,7 @@ fn cast_array(
eval_mode,
timezone,
allow_incompat,
ugly_hack_for_poc,
)?,
);

Expand Down Expand Up @@ -723,7 +728,9 @@ fn cast_array(
timezone,
allow_incompat,
)?),
_ if is_datafusion_spark_compatible(from_type, to_type, allow_incompat) => {
_ if ugly_hack_for_poc

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the cases (that we know of) where this gets invoked? (If we know we can replace this flag with an explicit check for those cases, perhaps?)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that we need to implement specific logic for adapting parquet schemas, rather than re-using our Spark-compatible cast. There is likely some overlap, so we can refactor the common code out. For example, regular spark casts do not need to support unsigned integers, but we need this when adapting Parquet schemas.

|| is_datafusion_spark_compatible(from_type, to_type, allow_incompat) =>
{
// use DataFusion cast only when we know that it is compatible with Spark
Ok(cast_with_options(&array, to_type, &CAST_OPTIONS)?)
}
Expand Down Expand Up @@ -840,6 +847,7 @@ fn cast_struct_to_struct(
eval_mode,
timezone.clone(),
allow_incompat,
false,
)?;
cast_fields.push((Arc::clone(&to_fields[i]), cast_field));
}
Expand All @@ -861,6 +869,7 @@ fn casts_struct_to_string(array: &StructArray, timezone: &str) -> DataFusionResu
EvalMode::Legacy,
timezone,
true,
false,
)
.and_then(|cv| cv.into_array(arr.len()))
})
Expand Down Expand Up @@ -1505,6 +1514,7 @@ impl PhysicalExpr for Cast {
self.eval_mode,
&self.timezone,
self.allow_incompat,
false,
)
}

Expand Down Expand Up @@ -2117,6 +2127,7 @@ mod tests {
EvalMode::Legacy,
timezone.clone(),
false,
false,
)?;
assert_eq!(
*result.data_type(),
Expand Down Expand Up @@ -2327,6 +2338,7 @@ mod tests {
EvalMode::Legacy,
"UTC".to_owned(),
false,
false,
);
assert!(result.is_err())
}
Expand All @@ -2340,6 +2352,7 @@ mod tests {
EvalMode::Legacy,
"Not a valid timezone".to_owned(),
false,
false,
);
assert!(result.is_err())
}
Expand All @@ -2364,6 +2377,7 @@ mod tests {
EvalMode::Legacy,
"UTC".to_owned(),
false,
false,
)
.unwrap();
let string_array = string_array.as_string::<i32>();
Expand Down Expand Up @@ -2400,6 +2414,7 @@ mod tests {
EvalMode::Legacy,
"UTC",
false,
false,
)
.unwrap();
if let ColumnarValue::Array(cast_array) = cast_array {
Expand Down Expand Up @@ -2433,6 +2448,7 @@ mod tests {
EvalMode::Legacy,
"UTC",
false,
false,
)
.unwrap();
if let ColumnarValue::Array(cast_array) = cast_array {
Expand Down
1 change: 1 addition & 0 deletions native/spark-expr/src/to_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ fn array_to_json_string(arr: &Arc<dyn Array>, timezone: &str) -> Result<ArrayRef
EvalMode::Legacy,
timezone,
false,
false,
)?
.into_array(arr.len())
}
Expand Down
Loading