1use std::{fmt::Display, sync::LazyLock};
5
6use bumpalo::Bump;
7use reifydb_core::{
8 common::JoinType,
9 interface::resolved::ResolvedShape,
10 value::column::{ColumnWithName, buffer::ColumnBuffer, columns::Columns},
11};
12use reifydb_rql::{
13 ast::parse_str,
14 nodes::AlterSequenceNode,
15 optimize::optimize_physical,
16 plan::{
17 logical::compile_logical,
18 physical::{
19 AggregateNode, AppendPhysicalNode, ApplyNode, AssertNode, DistinctNode, ExtendNode, FilterNode,
20 GateNode, JoinInnerNode, JoinLeftNode, JoinNaturalNode, MapNode, PatchNode, PhysicalPlan,
21 SortNode, TakeNode, compile_physical,
22 },
23 },
24};
25use reifydb_type::value::r#type::Type;
26
27use crate::{
28 procedure::rql::extract_query,
29 routine::{Routine, RoutineInfo, context::ProcedureContext, error::RoutineError},
30};
31
32static INFO: LazyLock<RoutineInfo> = LazyLock::new(|| RoutineInfo::new("rql::explain"));
33
34pub struct RqlExplain;
35
36impl Default for RqlExplain {
37 fn default() -> Self {
38 Self::new()
39 }
40}
41
42impl RqlExplain {
43 pub fn new() -> Self {
44 Self
45 }
46}
47
48impl<'a, 'tx> Routine<ProcedureContext<'a, 'tx>> for RqlExplain {
49 fn info(&self) -> &RoutineInfo {
50 &INFO
51 }
52
53 fn return_type(&self, _input_types: &[Type]) -> Type {
54 Type::Any
55 }
56
57 fn attaches_row_metadata(&self) -> bool {
58 false
59 }
60
61 fn execute(&self, ctx: &mut ProcedureContext<'a, 'tx>, _args: &Columns) -> Result<Columns, RoutineError> {
62 let query = extract_query(ctx.params, "rql::explain")?;
63
64 let bump = Bump::new();
65 let statements = parse_str(&bump, query.as_str())?;
66
67 let mut walker = PhysicalWalker::default();
68 for statement in statements {
69 let logical = compile_logical(&bump, ctx.catalog, ctx.tx, statement)?;
70 if let Some(mut plan) = compile_physical(&bump, ctx.catalog, ctx.tx, logical)? {
71 optimize_physical(&mut plan);
72 walker.walk(&plan, 0, None);
73 }
74 }
75
76 Ok(walker.into_columns())
77 }
78}
79
80#[derive(Default)]
81struct PhysicalWalker {
82 idx: Vec<i32>,
83 depth: Vec<i32>,
84 parent: Vec<Option<i32>>,
85 kind: Vec<String>,
86 detail: Vec<String>,
87}
88
89impl PhysicalWalker {
90 fn emit(&mut self, depth: i32, parent: Option<i32>, kind: &str, detail: String) -> i32 {
91 let next = self.idx.len() as i32;
92 self.idx.push(next);
93 self.depth.push(depth);
94 self.parent.push(parent);
95 self.kind.push(kind.to_string());
96 self.detail.push(detail);
97 next
98 }
99
100 fn into_columns(self) -> Columns {
101 Columns::new(vec![
102 ColumnWithName::int4("idx", self.idx),
103 ColumnWithName::int4("depth", self.depth),
104 ColumnWithName::new("parent", ColumnBuffer::int4_optional(self.parent)),
105 ColumnWithName::utf8("kind", self.kind),
106 ColumnWithName::utf8("detail", self.detail),
107 ])
108 }
109
110 fn walk(&mut self, plan: &PhysicalPlan<'_>, depth: i32, parent: Option<i32>) {
111 let (kind, detail) = describe(plan);
112 let me = self.emit(depth, parent, kind, detail);
113 self.recurse_children(plan, depth + 1, Some(me));
114 }
115
116 fn recurse_children(&mut self, plan: &PhysicalPlan<'_>, depth: i32, parent: Option<i32>) {
117 match plan {
118 PhysicalPlan::Aggregate(AggregateNode {
119 input,
120 ..
121 }) => self.walk(input, depth, parent),
122 PhysicalPlan::Filter(FilterNode {
123 input,
124 ..
125 }) => self.walk(input, depth, parent),
126 PhysicalPlan::Gate(GateNode {
127 input,
128 ..
129 }) => self.walk(input, depth, parent),
130 PhysicalPlan::Assert(AssertNode {
131 input: Some(input),
132 ..
133 }) => {
134 self.walk(input, depth, parent);
135 }
136 PhysicalPlan::Take(TakeNode {
137 input,
138 ..
139 }) => self.walk(input, depth, parent),
140 PhysicalPlan::Sort(SortNode {
141 input,
142 ..
143 }) => self.walk(input, depth, parent),
144 PhysicalPlan::Map(MapNode {
145 input: Some(input),
146 ..
147 }) => {
148 self.walk(input, depth, parent);
149 }
150 PhysicalPlan::Extend(ExtendNode {
151 input: Some(input),
152 ..
153 }) => {
154 self.walk(input, depth, parent);
155 }
156 PhysicalPlan::Patch(PatchNode {
157 input: Some(input),
158 ..
159 }) => {
160 self.walk(input, depth, parent);
161 }
162 PhysicalPlan::JoinInner(JoinInnerNode {
163 left,
164 right,
165 ..
166 }) => {
167 self.walk(left, depth, parent);
168 self.walk(right, depth, parent);
169 }
170 PhysicalPlan::JoinLeft(JoinLeftNode {
171 left,
172 right,
173 ..
174 }) => {
175 self.walk(left, depth, parent);
176 self.walk(right, depth, parent);
177 }
178 PhysicalPlan::JoinNatural(JoinNaturalNode {
179 left,
180 right,
181 ..
182 }) => {
183 self.walk(left, depth, parent);
184 self.walk(right, depth, parent);
185 }
186 PhysicalPlan::Apply(ApplyNode {
187 input: Some(input),
188 ..
189 }) => {
190 self.walk(input, depth, parent);
191 }
192 PhysicalPlan::Distinct(DistinctNode {
193 input,
194 ..
195 }) => self.walk(input, depth, parent),
196 PhysicalPlan::Window(node) => {
197 if let Some(input) = &node.input {
198 self.walk(input, depth, parent);
199 }
200 }
201 PhysicalPlan::Conditional(node) => {
202 self.walk(&node.then_branch, depth, parent);
203 for else_if in node.else_ifs.iter() {
204 self.walk(&else_if.then_branch, depth, parent);
205 }
206 if let Some(else_branch) = &node.else_branch {
207 self.walk(else_branch, depth, parent);
208 }
209 }
210 PhysicalPlan::Scalarize(scalarize) => self.walk(&scalarize.input, depth, parent),
211 PhysicalPlan::DefineFunction(def) => {
212 for child in def.body.iter() {
213 self.walk(child, depth, parent);
214 }
215 }
216 PhysicalPlan::Append(AppendPhysicalNode::Query {
217 left,
218 right,
219 }) => {
220 self.walk(left, depth, parent);
221 self.walk(right, depth, parent);
222 }
223 _ => {}
224 }
225 }
226}
227
228fn describe(plan: &PhysicalPlan<'_>) -> (&'static str, String) {
229 match plan {
230 PhysicalPlan::Loop(_) => ("Loop", String::new()),
231 PhysicalPlan::While(_) => ("While", String::new()),
232 PhysicalPlan::For(_) => ("For", String::new()),
233 PhysicalPlan::Break => ("Break", String::new()),
234 PhysicalPlan::Continue => ("Continue", String::new()),
235 PhysicalPlan::CreateDeferredView(_) => ("CreateDeferredView", String::new()),
236 PhysicalPlan::CreateTransactionalView(_) => ("CreateTransactionalView", String::new()),
237 PhysicalPlan::CreateNamespace(_) => ("CreateNamespace", String::new()),
238 PhysicalPlan::CreateRemoteNamespace(_) => ("CreateRemoteNamespace", String::new()),
239 PhysicalPlan::CreateTable(_) => ("CreateTable", String::new()),
240 PhysicalPlan::CreateRingBuffer(_) => ("CreateRingBuffer", String::new()),
241 PhysicalPlan::CreateDictionary(_) => ("CreateDictionary", String::new()),
242 PhysicalPlan::CreateSumType(_) => ("CreateSumType", String::new()),
243 PhysicalPlan::CreateSubscription(_) => ("CreateSubscription", String::new()),
244 PhysicalPlan::DropNamespace(_) => ("DropNamespace", String::new()),
245 PhysicalPlan::DropTable(_) => ("DropTable", String::new()),
246 PhysicalPlan::DropView(_) => ("DropView", String::new()),
247 PhysicalPlan::DropRingBuffer(_) => ("DropRingBuffer", String::new()),
248 PhysicalPlan::DropDictionary(_) => ("DropDictionary", String::new()),
249 PhysicalPlan::DropSumType(_) => ("DropSumType", String::new()),
250 PhysicalPlan::DropSubscription(_) => ("DropSubscription", String::new()),
251 PhysicalPlan::DropSeries(_) => ("DropSeries", String::new()),
252 PhysicalPlan::DropProcedure(_) => ("DropProcedure", String::new()),
253 PhysicalPlan::DropHandler(_) => ("DropHandler", String::new()),
254 PhysicalPlan::DropTest(_) => ("DropTest", String::new()),
255 PhysicalPlan::CreateSource(_) => ("CreateSource", String::new()),
256 PhysicalPlan::CreateSink(_) => ("CreateSink", String::new()),
257 PhysicalPlan::CreateBinding(_) => ("CreateBinding", String::new()),
258 PhysicalPlan::DropSource(_) => ("DropSource", String::new()),
259 PhysicalPlan::DropSink(_) => ("DropSink", String::new()),
260 PhysicalPlan::DropBinding(_) => ("DropBinding", String::new()),
261 PhysicalPlan::CreateIdentity(n) => ("CreateIdentity", format!("name={}", n.name.text())),
262 PhysicalPlan::CreateRole(n) => ("CreateRole", format!("name={}", n.name.text())),
263 PhysicalPlan::Grant(n) => ("Grant", format!("role={} user={}", n.role.text(), n.user.text())),
264 PhysicalPlan::Revoke(n) => ("Revoke", format!("role={} user={}", n.role.text(), n.user.text())),
265 PhysicalPlan::DropIdentity(n) => {
266 ("DropIdentity", format!("name={} if_exists={}", n.name.text(), n.if_exists))
267 }
268 PhysicalPlan::DropRole(n) => ("DropRole", format!("name={} if_exists={}", n.name.text(), n.if_exists)),
269 PhysicalPlan::CreateAuthentication(n) => {
270 ("CreateAuthentication", format!("user={} method={}", n.user.text(), n.method.text()))
271 }
272 PhysicalPlan::DropAuthentication(n) => (
273 "DropAuthentication",
274 format!("user={} method={} if_exists={}", n.user.text(), n.method.text(), n.if_exists),
275 ),
276 PhysicalPlan::CreatePolicy(n) => {
277 let name = n.name.as_ref().map(|f| f.text()).unwrap_or("<unnamed>");
278 ("CreatePolicy", format!("name={} type={}", name, n.target_type))
279 }
280 PhysicalPlan::AlterPolicy(n) => ("AlterPolicy", format!("name={} enabled={}", n.name.text(), n.enable)),
281 PhysicalPlan::DropPolicy(n) => {
282 ("DropPolicy", format!("name={} if_exists={}", n.name.text(), n.if_exists))
283 }
284 PhysicalPlan::AlterSequence(AlterSequenceNode {
285 sequence,
286 column,
287 value,
288 }) => ("AlterSequence", format!("{}.{} = {}", sequence.def().name, column.name(), value)),
289 PhysicalPlan::Delete(_) => ("Delete", String::new()),
290 PhysicalPlan::DeleteRingBuffer(_) => ("DeleteRingBuffer", String::new()),
291 PhysicalPlan::InsertTable(_) => ("InsertTable", String::new()),
292 PhysicalPlan::InsertRingBuffer(_) => ("InsertRingBuffer", String::new()),
293 PhysicalPlan::InsertDictionary(_) => ("InsertDictionary", String::new()),
294 PhysicalPlan::DeleteSeries(_) => ("DeleteSeries", String::new()),
295 PhysicalPlan::InsertSeries(_) => ("InsertSeries", String::new()),
296 PhysicalPlan::Update(_) => ("Update", String::new()),
297 PhysicalPlan::UpdateRingBuffer(_) => ("UpdateRingBuffer", String::new()),
298 PhysicalPlan::UpdateSeries(_) => ("UpdateSeries", String::new()),
299 PhysicalPlan::Aggregate(AggregateNode {
300 by,
301 map,
302 ..
303 }) => {
304 let by_str = by.iter().map(|e| e.to_string()).collect::<Vec<_>>().join(", ");
305 let map_str = map.iter().map(|e| e.to_string()).collect::<Vec<_>>().join(", ");
306 ("Aggregate", format!("by=[{}] map=[{}]", by_str, map_str))
307 }
308 PhysicalPlan::Filter(FilterNode {
309 conditions,
310 ..
311 }) => ("Filter", expressions_inline(conditions)),
312 PhysicalPlan::Gate(GateNode {
313 conditions,
314 ..
315 }) => ("Gate", expressions_inline(conditions)),
316 PhysicalPlan::Assert(AssertNode {
317 conditions,
318 message,
319 ..
320 }) => {
321 let cond = expressions_inline(conditions);
322 match message {
323 Some(msg) => ("Assert", format!("\"{}\" [{}]", msg, cond)),
324 None => ("Assert", format!("[{}]", cond)),
325 }
326 }
327 PhysicalPlan::AssertBlock(node) => {
328 let kind = if node.expect_error {
329 "AssertError"
330 } else {
331 "AssertBlock"
332 };
333 let msg = node.message.as_deref().unwrap_or("");
334 (kind, format!("\"{}\"", msg))
335 }
336 PhysicalPlan::Take(TakeNode {
337 take,
338 ..
339 }) => ("Take", take.to_string()),
340 PhysicalPlan::Sort(SortNode {
341 by,
342 ..
343 }) => ("Sort", expressions_inline(by)),
344 PhysicalPlan::Map(MapNode {
345 map,
346 ..
347 }) => ("Map", expressions_inline(map)),
348 PhysicalPlan::Extend(ExtendNode {
349 extend,
350 ..
351 }) => ("Extend", expressions_inline(extend)),
352 PhysicalPlan::Patch(PatchNode {
353 assignments,
354 ..
355 }) => ("Patch", expressions_inline(assignments)),
356 PhysicalPlan::JoinInner(JoinInnerNode {
357 on,
358 ..
359 }) => ("JoinInner", format!("on=[{}]", expressions_inline(on))),
360 PhysicalPlan::JoinLeft(JoinLeftNode {
361 on,
362 ..
363 }) => ("JoinLeft", format!("on=[{}]", expressions_inline(on))),
364 PhysicalPlan::JoinNatural(JoinNaturalNode {
365 join_type,
366 ..
367 }) => {
368 let kind = match join_type {
369 JoinType::Inner => "Inner",
370 JoinType::Left => "Left",
371 };
372 ("JoinNatural", format!("type={}", kind))
373 }
374 PhysicalPlan::IndexScan(node) => (
375 "IndexScan",
376 format!("{}::{}::{}", node.source.namespace().name(), node.source.name(), node.index_name),
377 ),
378 PhysicalPlan::TableScan(node) => {
379 ("TableScan", format!("{}::{}", node.source.namespace().name(), node.source.name()))
380 }
381 PhysicalPlan::ViewScan(node) => {
382 ("ViewScan", format!("{}::{}", node.source.namespace().name(), node.source.name()))
383 }
384 PhysicalPlan::RingBufferScan(node) => {
385 ("RingBufferScan", format!("{}::{}", node.source.namespace().name(), node.source.name()))
386 }
387 PhysicalPlan::DictionaryScan(node) => {
388 ("DictionaryScan", format!("{}::{}", node.source.namespace().name(), node.source.name()))
389 }
390 PhysicalPlan::SeriesScan(node) => {
391 ("SeriesScan", format!("{}.{}", node.source.namespace().name(), node.source.name()))
392 }
393 PhysicalPlan::Apply(ApplyNode {
394 operator,
395 expressions,
396 ..
397 }) => {
398 let summary = if expressions.is_empty() {
399 "no args".to_string()
400 } else {
401 format!("{} args", expressions.len())
402 };
403 ("Apply", format!("operator={} {}", operator.text(), summary))
404 }
405 PhysicalPlan::RemoteScan(node) => (
406 "RemoteScan",
407 format!(
408 "{}::{} @ {} rql=\"{}\"",
409 node.local_namespace, node.remote_name, node.address, node.remote_rql
410 ),
411 ),
412 PhysicalPlan::InlineData(node) => {
413 let total_fields: usize = node.rows.iter().map(|row| row.len()).sum();
414 ("InlineData", format!("rows={} fields={}", node.rows.len(), total_fields))
415 }
416 PhysicalPlan::Distinct(DistinctNode {
417 columns,
418 ..
419 }) => {
420 let detail = if columns.is_empty() {
421 "primary key".to_string()
422 } else {
423 columns.iter().map(|c| c.name().to_string()).collect::<Vec<_>>().join(", ")
424 };
425 ("Distinct", detail)
426 }
427 PhysicalPlan::CreatePrimaryKey(_) => ("CreatePrimaryKey", String::new()),
428 PhysicalPlan::CreateColumnProperty(_) => ("CreateColumnProperty", String::new()),
429 PhysicalPlan::CreateProcedure(_) => ("CreateProcedure", String::new()),
430 PhysicalPlan::CreateSeries(_) => ("CreateSeries", String::new()),
431 PhysicalPlan::CreateEvent(_) => ("CreateEvent", String::new()),
432 PhysicalPlan::CreateTag(_) => ("CreateTag", String::new()),
433 PhysicalPlan::CreateTest(_) => ("CreateTest", String::new()),
434 PhysicalPlan::RunTests(_) => ("RunTests", String::new()),
435 PhysicalPlan::CreateMigration(_) => ("CreateMigration", String::new()),
436 PhysicalPlan::Migrate(_) => ("Migrate", String::new()),
437 PhysicalPlan::RollbackMigration(_) => ("RollbackMigration", String::new()),
438 PhysicalPlan::Dispatch(_) => ("Dispatch", String::new()),
439 PhysicalPlan::AlterTable(node) => {
440 ("AlterTable", format!("{}.{}", node.namespace.name(), node.table.text()))
441 }
442 PhysicalPlan::AlterRemoteNamespace(_) => ("AlterRemoteNamespace", String::new()),
443 PhysicalPlan::TableVirtualScan(node) => {
444 ("TableVirtualScan", format!("{}::{}", node.source.namespace().name(), node.source.name()))
445 }
446 PhysicalPlan::Generator(node) => ("Generator", node.name.text().to_string()),
447 PhysicalPlan::Window(node) => ("Window", format!("kind={:?}", node.kind)),
448 PhysicalPlan::Declare(node) => ("Declare", format!("{} = {}", node.name.text(), node.value)),
449 PhysicalPlan::Assign(node) => ("Assign", format!("{} = {}", node.name.text(), node.value)),
450 PhysicalPlan::Variable(node) => ("Variable", node.variable_expr.fragment.text().to_string()),
451 PhysicalPlan::Conditional(node) => ("Conditional", format!("if {}", node.condition)),
452 PhysicalPlan::Scalarize(_) => ("Scalarize", String::new()),
453 PhysicalPlan::Environment(_) => ("Environment", String::new()),
454 PhysicalPlan::RowPointLookup(lookup) => {
455 let source_name = source_name_of(&lookup.source);
456 ("RowPointLookup", format!("source={} row={}", source_name, lookup.row_number))
457 }
458 PhysicalPlan::RowListLookup(lookup) => {
459 let source_name = source_name_of(&lookup.source);
460 let rows = lookup.row_numbers.iter().map(|n| n.to_string()).collect::<Vec<_>>().join(", ");
461 ("RowListLookup", format!("source={} rows=[{}]", source_name, rows))
462 }
463 PhysicalPlan::RowRangeScan(scan) => {
464 let source_name = source_name_of(&scan.source);
465 ("RowRangeScan", format!("source={} range={}..={}", source_name, scan.start, scan.end))
466 }
467 PhysicalPlan::DefineFunction(def) => {
468 let params: Vec<String> = def
469 .parameters
470 .iter()
471 .map(|p| {
472 if let Some(ref tc) = p.type_constraint {
473 format!("${}: {:?}", p.name.text(), tc)
474 } else {
475 format!("${}", p.name.text())
476 }
477 })
478 .collect();
479 let ret = if let Some(ref rt) = def.return_type {
480 format!(" -> {:?}", rt)
481 } else {
482 String::new()
483 };
484 ("DefineFunction", format!("{}[{}]{}", def.name.text(), params.join(", "), ret))
485 }
486 PhysicalPlan::Return(ret) => {
487 let value = ret.value.as_ref().map(|expr| expr.to_string()).unwrap_or_default();
488 ("Return", value)
489 }
490 PhysicalPlan::CallFunction(call) => {
491 let args: Vec<String> = call.arguments.iter().map(|a| format!("{}", a)).collect();
492 ("CallFunction", format!("{}({})", call.name.text(), args.join(", ")))
493 }
494 PhysicalPlan::Append(node) => match node {
495 AppendPhysicalNode::IntoVariable {
496 target,
497 ..
498 } => ("Append", format!("${}", target.text())),
499 AppendPhysicalNode::Query {
500 ..
501 } => ("Append", String::new()),
502 },
503 PhysicalPlan::DefineClosure(node) => {
504 let params: Vec<String> = node
505 .parameters
506 .iter()
507 .map(|p| {
508 if let Some(ref tc) = p.type_constraint {
509 format!("${}: {:?}", p.name.text(), tc)
510 } else {
511 format!("${}", p.name.text())
512 }
513 })
514 .collect();
515 ("DefineClosure", format!("[{}]", params.join(", ")))
516 }
517 }
518}
519
520fn source_name_of(source: &ResolvedShape) -> String {
521 match source {
522 ResolvedShape::Table(t) => t.identifier().text().to_string(),
523 ResolvedShape::View(v) => v.identifier().text().to_string(),
524 ResolvedShape::RingBuffer(rb) => rb.identifier().text().to_string(),
525 _ => "unknown".to_string(),
526 }
527}
528
529fn expressions_inline<E: Display>(items: &[E]) -> String {
530 items.iter().map(|e| e.to_string()).collect::<Vec<_>>().join(", ")
531}