use std::ffi::c_void;
use std::sync::Arc;
use async_ffi::{FfiFuture, FutureExt};
use async_trait::async_trait;
use datafusion_common::error::{DataFusionError, Result};
use datafusion_expr::LogicalPlan;
use datafusion_physical_plan::ExecutionPlan;
use datafusion_proto::bytes::{
logical_plan_from_bytes_with_extension_codec,
logical_plan_to_bytes_with_extension_codec,
physical_plan_from_bytes_with_extension_codec,
physical_plan_to_bytes_with_extension_codec,
};
use datafusion_proto::logical_plan::LogicalExtensionCodec;
use datafusion_proto::physical_plan::PhysicalExtensionCodec;
use datafusion_session::{QueryPlanner, Session};
use stabby::vec::Vec as SVec;
use tokio::runtime::Handle;
use crate::execution::FFI_TaskContextProvider;
use crate::proto::logical_extension_codec::FFI_LogicalExtensionCodec;
use crate::proto::physical_extension_codec::FFI_PhysicalExtensionCodec;
use crate::session::{FFI_SessionRef, ForeignSession};
use crate::util::FFI_Result;
use crate::{df_result, sresult_return};
#[repr(C)]
#[derive(Debug)]
pub struct FFI_QueryPlanner {
create_physical_plan: unsafe extern "C" fn(
&Self,
logical_plan_serialized: SVec<u8>,
session: FFI_SessionRef,
) -> FfiFuture<FFI_Result<SVec<u8>>>,
logical_codec: FFI_LogicalExtensionCodec,
physical_codec: FFI_PhysicalExtensionCodec,
clone: unsafe extern "C" fn(planner: &Self) -> Self,
release: unsafe extern "C" fn(arg: &mut Self),
pub version: unsafe extern "C" fn() -> u64,
private_data: *mut c_void,
pub library_marker_id: extern "C" fn() -> usize,
}
unsafe impl Send for FFI_QueryPlanner {}
unsafe impl Sync for FFI_QueryPlanner {}
struct QueryPlannerPrivateData {
planner: Arc<dyn QueryPlanner + Send + Sync>,
}
impl FFI_QueryPlanner {
fn inner(&self) -> &Arc<dyn QueryPlanner + Send + Sync> {
let private_data = self.private_data as *const QueryPlannerPrivateData;
unsafe { &(*private_data).planner }
}
}
unsafe extern "C" fn create_physical_plan_fn_wrapper(
planner: &FFI_QueryPlanner,
logical_plan_serialized: SVec<u8>,
session: FFI_SessionRef,
) -> FfiFuture<FFI_Result<SVec<u8>>> {
let internal_planner = Arc::clone(planner.inner());
let logical_codec: Arc<dyn LogicalExtensionCodec> = (&planner.logical_codec).into();
let physical_codec: Arc<dyn PhysicalExtensionCodec> =
(&planner.physical_codec).into();
async move {
let mut foreign_session = None;
let session = sresult_return!(
session
.as_local()
.map(Ok::<&dyn Session, DataFusionError>)
.unwrap_or_else(|| {
foreign_session = Some(ForeignSession::try_from(&session)?);
Ok(foreign_session.as_ref().unwrap())
})
);
let logical_plan = sresult_return!(logical_plan_from_bytes_with_extension_codec(
logical_plan_serialized.as_slice(),
session.task_ctx().as_ref(),
logical_codec.as_ref(),
));
let physical_plan = sresult_return!(
internal_planner
.create_physical_plan(&logical_plan, session)
.await
);
let physical_plan = sresult_return!(physical_plan_to_bytes_with_extension_codec(
physical_plan,
physical_codec.as_ref(),
));
FFI_Result::Ok(SVec::from(physical_plan.as_ref()))
}
.into_ffi()
}
unsafe extern "C" fn release_fn_wrapper(planner: &mut FFI_QueryPlanner) {
unsafe {
debug_assert!(!planner.private_data.is_null());
let private_data =
Box::from_raw(planner.private_data as *mut QueryPlannerPrivateData);
drop(private_data);
planner.private_data = std::ptr::null_mut();
}
}
unsafe extern "C" fn clone_fn_wrapper(planner: &FFI_QueryPlanner) -> FFI_QueryPlanner {
let old_planner = Arc::clone(planner.inner());
let private_data = Box::into_raw(Box::new(QueryPlannerPrivateData {
planner: old_planner,
})) as *mut c_void;
FFI_QueryPlanner {
create_physical_plan: create_physical_plan_fn_wrapper,
logical_codec: planner.logical_codec.clone(),
physical_codec: planner.physical_codec.clone(),
clone: clone_fn_wrapper,
release: release_fn_wrapper,
version: super::version,
private_data,
library_marker_id: crate::get_library_marker_id,
}
}
impl Drop for FFI_QueryPlanner {
fn drop(&mut self) {
unsafe { (self.release)(self) }
}
}
impl Clone for FFI_QueryPlanner {
fn clone(&self) -> Self {
unsafe { (self.clone)(self) }
}
}
impl FFI_QueryPlanner {
pub fn new(
planner: Arc<dyn QueryPlanner + Send + Sync>,
runtime: Option<Handle>,
task_ctx_provider: impl Into<FFI_TaskContextProvider>,
logical_codec: Arc<dyn LogicalExtensionCodec>,
physical_codec: Arc<dyn PhysicalExtensionCodec>,
) -> Self {
let task_ctx_provider = task_ctx_provider.into();
let logical_codec = FFI_LogicalExtensionCodec::new(
logical_codec,
runtime.clone(),
task_ctx_provider.clone(),
);
let physical_codec =
FFI_PhysicalExtensionCodec::new(physical_codec, runtime, task_ctx_provider);
Self::new_with_ffi_codecs(planner, logical_codec, physical_codec)
}
pub fn new_with_ffi_codecs(
planner: Arc<dyn QueryPlanner + Send + Sync>,
logical_codec: FFI_LogicalExtensionCodec,
physical_codec: FFI_PhysicalExtensionCodec,
) -> Self {
let any_ref: &dyn std::any::Any = planner.as_ref();
if let Some(planner) = any_ref.downcast_ref::<ForeignQueryPlanner>() {
let mut planner = planner.0.clone();
planner.logical_codec = logical_codec;
planner.physical_codec = physical_codec;
return planner;
}
let private_data = Box::new(QueryPlannerPrivateData { planner });
Self {
create_physical_plan: create_physical_plan_fn_wrapper,
logical_codec,
physical_codec,
clone: clone_fn_wrapper,
release: release_fn_wrapper,
version: super::version,
private_data: Box::into_raw(private_data) as *mut c_void,
library_marker_id: crate::get_library_marker_id,
}
}
pub async fn create_physical_plan_with_session_runtime(
&self,
logical_plan: &LogicalPlan,
session: &dyn Session,
session_runtime: Option<Handle>,
) -> Result<Arc<dyn ExecutionPlan>> {
let codec: Arc<dyn LogicalExtensionCodec> = (&self.logical_codec).into();
let logical_plan =
logical_plan_to_bytes_with_extension_codec(logical_plan, codec.as_ref())?;
let logical_plan = SVec::from(logical_plan.as_ref());
let task_ctx = session.task_ctx();
let session = FFI_SessionRef::new_with_ffi_codecs(
session,
session_runtime,
self.logical_codec.clone(),
self.physical_codec.clone(),
);
let physical_plan = unsafe {
df_result!((self.create_physical_plan)(self, logical_plan, session).await)?
};
let physical_codec: Arc<dyn PhysicalExtensionCodec> =
(&self.physical_codec).into();
physical_plan_from_bytes_with_extension_codec(
physical_plan.as_slice(),
task_ctx.as_ref(),
physical_codec.as_ref(),
)
}
}
#[derive(Debug)]
pub struct ForeignQueryPlanner(pub FFI_QueryPlanner);
unsafe impl Send for ForeignQueryPlanner {}
unsafe impl Sync for ForeignQueryPlanner {}
impl From<&FFI_QueryPlanner> for Arc<dyn QueryPlanner + Send + Sync> {
fn from(planner: &FFI_QueryPlanner) -> Self {
if (planner.library_marker_id)() == crate::get_library_marker_id() {
Arc::clone(planner.inner())
} else {
Arc::new(ForeignQueryPlanner(planner.clone()))
}
}
}
#[async_trait]
impl QueryPlanner for ForeignQueryPlanner {
async fn create_physical_plan(
&self,
logical_plan: &LogicalPlan,
session: &dyn Session,
) -> Result<Arc<dyn ExecutionPlan>> {
self.0
.create_physical_plan_with_session_runtime(logical_plan, session, None)
.await
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use arrow::datatypes::{DataType, Field, Schema};
use datafusion::prelude::SessionContext;
use datafusion_common::Result;
use datafusion_execution::TaskContextProvider;
use datafusion_expr::LogicalPlanBuilder;
use datafusion_physical_plan::empty::EmptyExec;
use datafusion_proto::logical_plan::DefaultLogicalExtensionCodec;
use datafusion_proto::physical_plan::DefaultPhysicalExtensionCodec;
use super::*;
#[derive(Debug)]
struct EmptyQueryPlanner;
#[async_trait]
impl QueryPlanner for EmptyQueryPlanner {
async fn create_physical_plan(
&self,
_logical_plan: &LogicalPlan,
_session: &dyn Session,
) -> Result<Arc<dyn ExecutionPlan>> {
let schema =
Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, true)]));
Ok(Arc::new(EmptyExec::new(schema)))
}
}
fn create_ffi_query_planner(ctx: Arc<SessionContext>) -> FFI_QueryPlanner {
let task_ctx_provider = Arc::clone(&ctx) as Arc<dyn TaskContextProvider>;
FFI_QueryPlanner::new(
Arc::new(EmptyQueryPlanner),
None,
&task_ctx_provider,
Arc::new(DefaultLogicalExtensionCodec {}),
Arc::new(DefaultPhysicalExtensionCodec {}),
)
}
#[test]
fn test_ffi_query_planner_local_bypass() {
let ctx = Arc::new(SessionContext::new());
let ffi_planner = create_ffi_query_planner(ctx);
let planner: Arc<dyn QueryPlanner + Send + Sync> = (&ffi_planner).into();
let any_ref: &dyn std::any::Any = planner.as_ref();
assert!(any_ref.downcast_ref::<EmptyQueryPlanner>().is_some());
}
#[tokio::test]
async fn test_round_trip_ffi_query_planner_create_physical_plan() -> Result<()> {
let ctx = Arc::new(SessionContext::new());
let mut ffi_planner = create_ffi_query_planner(Arc::clone(&ctx));
ffi_planner.library_marker_id = crate::mock_foreign_marker_id;
let planner: Arc<dyn QueryPlanner + Send + Sync> = (&ffi_planner).into();
let any_ref: &dyn std::any::Any = planner.as_ref();
assert!(any_ref.downcast_ref::<ForeignQueryPlanner>().is_some());
let logical_plan = LogicalPlanBuilder::empty(false).build()?;
let state = ctx.state();
let physical_plan = planner.create_physical_plan(&logical_plan, &state).await?;
assert_eq!(physical_plan.name(), "EmptyExec");
assert!(physical_plan.is::<EmptyExec>());
Ok(())
}
#[tokio::test]
async fn test_create_physical_plan_with_session_runtime() -> Result<()> {
let ctx = Arc::new(SessionContext::new());
let ffi_planner = create_ffi_query_planner(Arc::clone(&ctx));
let logical_plan = LogicalPlanBuilder::empty(false).build()?;
let state = ctx.state();
let physical_plan = ffi_planner
.create_physical_plan_with_session_runtime(
&logical_plan,
&state,
Some(Handle::current()),
)
.await?;
assert_eq!(physical_plan.name(), "EmptyExec");
assert!(physical_plan.is::<EmptyExec>());
Ok(())
}
}