1use std::sync::Arc;
21
22use super::stream::{RecordBatchReceiverStream, RecordBatchStreamAdapter};
23use super::{
24 DisplayAs, Distribution, ExecutionPlanProperties, PlanProperties,
25 SendableRecordBatchStream,
26};
27use crate::display::DisplayableExecutionPlan;
28use crate::execution_plan::EvaluationType;
29use crate::metrics::{MetricCategory, MetricType};
30use crate::{
31 ChildrenPropertiesMode, DisplayFormatType, ExecutionPlan, Partitioning,
32 ReplaceChildrenOptions,
33};
34
35use arrow::{array::StringBuilder, datatypes::SchemaRef, record_batch::RecordBatch};
36use datafusion_common::format::ExplainFormat;
37use datafusion_common::instant::Instant;
38use datafusion_common::tree_node::TreeNodeRecursion;
39use datafusion_common::{
40 DataFusionError, Result, assert_eq_or_internal_err, internal_err,
41};
42use datafusion_execution::TaskContext;
43use datafusion_physical_expr::EquivalenceProperties;
44use datafusion_physical_expr::PhysicalExpr;
45
46use futures::StreamExt;
47
48#[derive(Debug, Clone)]
51pub struct AnalyzeExec {
52 verbose: bool,
54 show_statistics: bool,
56 metric_types: Vec<MetricType>,
58 metric_categories: Option<Vec<MetricCategory>>,
60 format: ExplainFormat,
62 pub(crate) input: Arc<dyn ExecutionPlan>,
64 schema: SchemaRef,
66 cache: Arc<PlanProperties>,
67}
68
69pub struct AnalyzeExecBuilder {
73 verbose: bool,
74 show_statistics: bool,
75 input: Arc<dyn ExecutionPlan>,
76 schema: SchemaRef,
77 metric_types: Vec<MetricType>,
78 metric_categories: Option<Vec<MetricCategory>>,
79 format: ExplainFormat,
80}
81
82impl AnalyzeExecBuilder {
83 pub fn new(
84 verbose: bool,
85 show_statistics: bool,
86 input: Arc<dyn ExecutionPlan>,
87 schema: SchemaRef,
88 ) -> Self {
89 Self {
90 verbose,
91 show_statistics,
92 input,
93 schema,
94 metric_types: vec![MetricType::Summary, MetricType::Dev],
95 metric_categories: None,
96 format: ExplainFormat::Indent,
97 }
98 }
99
100 pub fn with_metric_types(mut self, metric_types: Vec<MetricType>) -> Self {
101 self.metric_types = metric_types;
102 self
103 }
104
105 pub fn with_metric_categories(
106 mut self,
107 metric_categories: Option<Vec<MetricCategory>>,
108 ) -> Self {
109 self.metric_categories = metric_categories;
110 self
111 }
112
113 pub fn with_format(mut self, format: ExplainFormat) -> Self {
114 self.format = format;
115 self
116 }
117
118 pub fn build(self) -> AnalyzeExec {
119 let cache =
120 AnalyzeExec::compute_properties(&self.input, Arc::clone(&self.schema));
121 AnalyzeExec {
122 verbose: self.verbose,
123 show_statistics: self.show_statistics,
124 metric_types: self.metric_types,
125 metric_categories: self.metric_categories,
126 format: self.format,
127 input: self.input,
128 schema: self.schema,
129 cache: Arc::new(cache),
130 }
131 }
132}
133
134impl AnalyzeExec {
135 pub fn builder(
137 verbose: bool,
138 show_statistics: bool,
139 input: Arc<dyn ExecutionPlan>,
140 schema: SchemaRef,
141 ) -> AnalyzeExecBuilder {
142 AnalyzeExecBuilder::new(verbose, show_statistics, input, schema)
143 }
144
145 pub fn verbose(&self) -> bool {
147 self.verbose
148 }
149
150 pub fn show_statistics(&self) -> bool {
152 self.show_statistics
153 }
154
155 pub fn metric_categories(&self) -> Option<&[MetricCategory]> {
157 self.metric_categories.as_deref()
158 }
159
160 pub fn format(&self) -> &ExplainFormat {
162 &self.format
163 }
164
165 pub fn input(&self) -> &Arc<dyn ExecutionPlan> {
167 &self.input
168 }
169
170 fn compute_properties(
172 input: &Arc<dyn ExecutionPlan>,
173 schema: SchemaRef,
174 ) -> PlanProperties {
175 PlanProperties::new(
176 EquivalenceProperties::new(schema),
177 Partitioning::UnknownPartitioning(1),
178 input.pipeline_behavior(),
179 input.boundedness(),
180 )
181 .with_evaluation_type(EvaluationType::Eager)
182 }
183}
184
185impl DisplayAs for AnalyzeExec {
186 fn fmt_as(
187 &self,
188 t: DisplayFormatType,
189 f: &mut std::fmt::Formatter,
190 ) -> std::fmt::Result {
191 match t {
192 DisplayFormatType::Default | DisplayFormatType::Verbose => {
193 write!(f, "AnalyzeExec verbose={}", self.verbose)
194 }
195 DisplayFormatType::TreeRender => {
196 write!(f, "")
198 }
199 }
200 }
201}
202
203impl ExecutionPlan for AnalyzeExec {
204 fn name(&self) -> &'static str {
205 "AnalyzeExec"
206 }
207
208 fn properties(&self) -> &Arc<PlanProperties> {
210 &self.cache
211 }
212
213 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
214 vec![&self.input]
215 }
216
217 fn required_input_distribution(&self) -> Vec<Distribution> {
218 self.input_distribution_requirements().into_per_child()
219 }
220
221 fn input_distribution_requirements(&self) -> crate::InputDistributionRequirements {
222 crate::InputDistributionRequirements::new(vec![
223 Distribution::UnspecifiedDistribution,
224 ])
225 }
226
227 fn apply_expressions(
228 &self,
229 _f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
230 ) -> Result<TreeNodeRecursion> {
231 Ok(TreeNodeRecursion::Continue)
232 }
233
234 fn replace_children(
235 self: Arc<Self>,
236 mut children: Vec<Arc<dyn ExecutionPlan>>,
237 _: ReplaceChildrenOptions,
238 ) -> Result<Arc<dyn ExecutionPlan>> {
239 Ok(Arc::new(
240 AnalyzeExec::builder(
241 self.verbose,
242 self.show_statistics,
243 children.pop().unwrap(),
244 Arc::clone(&self.schema),
245 )
246 .with_metric_types(self.metric_types.clone())
247 .with_metric_categories(self.metric_categories.clone())
248 .with_format(self.format.clone())
249 .build(),
250 ))
251 }
252
253 fn with_new_children(
254 self: Arc<Self>,
255 children: Vec<Arc<dyn ExecutionPlan>>,
256 ) -> Result<Arc<dyn ExecutionPlan>> {
257 self.replace_children(
258 children,
259 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
260 )
261 }
262
263 fn execute(
264 &self,
265 partition: usize,
266 context: Arc<TaskContext>,
267 ) -> Result<SendableRecordBatchStream> {
268 assert_eq_or_internal_err!(
269 partition,
270 0,
271 "AnalyzeExec invalid partition. Expected 0, got {partition}"
272 );
273
274 let num_input_partitions = self.input.output_partitioning().partition_count();
278 let mut builder =
279 RecordBatchReceiverStream::builder(self.schema(), num_input_partitions);
280
281 for input_partition in 0..num_input_partitions {
282 builder.run_input(
283 Arc::clone(&self.input),
284 input_partition,
285 Arc::clone(&context),
286 );
287 }
288
289 let start = Instant::now();
291 let captured_input = Arc::clone(&self.input);
292 let captured_schema = Arc::clone(&self.schema);
293 let verbose = self.verbose;
294 let show_statistics = self.show_statistics;
295 let metric_types = self.metric_types.clone();
296 let metric_categories = self.metric_categories.clone();
297 let format = self.format.clone();
298
299 let mut input_stream = builder.build();
303 let output = async move {
304 let mut total_rows = 0;
305 while let Some(batch) = input_stream.next().await.transpose()? {
306 total_rows += batch.num_rows();
307 }
308 drop(input_stream);
309
310 let duration = Instant::now() - start;
311 create_output_batch(
312 verbose,
313 show_statistics,
314 total_rows,
315 duration,
316 &captured_input,
317 &captured_schema,
318 &metric_types,
319 metric_categories.as_deref(),
320 &format,
321 )
322 };
323
324 Ok(Box::pin(RecordBatchStreamAdapter::new(
325 Arc::clone(&self.schema),
326 futures::stream::once(output),
327 )))
328 }
329
330 #[cfg(feature = "proto")]
331 fn try_to_proto(
332 &self,
333 ctx: &crate::proto::ExecutionPlanEncodeCtx<'_>,
334 ) -> Result<Option<datafusion_proto_models::protobuf::PhysicalPlanNode>> {
335 use datafusion_proto_models::protobuf;
336
337 let Self {
341 verbose,
342 show_statistics,
343 metric_types: _,
347 metric_categories,
348 format,
349 input,
350 schema,
351 cache: _,
353 } = self;
354
355 let input = ctx.encode_child(input)?;
356 let (has_metric_categories, metric_categories) = match metric_categories {
357 Some(categories) => {
358 (true, categories.iter().map(ToString::to_string).collect())
359 }
360 None => (false, vec![]),
361 };
362 let format = match format {
363 ExplainFormat::Indent => protobuf::ExplainFormat::Indent,
364 ExplainFormat::Tree => protobuf::ExplainFormat::Tree,
365 ExplainFormat::PostgresJSON => protobuf::ExplainFormat::Pgjson,
366 ExplainFormat::Graphviz => protobuf::ExplainFormat::Graphviz,
367 } as i32;
368 Ok(Some(protobuf::PhysicalPlanNode {
369 physical_plan_type: Some(
370 protobuf::physical_plan_node::PhysicalPlanType::Analyze(Box::new(
371 protobuf::AnalyzeExecNode {
372 verbose: *verbose,
373 show_statistics: *show_statistics,
374 input: Some(Box::new(input)),
375 schema: Some(schema.as_ref().try_into()?),
376 has_metric_categories,
377 metric_categories,
378 format,
379 },
380 )),
381 ),
382 }))
383 }
384}
385
386#[cfg(feature = "proto")]
387impl AnalyzeExec {
388 pub fn try_from_proto(
390 node: &datafusion_proto_models::protobuf::PhysicalPlanNode,
391 ctx: &crate::proto::ExecutionPlanDecodeCtx<'_>,
392 ) -> Result<Arc<dyn ExecutionPlan>> {
393 use datafusion_proto_models::protobuf;
394
395 let analyze = crate::expect_plan_variant!(
396 node,
397 protobuf::physical_plan_node::PhysicalPlanType::Analyze,
398 "AnalyzeExec",
399 );
400 let protobuf::AnalyzeExecNode {
403 verbose,
404 show_statistics,
405 input,
406 schema,
407 has_metric_categories,
408 metric_categories,
409 format,
410 } = analyze.as_ref();
411
412 let input =
413 ctx.decode_required_child(input.as_deref(), "AnalyzeExec", "input")?;
414 let metric_categories = if *has_metric_categories {
415 Some(
416 metric_categories
417 .iter()
418 .map(|category| category.parse::<MetricCategory>())
419 .collect::<Result<Vec<_>>>()?,
420 )
421 } else {
422 None
423 };
424 let proto_format = protobuf::ExplainFormat::try_from(*format).map_err(|_| {
425 DataFusionError::Internal(format!(
426 "Received an AnalyzeExecNode message with unknown ExplainFormat {format}"
427 ))
428 })?;
429 let format = match proto_format {
430 protobuf::ExplainFormat::Indent => ExplainFormat::Indent,
431 protobuf::ExplainFormat::Tree => ExplainFormat::Tree,
432 protobuf::ExplainFormat::Pgjson => ExplainFormat::PostgresJSON,
433 protobuf::ExplainFormat::Graphviz => ExplainFormat::Graphviz,
434 };
435 let schema = schema.as_ref().ok_or_else(|| {
436 datafusion_common::internal_datafusion_err!(
437 "AnalyzeExec is missing required field 'schema'"
438 )
439 })?;
440 Ok(Arc::new(
441 AnalyzeExec::builder(
442 *verbose,
443 *show_statistics,
444 input,
445 Arc::new(arrow::datatypes::Schema::try_from(schema)?),
446 )
447 .with_metric_categories(metric_categories)
448 .with_format(format)
449 .build(),
450 ))
451 }
452}
453
454#[expect(clippy::too_many_arguments)]
456fn create_output_batch(
457 verbose: bool,
458 show_statistics: bool,
459 total_rows: usize,
460 duration: std::time::Duration,
461 input: &Arc<dyn ExecutionPlan>,
462 schema: &SchemaRef,
463 metric_types: &[MetricType],
464 metric_categories: Option<&[MetricCategory]>,
465 format: &ExplainFormat,
466) -> Result<RecordBatch> {
467 let mut type_builder = StringBuilder::with_capacity(1, 1024);
468 let mut plan_builder = StringBuilder::with_capacity(1, 1024);
469
470 match format {
471 ExplainFormat::Indent => {
472 type_builder.append_value("Plan with Metrics");
474 let annotated_plan = DisplayableExecutionPlan::with_metrics(input.as_ref())
475 .set_metric_types(metric_types.to_vec())
476 .set_metric_categories(metric_categories.map(|c| c.to_vec()))
477 .set_show_statistics(show_statistics)
478 .indent(verbose)
479 .to_string();
480 plan_builder.append_value(annotated_plan);
481 if verbose {
484 type_builder.append_value("Plan with Full Metrics");
485 let annotated_plan =
486 DisplayableExecutionPlan::with_full_metrics(input.as_ref())
487 .set_metric_types(metric_types.to_vec())
488 .set_metric_categories(metric_categories.map(|c| c.to_vec()))
489 .set_show_statistics(show_statistics)
490 .indent(verbose)
491 .to_string();
492 plan_builder.append_value(annotated_plan);
493 type_builder.append_value("Output Rows");
494 plan_builder.append_value(total_rows.to_string());
495 type_builder.append_value("Duration");
496 plan_builder.append_value(format!("{duration:?}"));
497 }
498 }
499 ExplainFormat::PostgresJSON => {
500 type_builder.append_value("Plan with Metrics");
504 let mut displayable = if verbose {
505 DisplayableExecutionPlan::with_full_metrics(input.as_ref())
506 } else {
507 DisplayableExecutionPlan::with_metrics(input.as_ref())
508 };
509 displayable = displayable
510 .set_metric_types(metric_types.to_vec())
511 .set_metric_categories(metric_categories.map(|c| c.to_vec()));
512 if verbose {
513 displayable = displayable.set_summary(Some(total_rows), Some(duration));
514 }
515 plan_builder.append_value(displayable.pgjson(verbose).to_string());
516 }
517 ExplainFormat::Tree | ExplainFormat::Graphviz => {
518 return internal_err!("AnalyzeExec does not support {format} output format");
519 }
520 }
521
522 RecordBatch::try_new(
523 Arc::clone(schema),
524 vec![
525 Arc::new(type_builder.finish()),
526 Arc::new(plan_builder.finish()),
527 ],
528 )
529 .map_err(DataFusionError::from)
530}
531
532#[cfg(test)]
533mod tests {
534 use super::*;
535 use crate::{
536 collect,
537 test::{
538 assert_is_pending,
539 exec::{BlockingExec, assert_strong_count_converges_to_zero},
540 },
541 };
542
543 use arrow::datatypes::{DataType, Field, Schema};
544 use futures::FutureExt;
545
546 #[tokio::test]
547 async fn test_drop_cancel() -> Result<()> {
548 let task_ctx = Arc::new(TaskContext::default());
549 let schema =
550 Arc::new(Schema::new(vec![Field::new("a", DataType::Float32, true)]));
551
552 let blocking_exec = Arc::new(BlockingExec::new(Arc::clone(&schema), 1));
553 let refs = blocking_exec.refs();
554 let analyze_exec =
555 Arc::new(AnalyzeExec::builder(true, false, blocking_exec, schema).build());
556
557 let fut = collect(analyze_exec, task_ctx);
558 let mut fut = fut.boxed();
559
560 assert_is_pending(&mut fut);
561 drop(fut);
562 assert_strong_count_converges_to_zero(refs).await;
563
564 Ok(())
565 }
566}