datafusion_physical_plan/
explain.rs1use std::sync::Arc;
21
22use super::{DisplayAs, PlanProperties, SendableRecordBatchStream};
23use crate::execution_plan::{Boundedness, EmissionType};
24use crate::stream::RecordBatchStreamAdapter;
25use crate::{
26 ChildrenPropertiesMode, DisplayFormatType, ExecutionPlan, Partitioning,
27 ReplaceChildrenOptions,
28};
29
30use arrow::{array::StringBuilder, datatypes::SchemaRef, record_batch::RecordBatch};
31use datafusion_common::display::StringifiedPlan;
32use datafusion_common::tree_node::TreeNodeRecursion;
33use datafusion_common::{Result, assert_eq_or_internal_err};
34use datafusion_execution::TaskContext;
35use datafusion_physical_expr::{EquivalenceProperties, PhysicalExpr};
36
37use log::trace;
38
39#[derive(Debug, Clone)]
43pub struct ExplainExec {
44 schema: SchemaRef,
46 stringified_plans: Vec<StringifiedPlan>,
48 verbose: bool,
50 cache: Arc<PlanProperties>,
51}
52
53impl ExplainExec {
54 pub fn new(
56 schema: SchemaRef,
57 stringified_plans: Vec<StringifiedPlan>,
58 verbose: bool,
59 ) -> Self {
60 let cache = Self::compute_properties(Arc::clone(&schema));
61 ExplainExec {
62 schema,
63 stringified_plans,
64 verbose,
65 cache: Arc::new(cache),
66 }
67 }
68
69 pub fn stringified_plans(&self) -> &[StringifiedPlan] {
71 &self.stringified_plans
72 }
73
74 pub fn verbose(&self) -> bool {
76 self.verbose
77 }
78
79 fn compute_properties(schema: SchemaRef) -> PlanProperties {
81 PlanProperties::new(
82 EquivalenceProperties::new(schema),
83 Partitioning::UnknownPartitioning(1),
84 EmissionType::Final,
85 Boundedness::Bounded,
86 )
87 }
88}
89
90impl DisplayAs for ExplainExec {
91 fn fmt_as(
92 &self,
93 t: DisplayFormatType,
94 f: &mut std::fmt::Formatter,
95 ) -> std::fmt::Result {
96 match t {
97 DisplayFormatType::Default | DisplayFormatType::Verbose => {
98 write!(f, "ExplainExec")
99 }
100 DisplayFormatType::TreeRender => {
101 write!(f, "")
103 }
104 }
105 }
106}
107
108impl ExecutionPlan for ExplainExec {
109 fn name(&self) -> &'static str {
110 "ExplainExec"
111 }
112
113 fn properties(&self) -> &Arc<PlanProperties> {
115 &self.cache
116 }
117
118 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
119 vec![]
121 }
122
123 fn apply_expressions(
124 &self,
125 _f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
126 ) -> Result<TreeNodeRecursion> {
127 Ok(TreeNodeRecursion::Continue)
128 }
129
130 fn replace_children(
131 self: Arc<Self>,
132 _: Vec<Arc<dyn ExecutionPlan>>,
133 _: ReplaceChildrenOptions,
134 ) -> Result<Arc<dyn ExecutionPlan>> {
135 Ok(self)
136 }
137
138 fn with_new_children(
139 self: Arc<Self>,
140 children: Vec<Arc<dyn ExecutionPlan>>,
141 ) -> Result<Arc<dyn ExecutionPlan>> {
142 self.replace_children(
143 children,
144 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
145 )
146 }
147
148 fn execute(
149 &self,
150 partition: usize,
151 context: Arc<TaskContext>,
152 ) -> Result<SendableRecordBatchStream> {
153 trace!(
154 "Start ExplainExec::execute for partition {} of context session_id {} and task_id {:?}",
155 partition,
156 context.session_id(),
157 context.task_id()
158 );
159 assert_eq_or_internal_err!(
160 partition,
161 0,
162 "ExplainExec invalid partition {partition}"
163 );
164 let mut type_builder =
165 StringBuilder::with_capacity(self.stringified_plans.len(), 1024);
166 let mut plan_builder =
167 StringBuilder::with_capacity(self.stringified_plans.len(), 1024);
168
169 let plans_to_print = self
170 .stringified_plans
171 .iter()
172 .filter(|s| s.should_display(self.verbose));
173
174 let mut prev: Option<&StringifiedPlan> = None;
176
177 for p in plans_to_print {
178 type_builder.append_value(p.plan_type.to_string());
179 match prev {
180 Some(prev) if !should_show(prev, p) => {
181 plan_builder.append_value("SAME TEXT AS ABOVE");
182 }
183 Some(_) | None => {
184 plan_builder.append_value(&*p.plan);
185 }
186 }
187 prev = Some(p);
188 }
189
190 let record_batch = RecordBatch::try_new(
191 Arc::clone(&self.schema),
192 vec![
193 Arc::new(type_builder.finish()),
194 Arc::new(plan_builder.finish()),
195 ],
196 )?;
197
198 trace!(
199 "Before returning RecordBatchStream in ExplainExec::execute for partition {} of context session_id {} and task_id {:?}",
200 partition,
201 context.session_id(),
202 context.task_id()
203 );
204
205 Ok(Box::pin(RecordBatchStreamAdapter::new(
206 Arc::clone(&self.schema),
207 futures::stream::iter(vec![Ok(record_batch)]),
208 )))
209 }
210
211 #[cfg(feature = "proto")]
212 fn try_to_proto(
213 &self,
214 _ctx: &crate::proto::ExecutionPlanEncodeCtx<'_>,
215 ) -> Result<Option<datafusion_proto_models::protobuf::PhysicalPlanNode>> {
216 use datafusion_proto_models::protobuf;
217
218 Ok(Some(protobuf::PhysicalPlanNode {
219 physical_plan_type: Some(
220 protobuf::physical_plan_node::PhysicalPlanType::Explain(
221 protobuf::ExplainExecNode {
222 schema: Some(self.schema().as_ref().try_into()?),
223 stringified_plans: self
224 .stringified_plans()
225 .iter()
226 .map(stringified_plan_to_proto)
227 .collect(),
228 verbose: self.verbose(),
229 },
230 ),
231 ),
232 }))
233 }
234}
235
236#[cfg(feature = "proto")]
237impl ExplainExec {
238 pub fn try_from_proto(
240 node: &datafusion_proto_models::protobuf::PhysicalPlanNode,
241 _ctx: &crate::proto::ExecutionPlanDecodeCtx<'_>,
242 ) -> Result<Arc<dyn ExecutionPlan>> {
243 use datafusion_proto_models::protobuf;
244
245 let explain = crate::expect_plan_variant!(
246 node,
247 protobuf::physical_plan_node::PhysicalPlanType::Explain,
248 "ExplainExec",
249 );
250 let schema = explain.schema.as_ref().ok_or_else(|| {
251 datafusion_common::internal_datafusion_err!(
252 "ExplainExec is missing required field 'schema'"
253 )
254 })?;
255 Ok(Arc::new(ExplainExec::new(
256 Arc::new(arrow::datatypes::Schema::try_from(schema)?),
257 explain
258 .stringified_plans
259 .iter()
260 .map(stringified_plan_from_proto)
261 .collect(),
262 explain.verbose,
263 )))
264 }
265}
266
267#[cfg(feature = "proto")]
268fn stringified_plan_to_proto(
269 stringified_plan: &StringifiedPlan,
270) -> datafusion_proto_models::protobuf::StringifiedPlan {
271 use datafusion_common::display::PlanType;
272 use datafusion_proto_models::datafusion_common::EmptyMessage;
273 use datafusion_proto_models::protobuf;
274 use protobuf::plan_type::PlanTypeEnum::{
275 AnalyzedLogicalPlan, FinalAnalyzedLogicalPlan, FinalLogicalPlan,
276 FinalPhysicalPlan, FinalPhysicalPlanWithSchema, FinalPhysicalPlanWithStats,
277 InitialLogicalPlan, InitialPhysicalPlan, InitialPhysicalPlanWithSchema,
278 InitialPhysicalPlanWithStats, OptimizedLogicalPlan, OptimizedPhysicalPlan,
279 PhysicalPlanError,
280 };
281
282 protobuf::StringifiedPlan {
283 plan_type: match stringified_plan.clone().plan_type {
284 PlanType::InitialLogicalPlan => Some(protobuf::PlanType {
285 plan_type_enum: Some(InitialLogicalPlan(EmptyMessage {})),
286 }),
287 PlanType::AnalyzedLogicalPlan { analyzer_name } => Some(protobuf::PlanType {
288 plan_type_enum: Some(AnalyzedLogicalPlan(
289 protobuf::AnalyzedLogicalPlanType { analyzer_name },
290 )),
291 }),
292 PlanType::FinalAnalyzedLogicalPlan => Some(protobuf::PlanType {
293 plan_type_enum: Some(FinalAnalyzedLogicalPlan(EmptyMessage {})),
294 }),
295 PlanType::OptimizedLogicalPlan { optimizer_name } => {
296 Some(protobuf::PlanType {
297 plan_type_enum: Some(OptimizedLogicalPlan(
298 protobuf::OptimizedLogicalPlanType { optimizer_name },
299 )),
300 })
301 }
302 PlanType::FinalLogicalPlan => Some(protobuf::PlanType {
303 plan_type_enum: Some(FinalLogicalPlan(EmptyMessage {})),
304 }),
305 PlanType::InitialPhysicalPlan => Some(protobuf::PlanType {
306 plan_type_enum: Some(InitialPhysicalPlan(EmptyMessage {})),
307 }),
308 PlanType::OptimizedPhysicalPlan { optimizer_name } => {
309 Some(protobuf::PlanType {
310 plan_type_enum: Some(OptimizedPhysicalPlan(
311 protobuf::OptimizedPhysicalPlanType { optimizer_name },
312 )),
313 })
314 }
315 PlanType::FinalPhysicalPlan => Some(protobuf::PlanType {
316 plan_type_enum: Some(FinalPhysicalPlan(EmptyMessage {})),
317 }),
318 PlanType::InitialPhysicalPlanWithStats => Some(protobuf::PlanType {
319 plan_type_enum: Some(InitialPhysicalPlanWithStats(EmptyMessage {})),
320 }),
321 PlanType::InitialPhysicalPlanWithSchema => Some(protobuf::PlanType {
322 plan_type_enum: Some(InitialPhysicalPlanWithSchema(EmptyMessage {})),
323 }),
324 PlanType::FinalPhysicalPlanWithStats => Some(protobuf::PlanType {
325 plan_type_enum: Some(FinalPhysicalPlanWithStats(EmptyMessage {})),
326 }),
327 PlanType::FinalPhysicalPlanWithSchema => Some(protobuf::PlanType {
328 plan_type_enum: Some(FinalPhysicalPlanWithSchema(EmptyMessage {})),
329 }),
330 PlanType::PhysicalPlanError => Some(protobuf::PlanType {
331 plan_type_enum: Some(PhysicalPlanError(EmptyMessage {})),
332 }),
333 },
334 plan: stringified_plan.plan.to_string(),
335 }
336}
337
338#[cfg(feature = "proto")]
339fn stringified_plan_from_proto(
340 stringified_plan: &datafusion_proto_models::protobuf::StringifiedPlan,
341) -> StringifiedPlan {
342 use datafusion_common::display::PlanType;
343 use datafusion_proto_models::protobuf::plan_type::PlanTypeEnum::{
344 AnalyzedLogicalPlan, FinalAnalyzedLogicalPlan, FinalLogicalPlan,
345 FinalPhysicalPlan, FinalPhysicalPlanWithSchema, FinalPhysicalPlanWithStats,
346 InitialLogicalPlan, InitialPhysicalPlan, InitialPhysicalPlanWithSchema,
347 InitialPhysicalPlanWithStats, OptimizedLogicalPlan, OptimizedPhysicalPlan,
348 PhysicalPlanError,
349 };
350 use datafusion_proto_models::protobuf::{
351 AnalyzedLogicalPlanType, OptimizedLogicalPlanType, OptimizedPhysicalPlanType,
352 };
353
354 StringifiedPlan {
355 plan_type: match stringified_plan
356 .plan_type
357 .as_ref()
358 .and_then(|plan_type| plan_type.plan_type_enum.as_ref())
359 .unwrap_or_else(|| {
360 panic!(
361 "Cannot create protobuf::StringifiedPlan from {stringified_plan:?}"
362 )
363 }) {
364 InitialLogicalPlan(_) => PlanType::InitialLogicalPlan,
365 AnalyzedLogicalPlan(AnalyzedLogicalPlanType { analyzer_name }) => {
366 PlanType::AnalyzedLogicalPlan {
367 analyzer_name: analyzer_name.clone(),
368 }
369 }
370 FinalAnalyzedLogicalPlan(_) => PlanType::FinalAnalyzedLogicalPlan,
371 OptimizedLogicalPlan(OptimizedLogicalPlanType { optimizer_name }) => {
372 PlanType::OptimizedLogicalPlan {
373 optimizer_name: optimizer_name.clone(),
374 }
375 }
376 FinalLogicalPlan(_) => PlanType::FinalLogicalPlan,
377 InitialPhysicalPlan(_) => PlanType::InitialPhysicalPlan,
378 InitialPhysicalPlanWithStats(_) => PlanType::InitialPhysicalPlanWithStats,
379 InitialPhysicalPlanWithSchema(_) => PlanType::InitialPhysicalPlanWithSchema,
380 OptimizedPhysicalPlan(OptimizedPhysicalPlanType { optimizer_name }) => {
381 PlanType::OptimizedPhysicalPlan {
382 optimizer_name: optimizer_name.clone(),
383 }
384 }
385 FinalPhysicalPlan(_) => PlanType::FinalPhysicalPlan,
386 FinalPhysicalPlanWithStats(_) => PlanType::FinalPhysicalPlanWithStats,
387 FinalPhysicalPlanWithSchema(_) => PlanType::FinalPhysicalPlanWithSchema,
388 PhysicalPlanError(_) => PlanType::PhysicalPlanError,
389 },
390 plan: Arc::new(stringified_plan.plan.clone()),
391 }
392}
393
394fn should_show(previous_plan: &StringifiedPlan, this_plan: &StringifiedPlan) -> bool {
400 (previous_plan.plan != this_plan.plan) || this_plan.should_display(false)
403}