/Users/andrewlamb/Software/datafusion/datafusion/physical-plan/src/display.rs
Line | Count | Source (jump to first uncovered line) |
1 | | // Licensed to the Apache Software Foundation (ASF) under one |
2 | | // or more contributor license agreements. See the NOTICE file |
3 | | // distributed with this work for additional information |
4 | | // regarding copyright ownership. The ASF licenses this file |
5 | | // to you under the Apache License, Version 2.0 (the |
6 | | // "License"); you may not use this file except in compliance |
7 | | // with the License. You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, |
12 | | // software distributed under the License is distributed on an |
13 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
14 | | // KIND, either express or implied. See the License for the |
15 | | // specific language governing permissions and limitations |
16 | | // under the License. |
17 | | |
18 | | //! Implementation of physical plan display. See |
19 | | //! [`crate::displayable`] for examples of how to format |
20 | | |
21 | | use std::fmt; |
22 | | use std::fmt::Formatter; |
23 | | |
24 | | use arrow_schema::SchemaRef; |
25 | | |
26 | | use datafusion_common::display::{GraphvizBuilder, PlanType, StringifiedPlan}; |
27 | | use datafusion_expr::display_schema; |
28 | | use datafusion_physical_expr::{LexOrdering, PhysicalSortExpr}; |
29 | | |
30 | | use super::{accept, ExecutionPlan, ExecutionPlanVisitor}; |
31 | | |
32 | | /// Options for controlling how each [`ExecutionPlan`] should format itself |
33 | | #[derive(Debug, Clone, Copy)] |
34 | | pub enum DisplayFormatType { |
35 | | /// Default, compact format. Example: `FilterExec: c12 < 10.0` |
36 | | Default, |
37 | | /// Verbose, showing all available details |
38 | | Verbose, |
39 | | } |
40 | | |
41 | | /// Wraps an `ExecutionPlan` with various ways to display this plan |
42 | | #[derive(Debug, Clone)] |
43 | | pub struct DisplayableExecutionPlan<'a> { |
44 | | inner: &'a dyn ExecutionPlan, |
45 | | /// How to show metrics |
46 | | show_metrics: ShowMetrics, |
47 | | /// If statistics should be displayed |
48 | | show_statistics: bool, |
49 | | /// If schema should be displayed. See [`Self::set_show_schema`] |
50 | | show_schema: bool, |
51 | | } |
52 | | |
53 | | impl<'a> DisplayableExecutionPlan<'a> { |
54 | | /// Create a wrapper around an [`ExecutionPlan`] which can be |
55 | | /// pretty printed in a variety of ways |
56 | 11 | pub fn new(inner: &'a dyn ExecutionPlan) -> Self { |
57 | 11 | Self { |
58 | 11 | inner, |
59 | 11 | show_metrics: ShowMetrics::None, |
60 | 11 | show_statistics: false, |
61 | 11 | show_schema: false, |
62 | 11 | } |
63 | 11 | } |
64 | | |
65 | | /// Create a wrapper around an [`ExecutionPlan`] which can be |
66 | | /// pretty printed in a variety of ways that also shows aggregated |
67 | | /// metrics |
68 | 0 | pub fn with_metrics(inner: &'a dyn ExecutionPlan) -> Self { |
69 | 0 | Self { |
70 | 0 | inner, |
71 | 0 | show_metrics: ShowMetrics::Aggregated, |
72 | 0 | show_statistics: false, |
73 | 0 | show_schema: false, |
74 | 0 | } |
75 | 0 | } |
76 | | |
77 | | /// Create a wrapper around an [`ExecutionPlan`] which can be |
78 | | /// pretty printed in a variety of ways that also shows all low |
79 | | /// level metrics |
80 | 0 | pub fn with_full_metrics(inner: &'a dyn ExecutionPlan) -> Self { |
81 | 0 | Self { |
82 | 0 | inner, |
83 | 0 | show_metrics: ShowMetrics::Full, |
84 | 0 | show_statistics: false, |
85 | 0 | show_schema: false, |
86 | 0 | } |
87 | 0 | } |
88 | | |
89 | | /// Enable display of schema |
90 | | /// |
91 | | /// If true, plans will be displayed with schema information at the end |
92 | | /// of each line. The format is `schema=[[a:Int32;N, b:Int32;N, c:Int32;N]]` |
93 | 0 | pub fn set_show_schema(mut self, show_schema: bool) -> Self { |
94 | 0 | self.show_schema = show_schema; |
95 | 0 | self |
96 | 0 | } |
97 | | |
98 | | /// Enable display of statistics |
99 | 6 | pub fn set_show_statistics(mut self, show_statistics: bool) -> Self { |
100 | 6 | self.show_statistics = show_statistics; |
101 | 6 | self |
102 | 6 | } |
103 | | |
104 | | /// Return a `format`able structure that produces a single line |
105 | | /// per node. |
106 | | /// |
107 | | /// ```text |
108 | | /// ProjectionExec: expr=[a] |
109 | | /// CoalesceBatchesExec: target_batch_size=8192 |
110 | | /// FilterExec: a < 5 |
111 | | /// RepartitionExec: partitioning=RoundRobinBatch(16) |
112 | | /// CsvExec: source=...", |
113 | | /// ``` |
114 | 5 | pub fn indent(&self, verbose: bool) -> impl fmt::Display + 'a { |
115 | 5 | let format_type = if verbose { |
116 | 5 | DisplayFormatType::Verbose |
117 | | } else { |
118 | 0 | DisplayFormatType::Default |
119 | | }; |
120 | | struct Wrapper<'a> { |
121 | | format_type: DisplayFormatType, |
122 | | plan: &'a dyn ExecutionPlan, |
123 | | show_metrics: ShowMetrics, |
124 | | show_statistics: bool, |
125 | | show_schema: bool, |
126 | | } |
127 | | impl<'a> fmt::Display for Wrapper<'a> { |
128 | 5 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
129 | 5 | let mut visitor = IndentVisitor { |
130 | 5 | t: self.format_type, |
131 | 5 | f, |
132 | 5 | indent: 0, |
133 | 5 | show_metrics: self.show_metrics, |
134 | 5 | show_statistics: self.show_statistics, |
135 | 5 | show_schema: self.show_schema, |
136 | 5 | }; |
137 | 5 | accept(self.plan, &mut visitor) |
138 | 5 | } |
139 | | } |
140 | 5 | Wrapper { |
141 | 5 | format_type, |
142 | 5 | plan: self.inner, |
143 | 5 | show_metrics: self.show_metrics, |
144 | 5 | show_statistics: self.show_statistics, |
145 | 5 | show_schema: self.show_schema, |
146 | 5 | } |
147 | 5 | } |
148 | | |
149 | | /// Returns a `format`able structure that produces graphviz format for execution plan, which can |
150 | | /// be directly visualized [here](https://dreampuf.github.io/GraphvizOnline). |
151 | | /// |
152 | | /// An example is |
153 | | /// ```dot |
154 | | /// strict digraph dot_plan { |
155 | | // 0[label="ProjectionExec: expr=[id@0 + 2 as employee.id + Int32(2)]",tooltip=""] |
156 | | // 1[label="EmptyExec",tooltip=""] |
157 | | // 0 -> 1 |
158 | | // } |
159 | | /// ``` |
160 | 0 | pub fn graphviz(&self) -> impl fmt::Display + 'a { |
161 | | struct Wrapper<'a> { |
162 | | plan: &'a dyn ExecutionPlan, |
163 | | show_metrics: ShowMetrics, |
164 | | show_statistics: bool, |
165 | | } |
166 | | impl<'a> fmt::Display for Wrapper<'a> { |
167 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
168 | 0 | let t = DisplayFormatType::Default; |
169 | 0 |
|
170 | 0 | let mut visitor = GraphvizVisitor { |
171 | 0 | f, |
172 | 0 | t, |
173 | 0 | show_metrics: self.show_metrics, |
174 | 0 | show_statistics: self.show_statistics, |
175 | 0 | graphviz_builder: GraphvizBuilder::default(), |
176 | 0 | parents: Vec::new(), |
177 | 0 | }; |
178 | 0 |
|
179 | 0 | visitor.start_graph()?; |
180 | | |
181 | 0 | accept(self.plan, &mut visitor)?; |
182 | | |
183 | 0 | visitor.end_graph()?; |
184 | 0 | Ok(()) |
185 | 0 | } |
186 | | } |
187 | | |
188 | 0 | Wrapper { |
189 | 0 | plan: self.inner, |
190 | 0 | show_metrics: self.show_metrics, |
191 | 0 | show_statistics: self.show_statistics, |
192 | 0 | } |
193 | 0 | } |
194 | | |
195 | | /// Return a single-line summary of the root of the plan |
196 | | /// Example: `ProjectionExec: expr=[a@0 as a]`. |
197 | 6 | pub fn one_line(&self) -> impl fmt::Display + 'a { |
198 | | struct Wrapper<'a> { |
199 | | plan: &'a dyn ExecutionPlan, |
200 | | show_metrics: ShowMetrics, |
201 | | show_statistics: bool, |
202 | | show_schema: bool, |
203 | | } |
204 | | |
205 | | impl<'a> fmt::Display for Wrapper<'a> { |
206 | 6 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
207 | 6 | let mut visitor = IndentVisitor { |
208 | 6 | f, |
209 | 6 | t: DisplayFormatType::Default, |
210 | 6 | indent: 0, |
211 | 6 | show_metrics: self.show_metrics, |
212 | 6 | show_statistics: self.show_statistics, |
213 | 6 | show_schema: self.show_schema, |
214 | 6 | }; |
215 | 6 | visitor.pre_visit(self.plan)?1 ; |
216 | 5 | Ok(()) |
217 | 6 | } |
218 | | } |
219 | | |
220 | 6 | Wrapper { |
221 | 6 | plan: self.inner, |
222 | 6 | show_metrics: self.show_metrics, |
223 | 6 | show_statistics: self.show_statistics, |
224 | 6 | show_schema: self.show_schema, |
225 | 6 | } |
226 | 6 | } |
227 | | |
228 | | /// format as a `StringifiedPlan` |
229 | 0 | pub fn to_stringified(&self, verbose: bool, plan_type: PlanType) -> StringifiedPlan { |
230 | 0 | StringifiedPlan::new(plan_type, self.indent(verbose).to_string()) |
231 | 0 | } |
232 | | } |
233 | | |
234 | | #[derive(Debug, Clone, Copy)] |
235 | | enum ShowMetrics { |
236 | | /// Do not show any metrics |
237 | | None, |
238 | | |
239 | | /// Show aggregated metrics across partition |
240 | | Aggregated, |
241 | | |
242 | | /// Show full per-partition metrics |
243 | | Full, |
244 | | } |
245 | | |
246 | | /// Formats plans with a single line per node. |
247 | | /// |
248 | | /// # Example |
249 | | /// |
250 | | /// ```text |
251 | | /// ProjectionExec: expr=[column1@0 + 2 as column1 + Int64(2)] |
252 | | /// FilterExec: column1@0 = 5 |
253 | | /// ValuesExec |
254 | | /// ``` |
255 | | struct IndentVisitor<'a, 'b> { |
256 | | /// How to format each node |
257 | | t: DisplayFormatType, |
258 | | /// Write to this formatter |
259 | | f: &'a mut fmt::Formatter<'b>, |
260 | | /// Indent size |
261 | | indent: usize, |
262 | | /// How to show metrics |
263 | | show_metrics: ShowMetrics, |
264 | | /// If statistics should be displayed |
265 | | show_statistics: bool, |
266 | | /// If schema should be displayed |
267 | | show_schema: bool, |
268 | | } |
269 | | |
270 | | impl<'a, 'b> ExecutionPlanVisitor for IndentVisitor<'a, 'b> { |
271 | | type Error = fmt::Error; |
272 | 21 | fn pre_visit(&mut self, plan: &dyn ExecutionPlan) -> Result<bool, Self::Error> { |
273 | 21 | write!(self.f, "{:indent$}", "", indent = self.indent * 2)?0 ; |
274 | 21 | plan.fmt_as(self.t, self.f)?0 ; |
275 | 21 | match self.show_metrics { |
276 | 21 | ShowMetrics::None => {} |
277 | | ShowMetrics::Aggregated => { |
278 | 0 | if let Some(metrics) = plan.metrics() { |
279 | 0 | let metrics = metrics |
280 | 0 | .aggregate_by_name() |
281 | 0 | .sorted_for_display() |
282 | 0 | .timestamps_removed(); |
283 | 0 |
|
284 | 0 | write!(self.f, ", metrics=[{metrics}]")?; |
285 | | } else { |
286 | 0 | write!(self.f, ", metrics=[]")?; |
287 | | } |
288 | | } |
289 | | ShowMetrics::Full => { |
290 | 0 | if let Some(metrics) = plan.metrics() { |
291 | 0 | write!(self.f, ", metrics=[{metrics}]")?; |
292 | | } else { |
293 | 0 | write!(self.f, ", metrics=[]")?; |
294 | | } |
295 | | } |
296 | | } |
297 | 21 | if self.show_statistics { |
298 | 2 | let stats1 = plan.statistics().map_err(|_e| fmt::Error1 )?1 ; |
299 | 1 | write!(self.f, ", statistics=[{}]", stats)?0 ; |
300 | 19 | } |
301 | 19 | if self.show_schema { |
302 | 0 | write!( |
303 | 0 | self.f, |
304 | 0 | ", schema={}", |
305 | 0 | display_schema(plan.schema().as_ref()) |
306 | 0 | )?; |
307 | 19 | } |
308 | 19 | writeln!(self.f)?0 ; |
309 | 19 | self.indent += 1; |
310 | 19 | Ok(true) |
311 | 20 | } |
312 | | |
313 | 15 | fn post_visit(&mut self, _plan: &dyn ExecutionPlan) -> Result<bool, Self::Error> { |
314 | 15 | self.indent -= 1; |
315 | 15 | Ok(true) |
316 | 15 | } |
317 | | } |
318 | | |
319 | | struct GraphvizVisitor<'a, 'b> { |
320 | | f: &'a mut fmt::Formatter<'b>, |
321 | | /// How to format each node |
322 | | t: DisplayFormatType, |
323 | | /// How to show metrics |
324 | | show_metrics: ShowMetrics, |
325 | | /// If statistics should be displayed |
326 | | show_statistics: bool, |
327 | | |
328 | | graphviz_builder: GraphvizBuilder, |
329 | | /// Used to record parent node ids when visiting a plan. |
330 | | parents: Vec<usize>, |
331 | | } |
332 | | |
333 | | impl GraphvizVisitor<'_, '_> { |
334 | 0 | fn start_graph(&mut self) -> fmt::Result { |
335 | 0 | self.graphviz_builder.start_graph(self.f) |
336 | 0 | } |
337 | | |
338 | 0 | fn end_graph(&mut self) -> fmt::Result { |
339 | 0 | self.graphviz_builder.end_graph(self.f) |
340 | 0 | } |
341 | | } |
342 | | |
343 | | impl ExecutionPlanVisitor for GraphvizVisitor<'_, '_> { |
344 | | type Error = fmt::Error; |
345 | | |
346 | 0 | fn pre_visit(&mut self, plan: &dyn ExecutionPlan) -> Result<bool, Self::Error> { |
347 | 0 | let id = self.graphviz_builder.next_id(); |
348 | | |
349 | | struct Wrapper<'a>(&'a dyn ExecutionPlan, DisplayFormatType); |
350 | | |
351 | | impl<'a> std::fmt::Display for Wrapper<'a> { |
352 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
353 | 0 | self.0.fmt_as(self.1, f) |
354 | 0 | } |
355 | | } |
356 | | |
357 | 0 | let label = { format!("{}", Wrapper(plan, self.t)) }; |
358 | | |
359 | 0 | let metrics = match self.show_metrics { |
360 | 0 | ShowMetrics::None => "".to_string(), |
361 | | ShowMetrics::Aggregated => { |
362 | 0 | if let Some(metrics) = plan.metrics() { |
363 | 0 | let metrics = metrics |
364 | 0 | .aggregate_by_name() |
365 | 0 | .sorted_for_display() |
366 | 0 | .timestamps_removed(); |
367 | 0 |
|
368 | 0 | format!("metrics=[{metrics}]") |
369 | | } else { |
370 | 0 | "metrics=[]".to_string() |
371 | | } |
372 | | } |
373 | | ShowMetrics::Full => { |
374 | 0 | if let Some(metrics) = plan.metrics() { |
375 | 0 | format!("metrics=[{metrics}]") |
376 | | } else { |
377 | 0 | "metrics=[]".to_string() |
378 | | } |
379 | | } |
380 | | }; |
381 | | |
382 | 0 | let statistics = if self.show_statistics { |
383 | 0 | let stats = plan.statistics().map_err(|_e| fmt::Error)?; |
384 | 0 | format!("statistics=[{}]", stats) |
385 | | } else { |
386 | 0 | "".to_string() |
387 | | }; |
388 | | |
389 | 0 | let delimiter = if !metrics.is_empty() && !statistics.is_empty() { |
390 | 0 | ", " |
391 | | } else { |
392 | 0 | "" |
393 | | }; |
394 | | |
395 | 0 | self.graphviz_builder.add_node( |
396 | 0 | self.f, |
397 | 0 | id, |
398 | 0 | &label, |
399 | 0 | Some(&format!("{}{}{}", metrics, delimiter, statistics)), |
400 | 0 | )?; |
401 | | |
402 | 0 | if let Some(parent_node_id) = self.parents.last() { |
403 | 0 | self.graphviz_builder |
404 | 0 | .add_edge(self.f, *parent_node_id, id)?; |
405 | 0 | } |
406 | | |
407 | 0 | self.parents.push(id); |
408 | 0 |
|
409 | 0 | Ok(true) |
410 | 0 | } |
411 | | |
412 | 0 | fn post_visit(&mut self, _plan: &dyn ExecutionPlan) -> Result<bool, Self::Error> { |
413 | 0 | self.parents.pop(); |
414 | 0 | Ok(true) |
415 | 0 | } |
416 | | } |
417 | | |
418 | | /// Trait for types which could have additional details when formatted in `Verbose` mode |
419 | | pub trait DisplayAs { |
420 | | /// Format according to `DisplayFormatType`, used when verbose representation looks |
421 | | /// different from the default one |
422 | | /// |
423 | | /// Should not include a newline |
424 | | fn fmt_as(&self, t: DisplayFormatType, f: &mut fmt::Formatter) -> fmt::Result; |
425 | | } |
426 | | |
427 | | /// A newtype wrapper to display `T` implementing`DisplayAs` using the `Default` mode |
428 | | pub struct DefaultDisplay<T>(pub T); |
429 | | |
430 | | impl<T: DisplayAs> fmt::Display for DefaultDisplay<T> { |
431 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
432 | 0 | self.0.fmt_as(DisplayFormatType::Default, f) |
433 | 0 | } |
434 | | } |
435 | | |
436 | | /// A newtype wrapper to display `T` implementing `DisplayAs` using the `Verbose` mode |
437 | | pub struct VerboseDisplay<T>(pub T); |
438 | | |
439 | | impl<T: DisplayAs> fmt::Display for VerboseDisplay<T> { |
440 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
441 | 0 | self.0.fmt_as(DisplayFormatType::Verbose, f) |
442 | 0 | } |
443 | | } |
444 | | |
445 | | /// A wrapper to customize partitioned file display |
446 | | #[derive(Debug)] |
447 | | pub struct ProjectSchemaDisplay<'a>(pub &'a SchemaRef); |
448 | | |
449 | | impl<'a> fmt::Display for ProjectSchemaDisplay<'a> { |
450 | 1 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
451 | 1 | let parts: Vec<_> = self |
452 | 1 | .0 |
453 | 1 | .fields() |
454 | 1 | .iter() |
455 | 2 | .map(|x| x.name().to_owned()) |
456 | 1 | .collect::<Vec<String>>(); |
457 | 1 | write!(f, "[{}]", parts.join(", ")) |
458 | 1 | } |
459 | | } |
460 | | |
461 | | /// A wrapper to customize output ordering display. |
462 | | #[derive(Debug)] |
463 | | pub struct OutputOrderingDisplay<'a>(pub &'a [PhysicalSortExpr]); |
464 | | |
465 | | impl<'a> fmt::Display for OutputOrderingDisplay<'a> { |
466 | 1 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
467 | 1 | write!(f, "[")?0 ; |
468 | 1 | for (i, e) in self.0.iter().enumerate() { |
469 | 1 | if i > 0 { |
470 | 0 | write!(f, ", ")? |
471 | 1 | } |
472 | 1 | write!(f, "{e}")?0 ; |
473 | | } |
474 | 1 | write!(f, "]") |
475 | 1 | } |
476 | | } |
477 | | |
478 | 1 | pub fn display_orderings(f: &mut Formatter, orderings: &[LexOrdering]) -> fmt::Result { |
479 | 1 | if let Some(ordering) = orderings.first() { |
480 | 1 | if !ordering.is_empty() { |
481 | 1 | let start = if orderings.len() == 1 { |
482 | 1 | ", output_ordering=" |
483 | | } else { |
484 | 0 | ", output_orderings=[" |
485 | | }; |
486 | 1 | write!(f, "{}", start)?0 ; |
487 | 1 | for (idx, ordering) in |
488 | 1 | orderings.iter().enumerate().filter(|(_, o)| !o.is_empty()) |
489 | | { |
490 | 1 | match idx { |
491 | 1 | 0 => write!(f, "{}", OutputOrderingDisplay(ordering))?0 , |
492 | 0 | _ => write!(f, ", {}", OutputOrderingDisplay(ordering))?, |
493 | | } |
494 | | } |
495 | 1 | let end = if orderings.len() == 1 { "" } else { "]"0 }; |
496 | 1 | write!(f, "{}", end)?0 ; |
497 | 0 | } |
498 | 0 | } |
499 | | |
500 | 1 | Ok(()) |
501 | 1 | } |
502 | | |
503 | | #[cfg(test)] |
504 | | mod tests { |
505 | | use std::fmt::Write; |
506 | | use std::sync::Arc; |
507 | | |
508 | | use datafusion_common::{DataFusionError, Result, Statistics}; |
509 | | use datafusion_execution::{SendableRecordBatchStream, TaskContext}; |
510 | | |
511 | | use crate::{DisplayAs, ExecutionPlan, PlanProperties}; |
512 | | |
513 | | use super::DisplayableExecutionPlan; |
514 | | |
515 | | #[derive(Debug, Clone, Copy)] |
516 | | enum TestStatsExecPlan { |
517 | | Panic, |
518 | | Error, |
519 | | Ok, |
520 | | } |
521 | | |
522 | | impl DisplayAs for TestStatsExecPlan { |
523 | 6 | fn fmt_as( |
524 | 6 | &self, |
525 | 6 | _t: crate::DisplayFormatType, |
526 | 6 | f: &mut std::fmt::Formatter, |
527 | 6 | ) -> std::fmt::Result { |
528 | 6 | write!(f, "TestStatsExecPlan") |
529 | 6 | } |
530 | | } |
531 | | |
532 | | impl ExecutionPlan for TestStatsExecPlan { |
533 | 0 | fn name(&self) -> &'static str { |
534 | 0 | "TestStatsExecPlan" |
535 | 0 | } |
536 | | |
537 | 0 | fn as_any(&self) -> &dyn std::any::Any { |
538 | 0 | self |
539 | 0 | } |
540 | | |
541 | 0 | fn properties(&self) -> &PlanProperties { |
542 | 0 | unimplemented!() |
543 | | } |
544 | | |
545 | 0 | fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> { |
546 | 0 | vec![] |
547 | 0 | } |
548 | | |
549 | 0 | fn with_new_children( |
550 | 0 | self: Arc<Self>, |
551 | 0 | _: Vec<Arc<dyn ExecutionPlan>>, |
552 | 0 | ) -> Result<Arc<dyn ExecutionPlan>> { |
553 | 0 | unimplemented!() |
554 | | } |
555 | | |
556 | 0 | fn execute( |
557 | 0 | &self, |
558 | 0 | _: usize, |
559 | 0 | _: Arc<TaskContext>, |
560 | 0 | ) -> Result<SendableRecordBatchStream> { |
561 | 0 | todo!() |
562 | | } |
563 | | |
564 | 2 | fn statistics(&self) -> Result<Statistics> { |
565 | 2 | match self { |
566 | 1 | Self::Panic => panic!("expected panic"), |
567 | | Self::Error => { |
568 | 1 | Err(DataFusionError::Internal("expected error".to_string())) |
569 | | } |
570 | 0 | Self::Ok => Ok(Statistics::new_unknown(self.schema().as_ref())), |
571 | | } |
572 | 1 | } |
573 | | } |
574 | | |
575 | 6 | fn test_stats_display(exec: TestStatsExecPlan, show_stats: bool) { |
576 | 6 | let display = |
577 | 6 | DisplayableExecutionPlan::new(&exec).set_show_statistics(show_stats); |
578 | 6 | |
579 | 6 | let mut buf = String::new(); |
580 | 6 | write!(&mut buf, "{}", display.one_line()).unwrap(); |
581 | 6 | let buf = buf.trim(); |
582 | 6 | assert_eq!(buf, "TestStatsExecPlan"); |
583 | 4 | } |
584 | | |
585 | | #[test] |
586 | 1 | fn test_display_when_stats_panic_with_no_show_stats() { |
587 | 1 | test_stats_display(TestStatsExecPlan::Panic, false); |
588 | 1 | } |
589 | | |
590 | | #[test] |
591 | 1 | fn test_display_when_stats_error_with_no_show_stats() { |
592 | 1 | test_stats_display(TestStatsExecPlan::Error, false); |
593 | 1 | } |
594 | | |
595 | | #[test] |
596 | 1 | fn test_display_when_stats_ok_with_no_show_stats() { |
597 | 1 | test_stats_display(TestStatsExecPlan::Ok, false); |
598 | 1 | } |
599 | | |
600 | | #[test] |
601 | | #[should_panic(expected = "expected panic")] |
602 | 1 | fn test_display_when_stats_panic_with_show_stats() { |
603 | 1 | test_stats_display(TestStatsExecPlan::Panic, true); |
604 | 1 | } |
605 | | |
606 | | #[test] |
607 | | #[should_panic(expected = "Error")] // fmt::Error |
608 | 1 | fn test_display_when_stats_error_with_show_stats() { |
609 | 1 | test_stats_display(TestStatsExecPlan::Error, true); |
610 | 1 | } |
611 | | |
612 | | #[test] |
613 | 1 | fn test_display_when_stats_ok_with_show_stats() { |
614 | 1 | test_stats_display(TestStatsExecPlan::Ok, false); |
615 | 1 | } |
616 | | } |