1use crate::coalesce::LimitedBatchCoalescer;
19use crate::metrics::{ExecutionPlanMetricsSet, MetricsSet};
20use crate::stream::{EmptyRecordBatchStream, RecordBatchStreamAdapter};
21use crate::{
22 ChildrenPropertiesMode, DisplayAs, DisplayFormatType, ExecutionPlan,
23 ExecutionPlanProperties, PlanProperties, ReplaceChildrenOptions,
24 validate_child_count,
25};
26use arrow::array::RecordBatch;
27use arrow_schema::{FieldRef, Fields, Schema, SchemaRef};
28use datafusion_common::Result;
29use datafusion_common::tree_node::{Transformed, TreeNode, TreeNodeRecursion};
30use datafusion_execution::{RecordBatchStream, SendableRecordBatchStream, TaskContext};
31use datafusion_physical_expr::ScalarFunctionExpr;
32use datafusion_physical_expr::async_scalar_function::AsyncFuncExpr;
33use datafusion_physical_expr::equivalence::ProjectionMapping;
34use datafusion_physical_expr::expressions::Column;
35use datafusion_physical_expr_common::metrics::{BaselineMetrics, RecordOutput};
36use datafusion_physical_expr_common::physical_expr::PhysicalExpr;
37use futures::Stream;
38use futures::stream::StreamExt;
39use log::trace;
40use std::pin::Pin;
41use std::sync::Arc;
42use std::task::{Context, Poll, ready};
43
44#[derive(Debug, Clone)]
50pub struct AsyncFuncExec {
51 async_exprs: Vec<Arc<AsyncFuncExpr>>,
53 input: Arc<dyn ExecutionPlan>,
54 cache: Arc<PlanProperties>,
55 metrics: ExecutionPlanMetricsSet,
56}
57
58impl AsyncFuncExec {
59 pub fn try_new(
60 async_exprs: Vec<Arc<AsyncFuncExpr>>,
61 input: Arc<dyn ExecutionPlan>,
62 ) -> Result<Self> {
63 let async_fields = async_exprs
64 .iter()
65 .map(|async_expr| async_expr.return_field(input.schema().as_ref()))
66 .collect::<Result<Vec<FieldRef>>>()?;
67
68 let fields: Fields = input
70 .schema()
71 .fields()
72 .iter()
73 .cloned()
74 .chain(async_fields)
75 .collect();
76
77 let schema = Arc::new(Schema::new(fields));
78 let tuples = async_exprs
79 .iter()
80 .map(|expr| (Arc::clone(&expr.func), expr.name().to_string()))
81 .collect::<Vec<_>>();
82 let async_expr_mapping = ProjectionMapping::try_new(tuples, &input.schema())?;
83 let cache =
84 AsyncFuncExec::compute_properties(&input, schema, &async_expr_mapping)?;
85 Ok(Self {
86 input,
87 async_exprs,
88 cache: Arc::new(cache),
89 metrics: ExecutionPlanMetricsSet::new(),
90 })
91 }
92
93 fn compute_properties(
96 input: &Arc<dyn ExecutionPlan>,
97 schema: SchemaRef,
98 async_expr_mapping: &ProjectionMapping,
99 ) -> Result<PlanProperties> {
100 Ok(PlanProperties::new(
101 input
102 .equivalence_properties()
103 .project(async_expr_mapping, schema),
104 input.output_partitioning().clone(),
105 input.pipeline_behavior(),
106 input.boundedness(),
107 ))
108 }
109
110 #[deprecated(
111 since = "55.0.0",
112 note = "unused by DataFusion; `AsyncFuncExec` serializes itself via `AsyncFuncExec::try_to_proto`, which reads the field directly. There is no replacement; please open an issue if you have a use case for it."
113 )]
114 pub fn async_exprs(&self) -> &[Arc<AsyncFuncExpr>] {
115 &self.async_exprs
116 }
117
118 pub fn input(&self) -> &Arc<dyn ExecutionPlan> {
119 &self.input
120 }
121}
122
123impl DisplayAs for AsyncFuncExec {
124 fn fmt_as(
125 &self,
126 t: DisplayFormatType,
127 f: &mut std::fmt::Formatter,
128 ) -> std::fmt::Result {
129 let expr: Vec<String> = self
130 .async_exprs
131 .iter()
132 .map(|async_expr| async_expr.to_string())
133 .collect();
134 let exprs = expr.join(", ");
135 match t {
136 DisplayFormatType::Default | DisplayFormatType::Verbose => {
137 write!(f, "AsyncFuncExec: async_expr=[{exprs}]")
138 }
139 DisplayFormatType::TreeRender => {
140 writeln!(f, "format=async_expr")?;
141 writeln!(f, "async_expr={exprs}")?;
142 Ok(())
143 }
144 }
145 }
146}
147
148impl ExecutionPlan for AsyncFuncExec {
149 fn name(&self) -> &str {
150 "async_func"
151 }
152
153 fn properties(&self) -> &Arc<PlanProperties> {
154 &self.cache
155 }
156
157 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
158 vec![&self.input]
159 }
160
161 fn apply_expressions(
162 &self,
163 f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
164 ) -> Result<TreeNodeRecursion> {
165 crate::apply_expression_roots(
166 self.async_exprs
167 .iter()
168 .cloned()
169 .map(|expr| expr as Arc<dyn PhysicalExpr>),
170 f,
171 )
172 }
173
174 fn replace_children(
175 self: Arc<Self>,
176 mut children: Vec<Arc<dyn ExecutionPlan>>,
177 options: ReplaceChildrenOptions,
178 ) -> Result<Arc<dyn ExecutionPlan>> {
179 validate_child_count!(self, children);
180 match options.children_properties {
181 ChildrenPropertiesMode::Keep => Ok(Arc::new(Self {
182 input: children.swap_remove(0),
183 metrics: ExecutionPlanMetricsSet::new(),
184 ..Self::clone(&*self)
185 })),
186 ChildrenPropertiesMode::Recompute => Ok(Arc::new(AsyncFuncExec::try_new(
187 self.async_exprs.clone(),
188 children.swap_remove(0),
189 )?)),
190 }
191 }
192
193 fn with_new_children(
194 self: Arc<Self>,
195 children: Vec<Arc<dyn ExecutionPlan>>,
196 ) -> Result<Arc<dyn ExecutionPlan>> {
197 self.replace_children(
198 children,
199 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
200 )
201 }
202
203 fn with_new_children_and_same_properties(
204 self: Arc<Self>,
205 children: Vec<Arc<dyn ExecutionPlan>>,
206 ) -> Result<Arc<dyn ExecutionPlan>> {
207 self.replace_children(
208 children,
209 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Keep),
210 )
211 }
212
213 fn execute(
214 &self,
215 partition: usize,
216 context: Arc<TaskContext>,
217 ) -> Result<SendableRecordBatchStream> {
218 trace!(
219 "Start AsyncFuncExpr::execute for partition {} of context session_id {} and task_id {:?}",
220 partition,
221 context.session_id(),
222 context.task_id()
223 );
224
225 let input_stream = self.input.execute(partition, Arc::clone(&context))?;
227
228 let baseline_metrics = BaselineMetrics::new(&self.metrics, partition);
231
232 let async_exprs_captured = Arc::new(self.async_exprs.clone());
234 let schema_captured = self.schema();
235 let config_options_ref = Arc::clone(context.session_config().options());
236
237 let coalesced_input_stream = CoalesceInputStream {
238 input_stream,
239 batch_coalescer: LimitedBatchCoalescer::new(
240 Arc::clone(&self.input.schema()),
241 config_options_ref.execution.batch_size.get(),
242 None,
243 ),
244 };
245
246 let stream_with_async_functions = coalesced_input_stream.then(move |batch| {
247 let async_exprs_captured = Arc::clone(&async_exprs_captured);
250 let schema_captured = Arc::clone(&schema_captured);
251 let config_options = Arc::clone(&config_options_ref);
252 let baseline_metrics_captured = baseline_metrics.clone();
253
254 async move {
255 let batch = batch?;
256 let mut output_arrays = batch.columns().to_vec();
258 for async_expr in async_exprs_captured.iter() {
259 let output = async_expr
260 .invoke_with_args(&batch, Arc::clone(&config_options))
261 .await?;
262 output_arrays.push(output.to_array(batch.num_rows())?);
263 }
264 let batch = RecordBatch::try_new(schema_captured, output_arrays)?;
265
266 Ok(batch.record_output(&baseline_metrics_captured))
267 }
268 });
269
270 let adapter =
272 RecordBatchStreamAdapter::new(self.schema(), stream_with_async_functions);
273 Ok(Box::pin(adapter))
274 }
275
276 fn metrics(&self) -> Option<MetricsSet> {
277 Some(self.metrics.clone_inner())
278 }
279
280 #[cfg(feature = "proto")]
281 fn try_to_proto(
282 &self,
283 ctx: &crate::proto::ExecutionPlanEncodeCtx<'_>,
284 ) -> Result<Option<datafusion_proto_models::protobuf::PhysicalPlanNode>> {
285 use datafusion_proto_models::protobuf;
286
287 let Self {
291 async_exprs,
292 input,
293 cache: _,
295 metrics: _,
297 } = self;
298
299 let input = ctx.encode_child(input)?;
300 let async_expr_names = async_exprs.iter().map(|e| e.name().to_string()).collect();
301 let async_exprs = ctx.encode_expressions(async_exprs.iter().map(|e| &e.func))?;
302 Ok(Some(protobuf::PhysicalPlanNode {
303 physical_plan_type: Some(
304 protobuf::physical_plan_node::PhysicalPlanType::AsyncFunc(Box::new(
305 protobuf::AsyncFuncExecNode {
306 input: Some(Box::new(input)),
307 async_exprs,
308 async_expr_names,
309 },
310 )),
311 ),
312 }))
313 }
314}
315
316#[cfg(feature = "proto")]
317impl AsyncFuncExec {
318 pub fn try_from_proto(
329 node: &datafusion_proto_models::protobuf::PhysicalPlanNode,
330 ctx: &crate::proto::ExecutionPlanDecodeCtx<'_>,
331 ) -> Result<Arc<dyn ExecutionPlan>> {
332 use datafusion_common::assert_eq_or_internal_err;
333 use datafusion_proto_models::protobuf;
334 let async_func = crate::expect_plan_variant!(
335 node,
336 protobuf::physical_plan_node::PhysicalPlanType::AsyncFunc,
337 "AsyncFuncExec",
338 );
339 let protobuf::AsyncFuncExecNode {
342 input,
343 async_exprs,
344 async_expr_names,
345 } = async_func.as_ref();
346
347 let input =
348 ctx.decode_required_child(input.as_deref(), "AsyncFuncExec", "input")?;
349 let input_schema = input.schema();
350 assert_eq_or_internal_err!(
351 async_exprs.len(),
352 async_expr_names.len(),
353 "AsyncFuncExecNode async_exprs length does not match async_expr_names"
354 );
355 let async_exprs = async_exprs
356 .iter()
357 .zip(async_expr_names.iter())
358 .map(|(expr, name)| {
359 let physical_expr = ctx.decode_expr(expr, input_schema.as_ref())?;
360 Ok(Arc::new(AsyncFuncExpr::try_new(
361 name.clone(),
362 physical_expr,
363 input_schema.as_ref(),
364 )?))
365 })
366 .collect::<Result<Vec<_>>>()?;
367 Ok(Arc::new(AsyncFuncExec::try_new(async_exprs, input)?))
368 }
369}
370
371struct CoalesceInputStream {
372 input_stream: Pin<Box<dyn RecordBatchStream + Send>>,
373 batch_coalescer: LimitedBatchCoalescer,
374}
375
376impl Stream for CoalesceInputStream {
377 type Item = Result<RecordBatch>;
378
379 fn poll_next(
380 mut self: Pin<&mut Self>,
381 cx: &mut Context<'_>,
382 ) -> Poll<Option<Self::Item>> {
383 let mut completed = false;
384
385 loop {
386 if let Some(batch) = self.batch_coalescer.next_completed_batch() {
387 return Poll::Ready(Some(Ok(batch)));
388 }
389
390 if completed {
391 return Poll::Ready(None);
392 }
393
394 match ready!(self.input_stream.poll_next_unpin(cx)) {
395 Some(Ok(batch)) => {
396 if let Err(err) = self.batch_coalescer.push_batch(batch) {
397 return Poll::Ready(Some(Err(err)));
398 }
399 }
400 Some(err) => {
401 return Poll::Ready(Some(err));
402 }
403 None => {
404 completed = true;
405 let input_schema = self.input_stream.schema();
407 self.input_stream =
408 Box::pin(EmptyRecordBatchStream::new(input_schema));
409 if let Err(err) = self.batch_coalescer.finish() {
410 return Poll::Ready(Some(Err(err)));
411 }
412 }
413 }
414 }
415 }
416}
417
418const ASYNC_FN_PREFIX: &str = "__async_fn_";
419
420#[derive(Debug)]
424pub struct AsyncMapper {
425 num_input_columns: usize,
429 pub async_exprs: Vec<Arc<AsyncFuncExpr>>,
431}
432
433impl AsyncMapper {
434 pub fn new(num_input_columns: usize) -> Self {
435 Self {
436 num_input_columns,
437 async_exprs: Vec::new(),
438 }
439 }
440
441 pub fn is_empty(&self) -> bool {
442 self.async_exprs.is_empty()
443 }
444
445 pub fn next_column_name(&self) -> String {
446 format!("{}{}", ASYNC_FN_PREFIX, self.async_exprs.len())
447 }
448
449 pub fn find_references(
451 &mut self,
452 physical_expr: &Arc<dyn PhysicalExpr>,
453 schema: &Schema,
454 ) -> Result<()> {
455 physical_expr.apply(|expr| {
457 if let Some(scalar_func_expr) = expr.downcast_ref::<ScalarFunctionExpr>()
458 && scalar_func_expr.fun().as_async().is_some()
459 {
460 let next_name = self.next_column_name();
461 self.async_exprs.push(Arc::new(AsyncFuncExpr::try_new(
462 next_name,
463 Arc::clone(expr),
464 schema,
465 )?));
466 }
467 Ok(TreeNodeRecursion::Continue)
468 })?;
469 Ok(())
470 }
471
472 pub fn map_expr(
474 &self,
475 expr: Arc<dyn PhysicalExpr>,
476 ) -> Transformed<Arc<dyn PhysicalExpr>> {
477 let Some(idx) =
479 self.async_exprs
480 .iter()
481 .enumerate()
482 .find_map(|(idx, async_expr)| {
483 if async_expr.func == Arc::clone(&expr) {
484 Some(idx)
485 } else {
486 None
487 }
488 })
489 else {
490 return Transformed::no(expr);
491 };
492 Transformed::yes(self.output_column(idx))
494 }
495
496 pub fn output_column(&self, idx: usize) -> Arc<dyn PhysicalExpr> {
498 let async_expr = &self.async_exprs[idx];
499 let output_idx = self.num_input_columns + idx;
500 Arc::new(Column::new(async_expr.name(), output_idx))
501 }
502}
503
504#[cfg(test)]
505mod tests {
506 use std::sync::Arc;
507
508 use arrow::array::{RecordBatch, UInt32Array};
509 use arrow_schema::{DataType, Field, Schema};
510 use datafusion_common::Result;
511 use datafusion_execution::{TaskContext, config::SessionConfig};
512 use futures::StreamExt;
513
514 use crate::{ExecutionPlan, async_func::AsyncFuncExec, test::TestMemoryExec};
515
516 #[tokio::test]
517 async fn test_async_fn_with_coalescing() -> Result<()> {
518 let schema =
519 Arc::new(Schema::new(vec![Field::new("c0", DataType::UInt32, false)]));
520
521 let batch = RecordBatch::try_new(
522 Arc::clone(&schema),
523 vec![Arc::new(UInt32Array::from(vec![1, 2, 3, 4, 5, 6]))],
524 )?;
525
526 let batches: Vec<RecordBatch> = std::iter::repeat_n(batch, 50).collect();
527
528 let session_config = SessionConfig::new().with_batch_size(200);
529 let task_ctx = TaskContext::default().with_session_config(session_config);
530 let task_ctx = Arc::new(task_ctx);
531
532 let test_exec =
533 TestMemoryExec::try_new_exec(&[batches], Arc::clone(&schema), None)?;
534 let exec = AsyncFuncExec::try_new(vec![], test_exec)?;
535
536 let mut stream = exec.execute(0, Arc::clone(&task_ctx))?;
537 let batch = stream
538 .next()
539 .await
540 .expect("expected to get a record batch")?;
541 assert_eq!(200, batch.num_rows());
542 let batch = stream
543 .next()
544 .await
545 .expect("expected to get a record batch")?;
546 assert_eq!(100, batch.num_rows());
547
548 Ok(())
549 }
550}