1use std::collections::HashMap;
21use std::fmt;
22
23use crate::{
24 Aggregate, DescribeTable, Distinct, DistinctOn, DmlStatement, Expr, Filter, Join,
25 Limit, LogicalPlan, Partitioning, Projection, RecursiveQuery, Repartition, Sort,
26 Subquery, SubqueryAlias, TableProviderFilterPushDown, TableScan, Unnest, Values,
27 Window, expr_vec_fmt,
28};
29
30use crate::dml::CopyTo;
31use arrow::datatypes::Schema;
32use datafusion_common::display::GraphvizBuilder;
33use datafusion_common::tree_node::{TreeNodeRecursion, TreeNodeVisitor};
34use datafusion_common::{Column, DataFusionError, internal_datafusion_err};
35use serde_json::json;
36
37pub struct IndentVisitor<'a, 'b> {
43 f: &'a mut fmt::Formatter<'b>,
44 with_schema: bool,
46 indent: usize,
48}
49
50impl<'a, 'b> IndentVisitor<'a, 'b> {
51 pub fn new(f: &'a mut fmt::Formatter<'b>, with_schema: bool) -> Self {
54 Self {
55 f,
56 with_schema,
57 indent: 0,
58 }
59 }
60}
61
62impl<'n> TreeNodeVisitor<'n> for IndentVisitor<'_, '_> {
63 type Node = LogicalPlan;
64
65 fn f_down(
66 &mut self,
67 plan: &'n LogicalPlan,
68 ) -> datafusion_common::Result<TreeNodeRecursion> {
69 if self.indent > 0 {
70 writeln!(self.f)?;
71 }
72 write!(self.f, "{:indent$}", "", indent = self.indent * 2)?;
73 write!(self.f, "{}", plan.display())?;
74 if self.with_schema {
75 write!(self.f, " {}", display_schema(plan.schema().as_arrow()))?;
76 }
77
78 self.indent += 1;
79 Ok(TreeNodeRecursion::Continue)
80 }
81
82 fn f_up(
83 &mut self,
84 _plan: &'n LogicalPlan,
85 ) -> datafusion_common::Result<TreeNodeRecursion> {
86 self.indent -= 1;
87 Ok(TreeNodeRecursion::Continue)
88 }
89}
90
91pub fn display_schema(schema: &Schema) -> impl fmt::Display + '_ {
110 struct Wrapper<'a>(&'a Schema);
111
112 impl fmt::Display for Wrapper<'_> {
113 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
114 write!(f, "[")?;
115 for (idx, field) in self.0.fields().iter().enumerate() {
116 if idx > 0 {
117 write!(f, ", ")?;
118 }
119 let nullable_str = if field.is_nullable() { ";N" } else { "" };
120 write!(f, "{}:{}{}", field.name(), field.data_type(), nullable_str)?;
121 }
122 write!(f, "]")
123 }
124 }
125 Wrapper(schema)
126}
127
128pub struct GraphvizVisitor<'a, 'b> {
132 f: &'a mut fmt::Formatter<'b>,
133 graphviz_builder: GraphvizBuilder,
134 with_schema: bool,
136
137 parent_ids: Vec<usize>,
140}
141
142impl<'a, 'b> GraphvizVisitor<'a, 'b> {
143 pub fn new(f: &'a mut fmt::Formatter<'b>) -> Self {
144 Self {
145 f,
146 graphviz_builder: GraphvizBuilder::default(),
147 with_schema: false,
148 parent_ids: Vec::new(),
149 }
150 }
151
152 pub fn set_with_schema(&mut self, with_schema: bool) {
154 self.with_schema = with_schema;
155 }
156
157 pub fn pre_visit_plan(&mut self, label: &str) -> fmt::Result {
158 self.graphviz_builder.start_cluster(self.f, label)
159 }
160
161 pub fn post_visit_plan(&mut self) -> fmt::Result {
162 self.graphviz_builder.end_cluster(self.f)
163 }
164
165 pub fn start_graph(&mut self) -> fmt::Result {
166 self.graphviz_builder.start_graph(self.f)
167 }
168
169 pub fn end_graph(&mut self) -> fmt::Result {
170 self.graphviz_builder.end_graph(self.f)
171 }
172}
173
174impl<'n> TreeNodeVisitor<'n> for GraphvizVisitor<'_, '_> {
175 type Node = LogicalPlan;
176
177 fn f_down(
178 &mut self,
179 plan: &'n LogicalPlan,
180 ) -> datafusion_common::Result<TreeNodeRecursion> {
181 let id = self.graphviz_builder.next_id();
182
183 let label = if self.with_schema {
186 format!(
187 r"{}\nSchema: {}",
188 plan.display(),
189 display_schema(plan.schema().as_arrow())
190 )
191 } else {
192 format!("{}", plan.display())
193 };
194
195 self.graphviz_builder
196 .add_node(self.f, id, &label, None)
197 .map_err(|_e| internal_datafusion_err!("Fail to format"))?;
198
199 if let Some(parent_id) = self.parent_ids.last() {
202 self.graphviz_builder
203 .add_edge(self.f, *parent_id, id)
204 .map_err(|_e| internal_datafusion_err!("Fail to format"))?;
205 }
206
207 self.parent_ids.push(id);
208 Ok(TreeNodeRecursion::Continue)
209 }
210
211 fn f_up(
212 &mut self,
213 _plan: &LogicalPlan,
214 ) -> datafusion_common::Result<TreeNodeRecursion> {
215 let res = self.parent_ids.pop();
218 res.ok_or(internal_datafusion_err!("Fail to format"))
219 .map(|_| TreeNodeRecursion::Continue)
220 }
221}
222
223pub struct PgJsonVisitor<'a, 'b> {
271 f: &'a mut fmt::Formatter<'b>,
272
273 objects: HashMap<u32, serde_json::Value>,
275
276 next_id: u32,
277
278 with_schema: bool,
280
281 parent_ids: Vec<u32>,
284}
285
286impl<'a, 'b> PgJsonVisitor<'a, 'b> {
287 pub fn new(f: &'a mut fmt::Formatter<'b>) -> Self {
288 Self {
289 f,
290 objects: HashMap::new(),
291 next_id: 0,
292 with_schema: false,
293 parent_ids: Vec::new(),
294 }
295 }
296
297 pub fn with_schema(&mut self, with_schema: bool) {
299 self.with_schema = with_schema;
300 }
301
302 fn to_json_value(node: &LogicalPlan) -> serde_json::Value {
304 match node {
305 LogicalPlan::EmptyRelation(_) => {
306 json!({
307 "Node Type": "EmptyRelation",
308 })
309 }
310 LogicalPlan::RecursiveQuery(RecursiveQuery { is_distinct, .. }) => {
311 json!({
312 "Node Type": "RecursiveQuery",
313 "Is Distinct": is_distinct,
314 })
315 }
316 LogicalPlan::Values(Values { values, .. }) => {
317 let str_values = values
318 .iter()
319 .take(5)
321 .map(|row| {
322 let item = row
323 .iter()
324 .map(|expr| expr.to_string())
325 .collect::<Vec<_>>()
326 .join(", ");
327 format!("({item})")
328 })
329 .collect::<Vec<_>>()
330 .join(", ");
331
332 let eclipse = if values.len() > 5 { "..." } else { "" };
333
334 let values_str = format!("{str_values}{eclipse}");
335 json!({
336 "Node Type": "Values",
337 "Values": values_str
338 })
339 }
340 LogicalPlan::TableScan(TableScan {
341 source,
342 table_name,
343 filters,
344 fetch,
345 ..
346 }) => {
347 let mut object = json!({
348 "Node Type": "TableScan",
349 "Relation Name": table_name.table(),
350 });
351
352 if let Some(s) = table_name.schema() {
353 object["Schema"] = serde_json::Value::String(s.to_string());
354 }
355
356 if let Some(c) = table_name.catalog() {
357 object["Catalog"] = serde_json::Value::String(c.to_string());
358 }
359
360 if !filters.is_empty() {
361 let mut full_filter = vec![];
362 let mut partial_filter = vec![];
363 let mut unsupported_filters = vec![];
364 let filters: Vec<&Expr> = filters.iter().collect();
365
366 if let Ok(results) = source.supports_filters_pushdown(&filters) {
367 filters.iter().zip(results.iter()).for_each(
368 |(x, res)| match res {
369 TableProviderFilterPushDown::Exact => full_filter.push(x),
370 TableProviderFilterPushDown::Inexact => {
371 partial_filter.push(x)
372 }
373 TableProviderFilterPushDown::Unsupported => {
374 unsupported_filters.push(x)
375 }
376 },
377 );
378 }
379
380 if !full_filter.is_empty() {
381 object["Full Filters"] =
382 serde_json::Value::String(expr_vec_fmt!(full_filter));
383 };
384 if !partial_filter.is_empty() {
385 object["Partial Filters"] =
386 serde_json::Value::String(expr_vec_fmt!(partial_filter));
387 }
388 if !unsupported_filters.is_empty() {
389 object["Unsupported Filters"] =
390 serde_json::Value::String(expr_vec_fmt!(unsupported_filters));
391 }
392 }
393
394 if let Some(f) = fetch {
395 object["Fetch"] = serde_json::Value::Number((*f).into());
396 }
397
398 object
399 }
400 LogicalPlan::Projection(Projection { expr, .. }) => {
401 json!({
402 "Node Type": "Projection",
403 "Expressions": expr.iter().map(|e| e.to_string()).collect::<Vec<_>>()
404 })
405 }
406 LogicalPlan::Dml(DmlStatement { table_name, op, .. }) => {
407 json!({
408 "Node Type": "Projection",
409 "Operation": op.name(),
410 "Table Name": table_name.table()
411 })
412 }
413 LogicalPlan::Copy(CopyTo {
414 input: _,
415 output_url,
416 file_type,
417 partition_by: _,
418 options,
419 output_schema: _,
420 }) => {
421 let op_str = options
422 .iter()
423 .map(|(k, v)| format!("{k}={v}"))
424 .collect::<Vec<_>>()
425 .join(", ");
426 json!({
427 "Node Type": "CopyTo",
428 "Output URL": output_url,
429 "File Type": format!("{}", file_type.get_ext()),
430 "Options": op_str
431 })
432 }
433 LogicalPlan::Ddl(ddl) => {
434 json!({
435 "Node Type": "Ddl",
436 "Operation": format!("{}", ddl.display())
437 })
438 }
439 LogicalPlan::Filter(Filter {
440 predicate: expr, ..
441 }) => {
442 json!({
443 "Node Type": "Filter",
444 "Condition": format!("{}", expr)
445 })
446 }
447 LogicalPlan::Window(Window { window_expr, .. }) => {
448 json!({
449 "Node Type": "WindowAggr",
450 "Expressions": expr_vec_fmt!(window_expr)
451 })
452 }
453 LogicalPlan::Aggregate(Aggregate {
454 group_expr,
455 aggr_expr,
456 ..
457 }) => {
458 json!({
459 "Node Type": "Aggregate",
460 "Group By": expr_vec_fmt!(group_expr),
461 "Aggregates": expr_vec_fmt!(aggr_expr)
462 })
463 }
464 LogicalPlan::Sort(Sort { expr, fetch, .. }) => {
465 let mut object = json!({
466 "Node Type": "Sort",
467 "Sort Key": expr_vec_fmt!(expr),
468 });
469
470 if let Some(fetch) = fetch {
471 object["Fetch"] = serde_json::Value::Number((*fetch).into());
472 }
473
474 object
475 }
476 LogicalPlan::Join(Join {
477 on: keys,
478 filter,
479 join_constraint,
480 join_type,
481 ..
482 }) => {
483 let join_expr: Vec<String> =
484 keys.iter().map(|(l, r)| format!("{l} = {r}")).collect();
485 let filter_expr = filter
486 .as_ref()
487 .map(|expr| format!(" Filter: {expr}"))
488 .unwrap_or_else(|| "".to_string());
489 json!({
490 "Node Type": format!("{} Join", join_type),
491 "Join Constraint": format!("{:?}", join_constraint),
492 "Join Keys": join_expr.join(", "),
493 "Filter": format!("{}", filter_expr)
494 })
495 }
496 LogicalPlan::Repartition(Repartition {
497 partitioning_scheme,
498 ..
499 }) => match partitioning_scheme {
500 Partitioning::RoundRobinBatch(n) => {
501 json!({
502 "Node Type": "Repartition",
503 "Partitioning Scheme": "RoundRobinBatch",
504 "Partition Count": n
505 })
506 }
507 Partitioning::Hash(expr, n) => {
508 let hash_expr: Vec<String> =
509 expr.iter().map(|e| format!("{e}")).collect();
510
511 json!({
512 "Node Type": "Repartition",
513 "Partitioning Scheme": "Hash",
514 "Partition Count": n,
515 "Partitioning Key": hash_expr
516 })
517 }
518 Partitioning::Range(range) => {
519 let range_expr: Vec<String> =
520 range.ordering().iter().map(|e| format!("{e}")).collect();
521 let split_points: Vec<String> = range
522 .split_points()
523 .iter()
524 .map(|e| format!("{e}"))
525 .collect();
526
527 json!({
528 "Node Type": "Repartition",
529 "Partitioning Scheme": "Range",
530 "Partition Count": range.partition_count(),
531 "Partitioning Key": range_expr,
532 "Split Points": split_points
533 })
534 }
535 Partitioning::DistributeBy(expr) => {
536 let dist_by_expr: Vec<String> =
537 expr.iter().map(|e| format!("{e}")).collect();
538 json!({
539 "Node Type": "Repartition",
540 "Partitioning Scheme": "DistributeBy",
541 "Partitioning Key": dist_by_expr
542 })
543 }
544 },
545 LogicalPlan::Limit(Limit { skip, fetch, .. }) => {
546 let mut object = serde_json::json!(
547 {
548 "Node Type": "Limit",
549 }
550 );
551 if let Some(s) = skip {
552 object["Skip"] = s.to_string().into()
553 };
554 if let Some(f) = fetch {
555 object["Fetch"] = f.to_string().into()
556 };
557 object
558 }
559 LogicalPlan::Subquery(Subquery { .. }) => {
560 json!({
561 "Node Type": "Subquery"
562 })
563 }
564 LogicalPlan::SubqueryAlias(SubqueryAlias { alias, .. }) => {
565 json!({
566 "Node Type": "Subquery",
567 "Alias": alias.table(),
568 })
569 }
570 LogicalPlan::Statement(statement) => {
571 json!({
572 "Node Type": "Statement",
573 "Statement": format!("{}", statement.display())
574 })
575 }
576 LogicalPlan::Distinct(distinct) => match distinct {
577 Distinct::All(_) => {
578 json!({
579 "Node Type": "DistinctAll"
580 })
581 }
582 Distinct::On(DistinctOn {
583 on_expr,
584 select_expr,
585 sort_expr,
586 ..
587 }) => {
588 let mut object = json!({
589 "Node Type": "DistinctOn",
590 "On": expr_vec_fmt!(on_expr),
591 "Select": expr_vec_fmt!(select_expr),
592 });
593 if let Some(sort_expr) = sort_expr {
594 object["Sort"] =
595 serde_json::Value::String(expr_vec_fmt!(sort_expr));
596 }
597
598 object
599 }
600 },
601 LogicalPlan::Explain { .. } => {
602 json!({
603 "Node Type": "Explain"
604 })
605 }
606 LogicalPlan::Analyze { .. } => {
607 json!({
608 "Node Type": "Analyze"
609 })
610 }
611 LogicalPlan::Union(_) => {
612 json!({
613 "Node Type": "Union"
614 })
615 }
616 LogicalPlan::Extension(e) => {
617 json!({
618 "Node Type": e.node.name(),
619 "Detail": format!("{:?}", e.node)
620 })
621 }
622 LogicalPlan::DescribeTable(DescribeTable { .. }) => {
623 json!({
624 "Node Type": "DescribeTable"
625 })
626 }
627 LogicalPlan::Unnest(Unnest {
628 input: plan,
629 list_type_columns: list_col_indices,
630 struct_type_columns: struct_col_indices,
631 ..
632 }) => {
633 let input_columns = plan.schema().columns();
634 let list_type_columns = list_col_indices
635 .iter()
636 .map(|(i, unnest_info)| {
637 format!("{}|depth={:?}", input_columns[*i], unnest_info.depth)
638 })
639 .collect::<Vec<String>>();
640 let struct_type_columns = struct_col_indices
641 .iter()
642 .map(|i| &input_columns[*i])
643 .collect::<Vec<&Column>>();
644 json!({
645 "Node Type": "Unnest",
646 "ListColumn": expr_vec_fmt!(list_type_columns),
647 "StructColumn": expr_vec_fmt!(struct_type_columns),
648 })
649 }
650 }
651 }
652}
653
654impl<'n> TreeNodeVisitor<'n> for PgJsonVisitor<'_, '_> {
655 type Node = LogicalPlan;
656
657 fn f_down(
658 &mut self,
659 node: &'n LogicalPlan,
660 ) -> datafusion_common::Result<TreeNodeRecursion> {
661 let id = self.next_id;
662 self.next_id += 1;
663 let mut object = Self::to_json_value(node);
664
665 object["Plans"] = serde_json::Value::Array(vec![]);
666
667 if self.with_schema {
668 object["Output"] = serde_json::Value::Array(
669 node.schema()
670 .fields()
671 .iter()
672 .map(|f| f.name().to_string())
673 .map(serde_json::Value::String)
674 .collect(),
675 );
676 };
677
678 self.objects.insert(id, object);
679 self.parent_ids.push(id);
680 Ok(TreeNodeRecursion::Continue)
681 }
682
683 fn f_up(
684 &mut self,
685 _node: &Self::Node,
686 ) -> datafusion_common::Result<TreeNodeRecursion> {
687 let id = self.parent_ids.pop().unwrap();
688
689 let current_node = self
690 .objects
691 .remove(&id)
692 .ok_or_else(|| internal_datafusion_err!("Missing current node!"))?;
693
694 if let Some(parent_id) = self.parent_ids.last() {
695 let parent_node = self
696 .objects
697 .get_mut(parent_id)
698 .expect("Missing parent node!");
699 let plans = parent_node
700 .get_mut("Plans")
701 .and_then(|p| p.as_array_mut())
702 .expect("Plans should be an array");
703
704 plans.push(current_node);
705 } else {
706 let plan = serde_json::json!([{"Plan": current_node}]);
708 write!(
709 self.f,
710 "{}",
711 serde_json::to_string_pretty(&plan)
712 .map_err(|e| DataFusionError::External(Box::new(e)))?
713 )?;
714 }
715
716 Ok(TreeNodeRecursion::Continue)
717 }
718}
719
720#[cfg(test)]
721mod tests {
722 use arrow::datatypes::{DataType, Field};
723 use insta::assert_snapshot;
724
725 use super::*;
726
727 #[test]
728 fn test_display_empty_schema() {
729 let schema = Schema::empty();
730 assert_snapshot!(display_schema(&schema), @"[]");
731 }
732
733 #[test]
734 fn test_display_schema() {
735 let schema = Schema::new(vec![
736 Field::new("id", DataType::Int32, false),
737 Field::new("first_name", DataType::Utf8, true),
738 ]);
739
740 assert_snapshot!(display_schema(&schema), @"[id:Int32, first_name:Utf8;N]");
741 }
742}