blob: 70c957d8c3733166691bfac7d32e90dd2046d408 [file]
// 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::{ffi::c_void, pin::Pin, sync::Arc};
use abi_stable::{
std_types::{RResult, RString, RVec},
StableAbi,
};
use datafusion::{
error::DataFusionError,
execution::{SendableRecordBatchStream, TaskContext},
physical_plan::{DisplayAs, ExecutionPlan, PlanProperties},
};
use datafusion::{error::Result, physical_plan::DisplayFormatType};
use tokio::runtime::Handle;
use crate::{
df_result, plan_properties::FFI_PlanProperties,
record_batch_stream::FFI_RecordBatchStream, rresult,
};
/// A stable struct for sharing a [`ExecutionPlan`] across FFI boundaries.
#[repr(C)]
#[derive(Debug, StableAbi)]
#[allow(non_camel_case_types)]
pub struct FFI_ExecutionPlan {
/// Return the plan properties
pub properties: unsafe extern "C" fn(plan: &Self) -> FFI_PlanProperties,
/// Return a vector of children plans
pub children: unsafe extern "C" fn(plan: &Self) -> RVec<FFI_ExecutionPlan>,
/// Return the plan name.
pub name: unsafe extern "C" fn(plan: &Self) -> RString,
/// Execute the plan and return a record batch stream. Errors
/// will be returned as a string.
pub execute: unsafe extern "C" fn(
plan: &Self,
partition: usize,
) -> RResult<FFI_RecordBatchStream, RString>,
/// Used to create a clone on the provider of the execution plan. This should
/// only need to be called by the receiver of the plan.
pub clone: unsafe extern "C" fn(plan: &Self) -> Self,
/// Release the memory of the private data when it is no longer being used.
pub release: unsafe extern "C" fn(arg: &mut Self),
/// Internal data. This is only to be accessed by the provider of the plan.
/// A [`ForeignExecutionPlan`] should never attempt to access this data.
pub private_data: *mut c_void,
}
unsafe impl Send for FFI_ExecutionPlan {}
unsafe impl Sync for FFI_ExecutionPlan {}
pub struct ExecutionPlanPrivateData {
pub plan: Arc<dyn ExecutionPlan>,
pub context: Arc<TaskContext>,
pub runtime: Option<Handle>,
}
unsafe extern "C" fn properties_fn_wrapper(
plan: &FFI_ExecutionPlan,
) -> FFI_PlanProperties {
let private_data = plan.private_data as *const ExecutionPlanPrivateData;
let plan = &(*private_data).plan;
plan.properties().into()
}
unsafe extern "C" fn children_fn_wrapper(
plan: &FFI_ExecutionPlan,
) -> RVec<FFI_ExecutionPlan> {
let private_data = plan.private_data as *const ExecutionPlanPrivateData;
let plan = &(*private_data).plan;
let ctx = &(*private_data).context;
let runtime = &(*private_data).runtime;
let children: Vec<_> = plan
.children()
.into_iter()
.map(|child| {
FFI_ExecutionPlan::new(Arc::clone(child), Arc::clone(ctx), runtime.clone())
})
.collect();
children.into()
}
unsafe extern "C" fn execute_fn_wrapper(
plan: &FFI_ExecutionPlan,
partition: usize,
) -> RResult<FFI_RecordBatchStream, RString> {
let private_data = plan.private_data as *const ExecutionPlanPrivateData;
let plan = &(*private_data).plan;
let ctx = &(*private_data).context;
let runtime = (*private_data).runtime.clone();
rresult!(plan
.execute(partition, Arc::clone(ctx))
.map(|rbs| FFI_RecordBatchStream::new(rbs, runtime)))
}
unsafe extern "C" fn name_fn_wrapper(plan: &FFI_ExecutionPlan) -> RString {
let private_data = plan.private_data as *const ExecutionPlanPrivateData;
let plan = &(*private_data).plan;
plan.name().into()
}
unsafe extern "C" fn release_fn_wrapper(plan: &mut FFI_ExecutionPlan) {
let private_data = Box::from_raw(plan.private_data as *mut ExecutionPlanPrivateData);
drop(private_data);
}
unsafe extern "C" fn clone_fn_wrapper(plan: &FFI_ExecutionPlan) -> FFI_ExecutionPlan {
let private_data = plan.private_data as *const ExecutionPlanPrivateData;
let plan_data = &(*private_data);
FFI_ExecutionPlan::new(
Arc::clone(&plan_data.plan),
Arc::clone(&plan_data.context),
plan_data.runtime.clone(),
)
}
impl Clone for FFI_ExecutionPlan {
fn clone(&self) -> Self {
unsafe { (self.clone)(self) }
}
}
impl FFI_ExecutionPlan {
/// This function is called on the provider's side.
pub fn new(
plan: Arc<dyn ExecutionPlan>,
context: Arc<TaskContext>,
runtime: Option<Handle>,
) -> Self {
let private_data = Box::new(ExecutionPlanPrivateData {
plan,
context,
runtime,
});
Self {
properties: properties_fn_wrapper,
children: children_fn_wrapper,
name: name_fn_wrapper,
execute: execute_fn_wrapper,
clone: clone_fn_wrapper,
release: release_fn_wrapper,
private_data: Box::into_raw(private_data) as *mut c_void,
}
}
}
impl Drop for FFI_ExecutionPlan {
fn drop(&mut self) {
unsafe { (self.release)(self) }
}
}
/// This struct is used to access an execution plan provided by a foreign
/// library across a FFI boundary.
///
/// The ForeignExecutionPlan is to be used by the caller of the plan, so it has
/// no knowledge or access to the private data. All interaction with the plan
/// must occur through the functions defined in FFI_ExecutionPlan.
#[derive(Debug)]
pub struct ForeignExecutionPlan {
name: String,
plan: FFI_ExecutionPlan,
properties: PlanProperties,
children: Vec<Arc<dyn ExecutionPlan>>,
}
unsafe impl Send for ForeignExecutionPlan {}
unsafe impl Sync for ForeignExecutionPlan {}
impl DisplayAs for ForeignExecutionPlan {
fn fmt_as(
&self,
t: DisplayFormatType,
f: &mut std::fmt::Formatter,
) -> std::fmt::Result {
match t {
DisplayFormatType::Default | DisplayFormatType::Verbose => {
write!(
f,
"FFI_ExecutionPlan: {}, number_of_children={}",
self.name,
self.children.len(),
)
}
DisplayFormatType::TreeRender => {
// TODO: collect info
write!(f, "")
}
}
}
}
impl TryFrom<&FFI_ExecutionPlan> for ForeignExecutionPlan {
type Error = DataFusionError;
fn try_from(plan: &FFI_ExecutionPlan) -> Result<Self, Self::Error> {
unsafe {
let name = (plan.name)(plan).into();
let properties: PlanProperties = (plan.properties)(plan).try_into()?;
let children_rvec = (plan.children)(plan);
let children = children_rvec
.iter()
.map(ForeignExecutionPlan::try_from)
.map(|child| child.map(|c| Arc::new(c) as Arc<dyn ExecutionPlan>))
.collect::<Result<Vec<_>>>()?;
Ok(Self {
name,
plan: plan.clone(),
properties,
children,
})
}
}
}
impl ExecutionPlan for ForeignExecutionPlan {
fn name(&self) -> &str {
&self.name
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn properties(&self) -> &PlanProperties {
&self.properties
}
fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
self.children
.iter()
.map(|p| p as &Arc<dyn ExecutionPlan>)
.collect()
}
fn with_new_children(
self: Arc<Self>,
children: Vec<Arc<dyn ExecutionPlan>>,
) -> Result<Arc<dyn ExecutionPlan>> {
Ok(Arc::new(ForeignExecutionPlan {
plan: self.plan.clone(),
name: self.name.clone(),
children,
properties: self.properties.clone(),
}))
}
fn execute(
&self,
partition: usize,
_context: Arc<TaskContext>,
) -> Result<SendableRecordBatchStream> {
unsafe {
df_result!((self.plan.execute)(&self.plan, partition))
.map(|stream| Pin::new(Box::new(stream)) as SendableRecordBatchStream)
}
}
}
#[cfg(test)]
mod tests {
use arrow::datatypes::{DataType, Field, Schema};
use datafusion::{
physical_plan::{
execution_plan::{Boundedness, EmissionType},
Partitioning,
},
prelude::SessionContext,
};
use super::*;
#[derive(Debug)]
pub struct EmptyExec {
props: PlanProperties,
children: Vec<Arc<dyn ExecutionPlan>>,
}
impl EmptyExec {
pub fn new(schema: arrow::datatypes::SchemaRef) -> Self {
Self {
props: PlanProperties::new(
datafusion::physical_expr::EquivalenceProperties::new(schema),
Partitioning::UnknownPartitioning(3),
EmissionType::Incremental,
Boundedness::Bounded,
),
children: Vec::default(),
}
}
}
impl DisplayAs for EmptyExec {
fn fmt_as(
&self,
_t: DisplayFormatType,
_f: &mut std::fmt::Formatter,
) -> std::fmt::Result {
unimplemented!()
}
}
impl ExecutionPlan for EmptyExec {
fn name(&self) -> &'static str {
"empty-exec"
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn properties(&self) -> &PlanProperties {
&self.props
}
fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
self.children.iter().collect()
}
fn with_new_children(
self: Arc<Self>,
children: Vec<Arc<dyn ExecutionPlan>>,
) -> Result<Arc<dyn ExecutionPlan>> {
Ok(Arc::new(EmptyExec {
props: self.props.clone(),
children,
}))
}
fn execute(
&self,
_partition: usize,
_context: Arc<TaskContext>,
) -> Result<SendableRecordBatchStream> {
unimplemented!()
}
fn statistics(&self) -> Result<datafusion::common::Statistics> {
unimplemented!()
}
}
#[test]
fn test_round_trip_ffi_execution_plan() -> Result<()> {
let schema =
Arc::new(Schema::new(vec![Field::new("a", DataType::Float32, false)]));
let ctx = SessionContext::new();
let original_plan = Arc::new(EmptyExec::new(schema));
let original_name = original_plan.name().to_string();
let local_plan = FFI_ExecutionPlan::new(original_plan, ctx.task_ctx(), None);
let foreign_plan: ForeignExecutionPlan = (&local_plan).try_into()?;
assert!(original_name == foreign_plan.name());
let display = datafusion::physical_plan::display::DisplayableExecutionPlan::new(
&foreign_plan,
);
let buf = display.one_line().to_string();
assert_eq!(
buf.trim(),
"FFI_ExecutionPlan: empty-exec, number_of_children=0"
);
Ok(())
}
#[test]
fn test_ffi_execution_plan_children() -> Result<()> {
let schema =
Arc::new(Schema::new(vec![Field::new("a", DataType::Float32, false)]));
let ctx = SessionContext::new();
// Version 1: Adding child to the foreign plan
let child_plan = Arc::new(EmptyExec::new(Arc::clone(&schema)));
let child_local = FFI_ExecutionPlan::new(child_plan, ctx.task_ctx(), None);
let child_foreign = Arc::new(ForeignExecutionPlan::try_from(&child_local)?);
let parent_plan = Arc::new(EmptyExec::new(Arc::clone(&schema)));
let parent_local = FFI_ExecutionPlan::new(parent_plan, ctx.task_ctx(), None);
let parent_foreign = Arc::new(ForeignExecutionPlan::try_from(&parent_local)?);
assert_eq!(parent_foreign.children().len(), 0);
assert_eq!(child_foreign.children().len(), 0);
let parent_foreign = parent_foreign.with_new_children(vec![child_foreign])?;
assert_eq!(parent_foreign.children().len(), 1);
// Version 2: Adding child to the local plan
let child_plan = Arc::new(EmptyExec::new(Arc::clone(&schema)));
let child_local = FFI_ExecutionPlan::new(child_plan, ctx.task_ctx(), None);
let child_foreign = Arc::new(ForeignExecutionPlan::try_from(&child_local)?);
let parent_plan = Arc::new(EmptyExec::new(Arc::clone(&schema)));
let parent_plan = parent_plan.with_new_children(vec![child_foreign])?;
let parent_local = FFI_ExecutionPlan::new(parent_plan, ctx.task_ctx(), None);
let parent_foreign = Arc::new(ForeignExecutionPlan::try_from(&parent_local)?);
assert_eq!(parent_foreign.children().len(), 1);
Ok(())
}
}