diff --git a/datafusion/functions/src/core/mod.rs b/datafusion/functions/src/core/mod.rs index a14d563737240..b9967493ee10b 100644 --- a/datafusion/functions/src/core/mod.rs +++ b/datafusion/functions/src/core/mod.rs @@ -39,6 +39,7 @@ pub mod r#struct; pub mod union_extract; pub mod union_tag; pub mod version; +pub mod operators; // create UDFs make_udf_function!(arrow_cast::ArrowCastFunc, arrow_cast); @@ -57,6 +58,7 @@ make_udf_function!(union_extract::UnionExtractFun, union_extract); make_udf_function!(union_tag::UnionTagFunc, union_tag); make_udf_function!(version::VersionFunc, version); make_udf_function!(arrow_metadata::ArrowMetadataFunc, arrow_metadata); +make_udf_function!(operators::bitwise_xor::BitwiseXorFunc, bitwise_xor); pub mod expr_fn { use datafusion_expr::{Expr, Literal}; @@ -160,6 +162,7 @@ pub fn functions() -> Vec> { union_extract(), union_tag(), version(), + bitwise_xor(), r#struct(), ] } diff --git a/datafusion/functions/src/core/operators/bitwise_xor.rs b/datafusion/functions/src/core/operators/bitwise_xor.rs new file mode 100644 index 0000000000000..1861ee4c2f7c6 --- /dev/null +++ b/datafusion/functions/src/core/operators/bitwise_xor.rs @@ -0,0 +1,181 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//! Operator bitwise xor: `#` `^` + +use std::any::Any; + +use crate::core::operators::common::to_result_type_array; +use arrow::array::*; +use arrow::compute::kernels::bitwise::{bitwise_xor, bitwise_xor_scalar}; +use arrow::datatypes::DataType; +use datafusion_common::plan_err; +use datafusion_common::{Result, ScalarValue, utils::take_function_args}; +use datafusion_expr::{ + ColumnarValue, Operator, ScalarFunctionArgs, ScalarUDFImpl, Signature, Volatility, +}; +use std::sync::Arc; + +create_left_integral_dyn_scalar_kernel!(bitwise_xor_dyn_scalar, bitwise_xor_scalar); +create_left_integral_dyn_kernel!(bitwise_xor_dyn, bitwise_xor); + +#[derive(Debug, PartialEq, Eq, Hash)] +pub struct BitwiseXorFunc { + signature: Signature, +} + +impl BitwiseXorFunc { + pub fn new() -> Self { + Self { + signature: Signature::uniform( + 2, + vec![ + DataType::Int8, + DataType::Int16, + DataType::Int32, + DataType::Int64, + DataType::UInt8, + DataType::UInt16, + DataType::UInt32, + DataType::UInt64, + ], + Volatility::Immutable, + ), + } + } +} + +impl Default for BitwiseXorFunc { + fn default() -> Self { + Self::new() + } +} + +impl ScalarUDFImpl for BitwiseXorFunc { + fn as_any(&self) -> &dyn Any { + self + } + + fn name(&self) -> &str { + "bitwise_xor" + } + + fn signature(&self) -> &Signature { + &self.signature + } + + fn return_type(&self, arg_types: &[DataType]) -> Result { + Ok(arg_types[0].clone()) + } + + fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result { + let [lhs, rhs] = take_function_args(self.name(), &args.args)?; + + if let (ColumnarValue::Array(array), ColumnarValue::Scalar(scalar)) = (&lhs, &rhs) + && let Some(res) = bitwise_xor_dyn_scalar(array, scalar.clone()) + { + return res + .and_then(|a| { + to_result_type_array(&Operator::BitwiseXor, a, &lhs.data_type()) + }) + .map(ColumnarValue::Array); + } + + let left = lhs.to_array(args.number_rows)?; + let right = rhs.to_array(args.number_rows)?; + bitwise_xor_dyn(left, right).map(ColumnarValue::Array) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use arrow::datatypes::Field; + use datafusion_common::{Result, ScalarValue, config::ConfigOptions}; + + #[test] + fn bitwise_array_test() -> Result<()> { + let left = Arc::new(Int32Array::from(vec![Some(12), None, Some(11)])) as ArrayRef; + let right = + Arc::new(Int32Array::from(vec![Some(1), Some(3), Some(7)])) as ArrayRef; + let result = bitwise_xor_dyn(Arc::clone(&left), Arc::clone(&right))?; + let expected = Int32Array::from(vec![Some(13), None, Some(12)]); + assert_eq!(result.as_ref(), &expected); + + let left = Arc::new(UInt32Array::from(vec![Some(12), None, Some(11)])) as ArrayRef; + let right = + Arc::new(UInt32Array::from(vec![Some(1), Some(3), Some(7)])) as ArrayRef; + let result = bitwise_xor_dyn(Arc::clone(&left), Arc::clone(&right))?; + let expected = UInt32Array::from(vec![Some(13), None, Some(12)]); + assert_eq!(result.as_ref(), &expected); + Ok(()) + } + + #[test] + fn bitwise_scalar_test() -> Result<()> { + let left = Arc::new(Int32Array::from(vec![Some(12), None, Some(11)])) as ArrayRef; + let right = ScalarValue::from(3i32); + let result = bitwise_xor_dyn_scalar(&left, right).unwrap()?; + let expected = Int32Array::from(vec![Some(15), None, Some(8)]); + assert_eq!(result.as_ref(), &expected); + + let left = Arc::new(UInt32Array::from(vec![Some(12), None, Some(11)])) as ArrayRef; + let right = ScalarValue::from(3u32); + let result = bitwise_xor_dyn_scalar(&left, right).unwrap()?; + let expected = UInt32Array::from(vec![Some(15), None, Some(8)]); + assert_eq!(result.as_ref(), &expected); + + Ok(()) + } + + #[test] + fn test_bitwise_xor_scalar_null() -> Result<()> { + // Test with null scalar + let array = UInt32Array::from(vec![1, 2, 3]); + let scalar = ScalarValue::UInt32(None); + let result = bitwise_xor_dyn_scalar(&array, scalar).unwrap()?; + assert_eq!(result.len(), 3); + assert!(result.is_null(0)); + assert!(result.is_null(1)); + assert!(result.is_null(2)); + Ok(()) + } + + #[test] + fn test_bitwise_xor_func_invoke() -> Result<()> { + // Test the ScalarUDFImpl invoke_with_args method + let func = BitwiseXorFunc::new(); + let left = ColumnarValue::Array(Arc::new(Int32Array::from(vec![10, 20, 30])) as ArrayRef); + let right = ColumnarValue::Array(Arc::new(Int32Array::from(vec![30, 20, 10])) as ArrayRef); + let args = ScalarFunctionArgs { + args: vec![left, right], + arg_fields: vec![Field::new("a", DataType::Int32, false).into(), Field::new("b", DataType::Int32, false).into()], + number_rows: 3, + return_field: Field::new("f", DataType::Int32, false).into(), + config_options: Arc::new(ConfigOptions::new()), + }; + let result = func.invoke_with_args(args)?; + match result { + ColumnarValue::Array(arr) => { + let arr = arr.as_any().downcast_ref::().unwrap(); + assert_eq!(arr.values(), &[10 ^ 30, 20 ^ 20, 30 ^ 10]); + } + _ => panic!("Expected array result"), + } + Ok(()) + } +} diff --git a/datafusion/functions/src/core/operators/common.rs b/datafusion/functions/src/core/operators/common.rs new file mode 100644 index 0000000000000..902cb320a8e91 --- /dev/null +++ b/datafusion/functions/src/core/operators/common.rs @@ -0,0 +1,213 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//! Common utilities for operators. +// copy from physical-expr/src/expressions/binary/kernels.rs + +use arrow::array::*; +use arrow::compute::cast; +use arrow::datatypes::*; +use datafusion_common::{Result, internal_err}; + +use datafusion_expr::Operator; + +/// Downcasts $LEFT and $RIGHT to $ARRAY_TYPE and then calls $KERNEL($LEFT, $RIGHT) +macro_rules! call_kernel { + ($LEFT:expr, $RIGHT:expr, $KERNEL:expr, $ARRAY_TYPE:ident) => {{ + let left = $LEFT.as_any().downcast_ref::<$ARRAY_TYPE>().unwrap(); + let right = $RIGHT.as_any().downcast_ref::<$ARRAY_TYPE>().unwrap(); + let result: $ARRAY_TYPE = $KERNEL(left, right)?; + Ok(Arc::new(result)) + }}; +} + +/// Creates a $FUNC(left: ArrayRef, right: ArrayRef) that +/// downcasts left / right to the appropriate integral type and calls the kernel +macro_rules! create_left_integral_dyn_kernel { + ($FUNC:ident, $KERNEL:ident) => { + pub(crate) fn $FUNC(left: ArrayRef, right: ArrayRef) -> Result { + match &left.data_type() { + DataType::Int8 => { + call_kernel!(left, right, $KERNEL, Int8Array) + } + DataType::Int16 => { + call_kernel!(left, right, $KERNEL, Int16Array) + } + DataType::Int32 => { + call_kernel!(left, right, $KERNEL, Int32Array) + } + DataType::Int64 => { + call_kernel!(left, right, $KERNEL, Int64Array) + } + DataType::UInt8 => { + call_kernel!(left, right, $KERNEL, UInt8Array) + } + DataType::UInt16 => { + call_kernel!(left, right, $KERNEL, UInt16Array) + } + DataType::UInt32 => { + call_kernel!(left, right, $KERNEL, UInt32Array) + } + DataType::UInt64 => { + call_kernel!(left, right, $KERNEL, UInt64Array) + } + other => plan_err!( + "Data type {} not supported for binary operation '{}' on dyn arrays", + other, + stringify!($KERNEL) + ), + } + } + }; +} + +/// Downcasts $LEFT as $ARRAY_TYPE and $RIGHT as TYPE and calls $KERNEL($LEFT, $RIGHT) +macro_rules! call_scalar_kernel { + ($LEFT:expr, $RIGHT:expr, $KERNEL:ident, $ARRAY_TYPE:ident, $TYPE:ty) => {{ + let len = $LEFT.len(); + let array = $LEFT.as_any().downcast_ref::<$ARRAY_TYPE>().unwrap(); + let scalar = $RIGHT; + if scalar.is_null() { + Ok(new_null_array(array.data_type(), len)) + } else { + let scalar: $TYPE = scalar.try_into().unwrap(); + let result: $ARRAY_TYPE = $KERNEL(array, scalar).unwrap(); + Ok(Arc::new(result) as ArrayRef) + } + }}; +} + +/// Creates a $FUNC(left: ArrayRef, right: ScalarValue) that +/// downcasts left / right to the appropriate integral type and calls the kernel +macro_rules! create_left_integral_dyn_scalar_kernel { + ($FUNC:ident, $KERNEL:ident) => { + pub(crate) fn $FUNC( + array: &dyn Array, + scalar: ScalarValue, + ) -> Option> { + let result = match array.data_type() { + DataType::Int8 => { + call_scalar_kernel!(array, scalar, $KERNEL, Int8Array, i8) + } + DataType::Int16 => { + call_scalar_kernel!(array, scalar, $KERNEL, Int16Array, i16) + } + DataType::Int32 => { + call_scalar_kernel!(array, scalar, $KERNEL, Int32Array, i32) + } + DataType::Int64 => { + call_scalar_kernel!(array, scalar, $KERNEL, Int64Array, i64) + } + DataType::UInt8 => { + call_scalar_kernel!(array, scalar, $KERNEL, UInt8Array, u8) + } + DataType::UInt16 => { + call_scalar_kernel!(array, scalar, $KERNEL, UInt16Array, u16) + } + DataType::UInt32 => { + call_scalar_kernel!(array, scalar, $KERNEL, UInt32Array, u32) + } + DataType::UInt64 => { + call_scalar_kernel!(array, scalar, $KERNEL, UInt64Array, u64) + } + other => plan_err!( + "Data type {} not supported for binary operation '{}' on dyn arrays", + other, + stringify!($KERNEL) + ), + }; + Some(result) + } + }; +} + +/// Casts dictionary array to result type for binary numerical operators. Such operators +/// between array and scalar produce a dictionary array other than primitive array of the +/// same operators between array and array. This leads to inconsistent result types causing +/// errors in the following query execution. For such operators between array and scalar, +/// we cast the dictionary array to primitive array. +pub fn to_result_type_array( + op: &Operator, + array: ArrayRef, + result_type: &DataType, +) -> Result { + if array.data_type() == result_type { + Ok(array) + } else if op.is_numerical_operators() { + match array.data_type() { + DataType::Dictionary(_, value_type) => { + if value_type.as_ref() == result_type { + Ok(cast(&array, result_type)?) + } else { + internal_err!( + "Incompatible Dictionary value type {value_type} with result type {result_type} of Binary operator {op:?}" + ) + } + } + _ => Ok(array), + } + } else { + Ok(array) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::Arc; + use datafusion_expr::Operator; + use arrow::array::{Int8Array, Int32Array, ArrayRef, DictionaryArray}; + + #[test] + fn test_to_result_type_array() { + let values = Arc::new(Int32Array::from(vec![1, 2, 3, 4])); + let keys = Int8Array::from(vec![Some(0), None, Some(2), Some(3)]); + let dictionary = + Arc::new(DictionaryArray::try_new(keys, values).unwrap()) as ArrayRef; + + // Casting Dictionary to Int32 + let casted = to_result_type_array( + &Operator::Plus, + Arc::clone(&dictionary), + &DataType::Int32, + ) + .unwrap(); + assert_eq!( + &casted, + &(Arc::new(Int32Array::from(vec![Some(1), None, Some(3), Some(4)])) + as ArrayRef) + ); + + // Array has same datatype as result type, no casting + let casted = to_result_type_array( + &Operator::Plus, + Arc::clone(&dictionary), + dictionary.data_type(), + ) + .unwrap(); + assert_eq!(&casted, &dictionary); + + // Not numerical operator, no casting + let casted = to_result_type_array( + &Operator::Eq, + Arc::clone(&dictionary), + &DataType::Int32, + ) + .unwrap(); + assert_eq!(&casted, &dictionary); + } +} \ No newline at end of file diff --git a/datafusion/functions/src/core/operators/mod.rs b/datafusion/functions/src/core/operators/mod.rs new file mode 100644 index 0000000000000..cb9023e2fb7b7 --- /dev/null +++ b/datafusion/functions/src/core/operators/mod.rs @@ -0,0 +1,22 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//! Counterpart functions of operators + +#[macro_use] +mod common; +pub mod bitwise_xor; \ No newline at end of file diff --git a/datafusion/physical-expr/src/expressions/binary.rs b/datafusion/physical-expr/src/expressions/binary.rs index 72eae396e68a6..ba18eed84b8ff 100644 --- a/datafusion/physical-expr/src/expressions/binary.rs +++ b/datafusion/physical-expr/src/expressions/binary.rs @@ -45,7 +45,7 @@ use datafusion_physical_expr_common::datum::{apply, apply_cmp}; use kernels::{ bitwise_and_dyn, bitwise_and_dyn_scalar, bitwise_or_dyn, bitwise_or_dyn_scalar, bitwise_shift_left_dyn, bitwise_shift_left_dyn_scalar, bitwise_shift_right_dyn, - bitwise_shift_right_dyn_scalar, bitwise_xor_dyn, bitwise_xor_dyn_scalar, + bitwise_shift_right_dyn_scalar, concat_elements_utf8view, regex_match_dyn, regex_match_dyn_scalar, }; @@ -657,7 +657,6 @@ impl BinaryExpr { RegexNotIMatch => regex_match_dyn_scalar(array, &scalar, true, true), BitwiseAnd => bitwise_and_dyn_scalar(array, scalar), BitwiseOr => bitwise_or_dyn_scalar(array, scalar), - BitwiseXor => bitwise_xor_dyn_scalar(array, scalar), BitwiseShiftRight => bitwise_shift_right_dyn_scalar(array, scalar), BitwiseShiftLeft => bitwise_shift_left_dyn_scalar(array, scalar), // if scalar operation is not supported - fallback to array implementation @@ -709,13 +708,12 @@ impl BinaryExpr { RegexNotIMatch => regex_match_dyn(&left, &right, true, true), BitwiseAnd => bitwise_and_dyn(left, right), BitwiseOr => bitwise_or_dyn(left, right), - BitwiseXor => bitwise_xor_dyn(left, right), BitwiseShiftRight => bitwise_shift_right_dyn(left, right), BitwiseShiftLeft => bitwise_shift_left_dyn(left, right), StringConcat => concat_elements(&left, &right), AtArrow | ArrowAt | Arrow | LongArrow | HashArrow | HashLongArrow | AtAt | HashMinus | AtQuestion | Question | QuestionAnd | QuestionPipe - | IntegerDivide => { + | IntegerDivide | BitwiseXor => { not_impl_err!( "Binary operator '{:?}' is not supported in the physical expr", self.op @@ -1535,30 +1533,6 @@ mod tests { DataType::UInt64, [11u64, 6u64, 7u64], ); - test_coercion!( - Int16Array, - DataType::Int16, - vec![3i16, 2i16, 3i16], - Int64Array, - DataType::Int64, - vec![10i64, 6i64, 5i64], - Operator::BitwiseXor, - Int64Array, - DataType::Int64, - [9i64, 4i64, 6i64], - ); - test_coercion!( - UInt16Array, - DataType::UInt16, - vec![3u16, 2u16, 3u16], - UInt64Array, - DataType::UInt64, - vec![10u64, 6u64, 5u64], - Operator::BitwiseXor, - UInt64Array, - DataType::UInt64, - [9u64, 4u64, 6u64], - ); test_coercion!( Int16Array, DataType::Int16, @@ -4267,10 +4241,6 @@ mod tests { let expected = Int32Array::from(vec![Some(13), None, Some(15)]); assert_eq!(result.as_ref(), &expected); - result = bitwise_xor_dyn(Arc::clone(&left), Arc::clone(&right))?; - let expected = Int32Array::from(vec![Some(13), None, Some(12)]); - assert_eq!(result.as_ref(), &expected); - let left = Arc::new(UInt32Array::from(vec![Some(12), None, Some(11)])) as ArrayRef; let right = @@ -4283,10 +4253,6 @@ mod tests { let expected = UInt32Array::from(vec![Some(13), None, Some(15)]); assert_eq!(result.as_ref(), &expected); - result = bitwise_xor_dyn(Arc::clone(&left), Arc::clone(&right))?; - let expected = UInt32Array::from(vec![Some(13), None, Some(12)]); - assert_eq!(result.as_ref(), &expected); - Ok(()) } @@ -4349,10 +4315,6 @@ mod tests { let expected = Int32Array::from(vec![Some(15), None, Some(11)]); assert_eq!(result.as_ref(), &expected); - result = bitwise_xor_dyn_scalar(&left, right).unwrap()?; - let expected = Int32Array::from(vec![Some(15), None, Some(8)]); - assert_eq!(result.as_ref(), &expected); - let left = Arc::new(UInt32Array::from(vec![Some(12), None, Some(11)])) as ArrayRef; let right = ScalarValue::from(3u32); @@ -4364,9 +4326,6 @@ mod tests { let expected = UInt32Array::from(vec![Some(15), None, Some(11)]); assert_eq!(result.as_ref(), &expected); - result = bitwise_xor_dyn_scalar(&left, right).unwrap()?; - let expected = UInt32Array::from(vec![Some(15), None, Some(8)]); - assert_eq!(result.as_ref(), &expected); Ok(()) } diff --git a/datafusion/physical-expr/src/expressions/binary/kernels.rs b/datafusion/physical-expr/src/expressions/binary/kernels.rs index 39e3a6f16b5cf..a09ac9e8ae84c 100644 --- a/datafusion/physical-expr/src/expressions/binary/kernels.rs +++ b/datafusion/physical-expr/src/expressions/binary/kernels.rs @@ -21,7 +21,6 @@ use arrow::array::*; use arrow::compute::kernels::bitwise::{ bitwise_and, bitwise_and_scalar, bitwise_or, bitwise_or_scalar, bitwise_shift_left, bitwise_shift_left_scalar, bitwise_shift_right, bitwise_shift_right_scalar, - bitwise_xor, bitwise_xor_scalar, }; use arrow::compute::kernels::boolean::not; use arrow::compute::kernels::comparison::{regexp_is_match, regexp_is_match_scalar}; @@ -83,7 +82,7 @@ macro_rules! create_left_integral_dyn_kernel { } create_left_integral_dyn_kernel!(bitwise_or_dyn, bitwise_or); -create_left_integral_dyn_kernel!(bitwise_xor_dyn, bitwise_xor); +// create_left_integral_dyn_kernel!(bitwise_xor_dyn, bitwise_xor); create_left_integral_dyn_kernel!(bitwise_and_dyn, bitwise_and); create_left_integral_dyn_kernel!(bitwise_shift_right_dyn, bitwise_shift_right); create_left_integral_dyn_kernel!(bitwise_shift_left_dyn, bitwise_shift_left); @@ -150,7 +149,7 @@ macro_rules! create_left_integral_dyn_scalar_kernel { create_left_integral_dyn_scalar_kernel!(bitwise_and_dyn_scalar, bitwise_and_scalar); create_left_integral_dyn_scalar_kernel!(bitwise_or_dyn_scalar, bitwise_or_scalar); -create_left_integral_dyn_scalar_kernel!(bitwise_xor_dyn_scalar, bitwise_xor_scalar); +// create_left_integral_dyn_scalar_kernel!(bitwise_xor_dyn_scalar, bitwise_xor_scalar); create_left_integral_dyn_scalar_kernel!( bitwise_shift_right_dyn_scalar, bitwise_shift_right_scalar diff --git a/datafusion/sql/src/expr/mod.rs b/datafusion/sql/src/expr/mod.rs index dbf2ce67732ec..591057b04d3ba 100644 --- a/datafusion/sql/src/expr/mod.rs +++ b/datafusion/sql/src/expr/mod.rs @@ -15,7 +15,10 @@ // specific language governing permissions and limitations // under the License. +use std::sync::Arc; + use arrow::datatypes::{DataType, TimeUnit}; +use datafusion_expr::ScalarUDF; use datafusion_expr::planner::{ PlannerResult, RawBinaryExpr, RawDictionaryExpr, RawFieldAccessExpr, }; @@ -140,6 +143,14 @@ impl SqlToRel<'_, S> { } let RawBinaryExpr { op, left, right } = binary_expr; + + // This is part of the ongoing effort to migrate binary operators to the UDF framework. + // See https://github.com/apache/datafusion/issues/20018 for more details. + if let Some(fm) = self.rewrite_operator_to_function(&op) { + let inner = ScalarFunction::new_udf(fm, vec![left, right]); + return Ok(Expr::ScalarFunction(inner)) + } + Ok(Expr::BinaryExpr(BinaryExpr::new( Box::new(left), self.parse_sql_binary_op(&op)?, @@ -176,6 +187,20 @@ impl SqlToRel<'_, S> { Ok(expr) } + /// This is part of the ongoing effort to migrate binary operators to the UDF framework. + /// See https://github.com/apache/datafusion/issues/20018 for more details. + fn rewrite_operator_to_function(&self, op: &BinaryOperator) -> Option> { + let expr_op = if let Ok(op) = self.parse_sql_binary_op(&op) { + op + } else { + return None; + }; + match expr_op { + Operator::BitwiseXor => self.context_provider.get_function_meta("bitwise_xor"), + _ => None, + } + } + /// Rewrite aliases which are not-complete (e.g. ones that only include only table qualifier in a schema.table qualified relation) fn rewrite_partial_qualifier(&self, expr: Expr, schema: &DFSchema) -> Expr { match expr {