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