1use crate::plan::{JoinType, LogicalPlan, SetOpType};
4
5pub fn explain(plan: &LogicalPlan) -> String {
18 let mut out = String::new();
19 explain_inner(plan, 0, &mut out);
20 if out.ends_with('\n') {
22 out.pop();
23 }
24 out
25}
26
27pub(crate) fn explain_inner(plan: &LogicalPlan, depth: usize, out: &mut String) {
28 let indent = " ".repeat(depth);
29 match plan {
30 LogicalPlan::Scan {
31 table,
32 alias,
33 limit,
34 } => {
35 let limit_str = limit.map(|l| format!(" limit={l}")).unwrap_or_default();
36 if let Some(a) = alias {
37 out.push_str(&format!("{indent}Scan [{table} AS {a}{limit_str}]\n"));
38 } else {
39 out.push_str(&format!("{indent}Scan [{table}{limit_str}]\n"));
40 }
41 }
42 LogicalPlan::Filter { input, predicate } => {
43 out.push_str(&format!("{indent}Filter [{predicate}]\n"));
44 explain_inner(input, depth + 1, out);
45 }
46 LogicalPlan::Project { input, columns } => {
47 let cols = columns.join(", ");
48 out.push_str(&format!("{indent}Project [{cols}]\n"));
49 explain_inner(input, depth + 1, out);
50 }
51 LogicalPlan::Join {
52 left,
53 right,
54 on,
55 join_type,
56 ..
57 } => {
58 let jt = match join_type {
59 JoinType::Inner => "INNER",
60 JoinType::Left => "LEFT",
61 JoinType::Right => "RIGHT",
62 JoinType::Full => "FULL",
63 JoinType::Cross => "CROSS",
64 };
65 if on.is_empty() {
66 out.push_str(&format!("{indent}Join [{jt}]\n"));
67 } else {
68 out.push_str(&format!("{indent}Join [{jt} ON {on}]\n"));
69 }
70 explain_inner(left, depth + 1, out);
71 explain_inner(right, depth + 1, out);
72 }
73 LogicalPlan::Aggregate {
74 input,
75 group_by,
76 aggregates,
77 } => {
78 let gb = group_by.join(", ");
79 let agg = aggregates.join(", ");
80 out.push_str(&format!(
81 "{indent}Aggregate [group_by=[{gb}] agg=[{agg}]]\n"
82 ));
83 explain_inner(input, depth + 1, out);
84 }
85 LogicalPlan::Sort { input, order_by } => {
86 let keys: Vec<String> = order_by
87 .iter()
88 .map(|s| {
89 if s.ascending {
90 format!("{} ASC", s.column)
91 } else {
92 format!("{} DESC", s.column)
93 }
94 })
95 .collect();
96 out.push_str(&format!("{indent}Sort [{}]\n", keys.join(", ")));
97 explain_inner(input, depth + 1, out);
98 }
99 LogicalPlan::Limit {
100 input,
101 count,
102 offset,
103 } => {
104 let mut parts = Vec::new();
105 if let Some(c) = count {
106 parts.push(format!("count={c}"));
107 }
108 if let Some(o) = offset {
109 parts.push(format!("offset={o}"));
110 }
111 out.push_str(&format!("{indent}Limit [{}]\n", parts.join(", ")));
112 explain_inner(input, depth + 1, out);
113 }
114 LogicalPlan::Values { columns, rows } => {
115 let cols = if columns.is_empty() {
116 String::new()
117 } else {
118 format!(" cols=[{}]", columns.join(", "))
119 };
120 out.push_str(&format!("{indent}Values [rows={rows}{cols}]\n"));
121 }
122 LogicalPlan::Empty => {
123 out.push_str(&format!("{indent}Empty\n"));
124 }
125 LogicalPlan::SetOp {
126 op,
127 all,
128 left,
129 right,
130 } => {
131 let op_str = match op {
132 SetOpType::Union => "UNION",
133 SetOpType::Intersect => "INTERSECT",
134 SetOpType::Except => "EXCEPT",
135 };
136 let all_str = if *all { " ALL" } else { "" };
137 out.push_str(&format!("{indent}SetOp [{op_str}{all_str}]\n"));
138 explain_inner(left, depth + 1, out);
139 explain_inner(right, depth + 1, out);
140 }
141 LogicalPlan::Cte {
142 name,
143 query,
144 recursive,
145 } => {
146 let rec = if *recursive { " RECURSIVE" } else { "" };
147 out.push_str(&format!("{indent}Cte [{name}{rec}]\n"));
148 explain_inner(query, depth + 1, out);
149 }
150 LogicalPlan::CteRef { name } => {
151 out.push_str(&format!("{indent}CteRef [{name}]\n"));
152 }
153 LogicalPlan::Window { input, functions } => {
154 let names: Vec<&str> = functions.iter().map(|f| f.name.as_str()).collect();
155 out.push_str(&format!("{indent}Window [{}]\n", names.join(", ")));
156 explain_inner(input, depth + 1, out);
157 }
158 LogicalPlan::Subquery { query, alias } => {
159 if let Some(a) = alias {
160 out.push_str(&format!("{indent}Subquery [alias={a}]\n"));
161 } else {
162 out.push_str(&format!("{indent}Subquery\n"));
163 }
164 explain_inner(query, depth + 1, out);
165 }
166 LogicalPlan::Exists { subquery, negated } => {
167 let neg = if *negated { "NOT " } else { "" };
168 out.push_str(&format!("{indent}Exists [{neg}EXISTS]\n"));
169 explain_inner(subquery, depth + 1, out);
170 }
171 LogicalPlan::InSubquery {
172 expr,
173 subquery,
174 negated,
175 } => {
176 let neg = if *negated { "NOT " } else { "" };
177 out.push_str(&format!("{indent}InSubquery [{expr} {neg}IN]\n"));
178 explain_inner(subquery, depth + 1, out);
179 }
180 }
181}