| // 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. | |
| use std::{ | |
| any::Any, | |
| fmt::{Debug, Display, Formatter}, | |
| hash::{Hash, Hasher}, | |
| sync::Arc, | |
| }; | |
| use arrow::{ | |
| array::{Array, ArrayRef, StructArray, as_struct_array, make_array, new_empty_array}, | |
| datatypes::{DataType, Field, Schema, SchemaRef}, | |
| ffi::{FFI_ArrowArray, FFI_ArrowSchema, from_ffi}, | |
| record_batch::{RecordBatch, RecordBatchOptions}, | |
| }; | |
| use auron_jni_bridge::{ | |
| is_task_running, jni_call, jni_call_static, jni_new_direct_byte_buffer, jni_new_global_ref, | |
| }; | |
| use datafusion::{ | |
| error::Result, | |
| logical_expr::ColumnarValue, | |
| physical_expr::{PhysicalExprRef, physical_exprs_bag_equal}, | |
| physical_expr_common::physical_expr::DynEq, | |
| physical_plan::PhysicalExpr, | |
| }; | |
| use datafusion_ext_commons::{arrow::cast::cast, df_execution_err}; | |
| use jni::objects::GlobalRef; | |
| use once_cell::sync::OnceCell; | |
| pub struct SparkUDFWrapperExpr { | |
| pub serialized: Vec<u8>, | |
| pub return_type: DataType, | |
| pub return_nullable: bool, | |
| pub params: Vec<PhysicalExprRef>, | |
| pub import_schema: SchemaRef, | |
| pub params_schema: OnceCell<SchemaRef>, | |
| jcontext: OnceCell<GlobalRef>, | |
| expr_string: String, | |
| } | |
| impl PartialEq for SparkUDFWrapperExpr { | |
| fn eq(&self, other: &Self) -> bool { | |
| physical_exprs_bag_equal(&self.params, &other.params) | |
| && self.serialized == other.serialized | |
| && self.return_type == other.return_type | |
| && self.return_nullable == other.return_nullable | |
| } | |
| } | |
| impl Eq for SparkUDFWrapperExpr {} | |
| impl SparkUDFWrapperExpr { | |
| pub fn try_new( | |
| serialized: Vec<u8>, | |
| return_type: DataType, | |
| return_nullable: bool, | |
| params: Vec<PhysicalExprRef>, | |
| expr_string: String, | |
| ) -> Result<Self> { | |
| Ok(Self { | |
| serialized, | |
| return_type: return_type.clone(), | |
| return_nullable, | |
| params, | |
| import_schema: Arc::new(Schema::new(vec![Field::new("", return_type, true)])), | |
| params_schema: OnceCell::new(), | |
| jcontext: OnceCell::new(), | |
| expr_string, | |
| }) | |
| } | |
| fn jcontext(&self) -> Result<GlobalRef> { | |
| self.jcontext | |
| .get_or_try_init(|| { | |
| let serialized_buf = jni_new_direct_byte_buffer!(&self.serialized)?; | |
| let jcontext_local = | |
| jni_call_static!(JniBridge.getAuronUDFWrapperContext(serialized_buf.as_obj()) -> JObject)?; | |
| jni_new_global_ref!(jcontext_local.as_obj()) | |
| }) | |
| .cloned() | |
| } | |
| } | |
| impl Display for SparkUDFWrapperExpr { | |
| fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { | |
| Debug::fmt(self, f) | |
| } | |
| } | |
| impl Debug for SparkUDFWrapperExpr { | |
| fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { | |
| write!(f, "UDFWrapper({})", self.expr_string) | |
| } | |
| } | |
| impl Hash for SparkUDFWrapperExpr { | |
| fn hash<H: Hasher>(&self, state: &mut H) { | |
| self.serialized.hash(state); | |
| } | |
| } | |
| impl PhysicalExpr for SparkUDFWrapperExpr { | |
| fn data_type(&self, _input_schema: &Schema) -> Result<DataType> { | |
| Ok(self.return_type.clone()) | |
| } | |
| fn nullable(&self, _input_schema: &Schema) -> Result<bool> { | |
| Ok(self.return_nullable) | |
| } | |
| fn evaluate(&self, batch: &RecordBatch) -> Result<ColumnarValue> { | |
| if !is_task_running() { | |
| df_execution_err!("SparkUDFWrapper: is_task_running=false")?; | |
| } | |
| let batch_schema = batch.schema(); | |
| let num_rows = batch.num_rows(); | |
| if num_rows == 0 { | |
| return Ok(ColumnarValue::Array(new_empty_array(&self.return_type))); | |
| } | |
| // init params schema | |
| let params_schema = self | |
| .params_schema | |
| .get_or_try_init(|| -> Result<SchemaRef> { | |
| let mut param_fields = Vec::with_capacity(self.params.len()); | |
| for param in &self.params { | |
| param_fields.push(Field::new( | |
| "", | |
| param.data_type(batch_schema.as_ref())?, | |
| param.nullable(batch_schema.as_ref())?, | |
| )); | |
| } | |
| Ok(Arc::new(Schema::new(param_fields))) | |
| })?; | |
| // evaluate params | |
| let params: Vec<ArrayRef> = self | |
| .params | |
| .iter() | |
| .zip(params_schema.fields()) | |
| .map(|(param, field)| { | |
| let param_array = param.evaluate(batch).and_then(|r| r.into_array(num_rows))?; | |
| cast(¶m_array, field.data_type()) | |
| }) | |
| .collect::<Result<_>>()?; | |
| let params_batch = RecordBatch::try_new_with_options( | |
| params_schema.clone(), | |
| params, | |
| &RecordBatchOptions::new().with_row_count(Some(num_rows)), | |
| )?; | |
| // invoke UDF through JNI | |
| Ok(ColumnarValue::Array(invoke_udf( | |
| self.jcontext()?, | |
| params_batch, | |
| self.import_schema.clone(), | |
| )?)) | |
| } | |
| fn children(&self) -> Vec<&PhysicalExprRef> { | |
| self.params.iter().collect() | |
| } | |
| fn with_new_children( | |
| self: Arc<Self>, | |
| children: Vec<PhysicalExprRef>, | |
| ) -> Result<PhysicalExprRef> { | |
| Ok(Arc::new(Self::try_new( | |
| self.serialized.clone(), | |
| self.return_type.clone(), | |
| self.return_nullable, | |
| children, | |
| self.expr_string.clone(), | |
| )?)) | |
| } | |
| fn fmt_sql(&self, f: &mut Formatter<'_>) -> std::fmt::Result { | |
| write!(f, "fmt_sql not used") | |
| } | |
| } | |
| fn invoke_udf( | |
| jcontext: GlobalRef, | |
| params_batch: RecordBatch, | |
| result_schema: SchemaRef, | |
| ) -> Result<ArrayRef> { | |
| // evaluate via context | |
| let struct_array = StructArray::from(params_batch); | |
| let mut export_ffi_array = FFI_ArrowArray::new(&struct_array.to_data()); | |
| let mut import_ffi_array = FFI_ArrowArray::empty(); | |
| jni_call!(SparkAuronUDFWrapperContext(jcontext.as_obj()).eval( | |
| &mut export_ffi_array as *mut FFI_ArrowArray as i64, | |
| &mut import_ffi_array as *mut FFI_ArrowArray as i64, | |
| ) -> ())?; | |
| // import output from context | |
| let import_ffi_schema = FFI_ArrowSchema::try_from(result_schema.as_ref())?; | |
| let import_struct_array = | |
| make_array(unsafe { from_ffi(import_ffi_array, &import_ffi_schema)? }); | |
| let import_array = as_struct_array(&import_struct_array).column(0).clone(); | |
| Ok(import_array) | |
| } |