use std::{
fmt::{self, Debug},
hash::Hash,
iter,
pin::Pin,
sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex,
},
task::{Context, Poll},
};
use async_trait::async_trait;
use datafusion::{
arrow::{array::RecordBatch, datatypes::SchemaRef},
common::DFSchemaRef,
error::DataFusionError,
execution::{RecordBatchStream, SendableRecordBatchStream, SessionState},
physical_plan::{
stream::RecordBatchStreamAdapter, DisplayAs, ExecutionPlan, Partitioning, PlanProperties,
},
physical_planner::{ExtensionPlanner, PhysicalPlanner},
};
use datafusion_expr::{
Expr, Extension, LogicalPlan, UserDefinedLogicalNode, UserDefinedLogicalNodeCore,
};
use futures::{
channel::mpsc::{channel, Receiver, Sender},
stream, SinkExt, Stream, StreamExt, TryStreamExt,
};
use pin_project_lite::pin_project;
pub fn fork_node(plan: Arc<LogicalPlan>) -> (ForkNode, ForkNode) {
let parallelism = std::thread::available_parallelism().unwrap().get();
let (sender, receiver): (Vec<_>, Vec<_>) = iter::repeat_n((), parallelism)
.map(|_| {
let (sender, receiver) = channel(0);
(
Arc::new(Mutex::new(Some(sender))),
Arc::new(Mutex::new(Some(receiver))),
)
})
.unzip();
let executed: Vec<Arc<AtomicBool>> = iter::repeat_n((), parallelism)
.map(|_| Arc::new(AtomicBool::new(false)))
.collect();
(
ForkNode {
input: plan.clone(),
sender: sender.clone(),
receiver: receiver.clone(),
executed: executed.clone(),
},
ForkNode {
input: plan,
sender,
receiver,
executed,
},
)
}
#[allow(clippy::type_complexity)]
pub struct ForkNode {
pub(crate) input: Arc<LogicalPlan>,
sender: Vec<Arc<Mutex<Option<Sender<Result<RecordBatch, DataFusionError>>>>>>,
receiver: Vec<Arc<Mutex<Option<Receiver<Result<RecordBatch, DataFusionError>>>>>>,
executed: Vec<Arc<AtomicBool>>,
}
impl PartialEq for ForkNode {
fn eq(&self, other: &Self) -> bool {
self.input.eq(&other.input)
}
}
impl Eq for ForkNode {}
impl PartialOrd for ForkNode {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.input.partial_cmp(&other.input)
}
}
impl Hash for ForkNode {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
"ForkNode".hash(state);
self.input.hash(state);
}
}
impl fmt::Debug for ForkNode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
UserDefinedLogicalNodeCore::fmt_for_explain(self, f)
}
}
impl UserDefinedLogicalNodeCore for ForkNode {
fn name(&self) -> &str {
"ForkNode"
}
fn inputs(&self) -> Vec<&LogicalPlan> {
vec![&self.input]
}
fn schema(&self) -> &DFSchemaRef {
self.input.schema()
}
fn expressions(&self) -> Vec<Expr> {
vec![]
}
fn fmt_for_explain(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "ForkNode")
}
fn with_exprs_and_inputs(
&self,
exprs: Vec<Expr>,
mut inputs: Vec<LogicalPlan>,
) -> datafusion::error::Result<Self> {
assert_eq!(inputs.len(), 1, "input size inconsistent");
assert_eq!(exprs.len(), 0, "expression size inconsistent");
Ok(Self {
input: Arc::new(inputs.pop().unwrap()),
sender: self.sender.clone(),
receiver: self.receiver.clone(),
executed: self.executed.clone(),
})
}
}
impl From<ForkNode> for LogicalPlan {
fn from(value: ForkNode) -> Self {
LogicalPlan::Extension(Extension {
node: Arc::new(value),
})
}
}
#[allow(clippy::type_complexity)]
pub(crate) struct PhysicalForkNode {
input: Arc<dyn ExecutionPlan>,
properties: Arc<PlanProperties>,
sender: Vec<Arc<Mutex<Option<Sender<Result<RecordBatch, DataFusionError>>>>>>,
receiver: Vec<Arc<Mutex<Option<Receiver<Result<RecordBatch, DataFusionError>>>>>>,
executed: Vec<Arc<AtomicBool>>,
}
impl Debug for PhysicalForkNode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "PhysicalForkNode")?;
self.input.fmt(f)
}
}
impl DisplayAs for PhysicalForkNode {
fn fmt_as(
&self,
_t: datafusion::physical_plan::DisplayFormatType,
f: &mut fmt::Formatter,
) -> fmt::Result {
self.fmt(f)
}
}
impl ExecutionPlan for PhysicalForkNode {
fn name(&self) -> &str {
"PhysicalForkNode"
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn properties(&self) -> &Arc<PlanProperties> {
&self.properties
}
fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
vec![&self.input]
}
fn with_new_children(
self: Arc<Self>,
mut children: Vec<Arc<dyn ExecutionPlan>>,
) -> datafusion::error::Result<Arc<dyn ExecutionPlan>> {
assert_eq!(children.len(), 1);
let properties = Arc::new(
(**children[0].properties())
.clone()
.with_partitioning(Partitioning::UnknownPartitioning(self.executed.len())),
);
Ok(Arc::new(PhysicalForkNode {
input: children.pop().unwrap(),
properties,
sender: self.sender.clone(),
receiver: self.receiver.clone(),
executed: self.executed.clone(),
}))
}
fn execute(
&self,
partition: usize,
context: Arc<datafusion::execution::TaskContext>,
) -> Result<SendableRecordBatchStream, DataFusionError> {
let input_partitions = self.input.properties().partitioning.partition_count();
let n_partitions = self.executed.len();
let executed = self.executed[partition]
.compare_exchange(false, true, Ordering::Release, Ordering::Acquire)
.is_err();
if executed {
let receiver = {
let mut lock = self.receiver[partition].lock().unwrap();
lock.take()
}
.ok_or(DataFusionError::Internal(
"Fork node can only be executed once.".to_string(),
))
.unwrap();
return Ok(Box::pin(RecordBatchStreamAdapter::new(
self.schema().clone(),
receiver,
)));
}
let sender = {
let mut lock = self.sender[partition].lock().unwrap();
lock.take()
}
.ok_or(DataFusionError::Internal(
"Fork node can only be executed once.".to_string(),
))
.unwrap();
let schema = self.schema().clone();
if partition >= input_partitions {
return Ok(Box::pin(RecordBatchStreamSender::new(
schema,
sender,
stream::empty(),
)));
}
let mut count = input_partitions / n_partitions;
if partition < input_partitions % n_partitions {
count += 1;
}
let stream = (1..count).try_fold(
self.input.clone().execute(partition, context.clone())?,
|acc, x| {
let partition = x * n_partitions + partition;
Ok::<_, DataFusionError>(Box::pin(RecordBatchStreamAdapter::new(
self.schema().clone(),
acc.chain(self.input.clone().execute(partition, context.clone())?),
)) as SendableRecordBatchStream)
},
)?;
Ok(Box::pin(RecordBatchStreamSender::new(
schema,
sender.clone(),
stream.and_then(move |batch| {
let mut sender = sender.clone();
async move {
sender
.send(Ok(batch.clone()))
.await
.map_err(|err| DataFusionError::External(Box::new(err)))?;
Ok(batch)
}
}),
)))
}
}
pub struct ForkNodePlanner {}
impl ForkNodePlanner {
pub(crate) fn new() -> Self {
Self {}
}
}
#[async_trait]
impl ExtensionPlanner for ForkNodePlanner {
async fn plan_extension(
&self,
_planner: &dyn PhysicalPlanner,
node: &dyn UserDefinedLogicalNode,
logical_inputs: &[&LogicalPlan],
physical_inputs: &[Arc<dyn ExecutionPlan>],
_session_state: &SessionState,
) -> Result<Option<Arc<dyn ExecutionPlan>>, DataFusionError> {
if let Some(fork_node) = node.as_any().downcast_ref::<ForkNode>() {
assert_eq!(physical_inputs.len(), 1);
assert_eq!(logical_inputs.len(), 1);
let len = fork_node.sender.len();
let properties = Arc::new(
(**physical_inputs[0].properties())
.clone()
.with_partitioning(Partitioning::UnknownPartitioning(len)),
);
Ok(Some(Arc::new(PhysicalForkNode {
input: physical_inputs[0].clone(),
properties,
sender: fork_node.sender.clone(),
receiver: fork_node.receiver.clone(),
executed: fork_node.executed.clone(),
})))
} else {
Ok(None)
}
}
}
pin_project! {
pub struct RecordBatchStreamSender<S> {
schema: SchemaRef,
sender: ClosingSender,
#[pin]
stream: S,
}
}
impl<S> RecordBatchStreamSender<S> {
fn new(
schema: SchemaRef,
sender: Sender<Result<RecordBatch, DataFusionError>>,
stream: S,
) -> Self {
RecordBatchStreamSender {
schema,
sender: ClosingSender(sender),
stream,
}
}
}
impl<S> std::fmt::Debug for RecordBatchStreamSender<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RecordBatchStreamSender")
.field("schema", &self.schema)
.finish()
}
}
impl<S> Stream for RecordBatchStreamSender<S>
where
S: Stream<Item = Result<RecordBatch, DataFusionError>>,
{
type Item = Result<RecordBatch, DataFusionError>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let project = self.project();
match project.stream.poll_next(cx) {
Poll::Ready(None) => {
project.sender.0.close_channel();
Poll::Ready(None)
}
x => x,
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.stream.size_hint()
}
}
impl<S> RecordBatchStream for RecordBatchStreamSender<S>
where
S: Stream<Item = Result<RecordBatch, DataFusionError>>,
{
fn schema(&self) -> SchemaRef {
Arc::clone(&self.schema)
}
}
struct ClosingSender(Sender<Result<RecordBatch, DataFusionError>>);
impl Drop for ClosingSender {
fn drop(&mut self) {
if !self.0.is_closed() {
self.0.close_channel();
}
}
}