diff --git a/datafusion/expr/src/type_coercion/binary.rs b/datafusion/expr/src/type_coercion/binary.rs index dd9449198796..5141d569558b 100644 --- a/datafusion/expr/src/type_coercion/binary.rs +++ b/datafusion/expr/src/type_coercion/binary.rs @@ -24,7 +24,7 @@ use crate::Operator; use arrow::array::{new_empty_array, Array}; use arrow::compute::can_cast_types; use arrow::datatypes::{ - DataType, Field, TimeUnit, DECIMAL128_MAX_PRECISION, DECIMAL128_MAX_SCALE, + DataType, Field, FieldRef, TimeUnit, DECIMAL128_MAX_PRECISION, DECIMAL128_MAX_SCALE, DECIMAL256_MAX_PRECISION, DECIMAL256_MAX_SCALE, }; @@ -298,6 +298,7 @@ pub fn comparison_coercion(lhs_type: &DataType, rhs_type: &DataType) -> Option Option } } +fn struct_coercion(lhs_type: &DataType, rhs_type: &DataType) -> Option { + use arrow::datatypes::DataType::*; + match (lhs_type, rhs_type) { + (Struct(lhs_fields), Struct(rhs_fields)) => { + let types = lhs_fields + .iter() + .map(|f| f.data_type()) + .zip(rhs_fields.iter().map(|f| f.data_type())) + .map(|(lhs, rhs)| comparison_coercion(lhs, rhs)) + .collect::>>()?; + let fields = types + .into_iter() + .enumerate() + .map(|(i, datatype)| { + Arc::new(Field::new(format!("c{}", i), datatype, true)) + }) + .collect::>(); + Some(Struct(fields.into())) + } + _ => None, + } +} + /// coercion rules for like operations. /// This is a union of string coercion rules and dictionary coercion rules pub fn like_coercion(lhs_type: &DataType, rhs_type: &DataType) -> Option { diff --git a/datafusion/sql/src/expr/mod.rs b/datafusion/sql/src/expr/mod.rs index 27351e10eb34..d83a696f73be 100644 --- a/datafusion/sql/src/expr/mod.rs +++ b/datafusion/sql/src/expr/mod.rs @@ -27,7 +27,10 @@ mod substring; mod unary_op; mod value; +use std::vec; + use crate::planner::{ContextProvider, PlannerContext, SqlToRel}; + use arrow_schema::DataType; use arrow_schema::TimeUnit; use datafusion_common::{ @@ -513,6 +516,8 @@ impl<'a, S: ContextProvider> SqlToRel<'a, S> { self.parse_struct(values, fields, schema, planner_context) } + SQLExpr::Tuple(values) => self.parse_tuple(values, schema, planner_context), + _ => not_impl_err!("Unsupported ast node in sqltorel: {sql:?}"), } } @@ -583,6 +588,25 @@ impl<'a, S: ContextProvider> SqlToRel<'a, S> { ))) } + fn parse_tuple( + &self, + values: Vec, + input_schema: &DFSchema, + planner_context: &mut PlannerContext, + ) -> Result { + if values.is_empty() { + return not_impl_err!("Empty tuple not supported yet"); + } + match values.get(0).unwrap() { + SQLExpr::Identifier(_) | SQLExpr::Value(_) => { + self.parse_struct(values, vec![], input_schema, planner_context) + } + _ => { + not_impl_err!("Only identifiers and literals are supported in tuples") + } + } + } + fn sql_in_list_to_expr( &self, expr: SQLExpr,