Skip to main content

akar_processor/processor/
plan_serializer.rs

1use akar_planner::logical_operator::LogicalOperator;
2
3#[allow(unreachable_patterns)]
4pub fn serialize_plan_tree(op: &LogicalOperator, depth: usize) -> String {
5    let indent = "  ".repeat(depth);
6    let prefix = if depth > 0 { "├─ " } else { "" };
7
8    let op_name = match op {
9        LogicalOperator::ScanNode(s) => match &s.fts_query {
10            Some(fq) => format!("ScanNode({}) FTS[{}({})]", s.table_name, fq.index_name, fq.query_string),
11            None => format!("ScanNode({})", s.table_name),
12        },
13        LogicalOperator::ScanRel(s) => format!("ScanRel({})", s.table_name),
14        LogicalOperator::Filter(_) => "Filter".to_string(),
15        LogicalOperator::Projection(p) => format!("Projection({} cols)", p.expressions.len()),
16        LogicalOperator::HashJoin(hj) => format!("HashJoin({} keys)", hj.join_keys.len()),
17        LogicalOperator::CrossProduct(_) => "CrossProduct".to_string(),
18        LogicalOperator::OrderBy(ob) => format!("OrderBy({} keys)", ob.sort_keys.len()),
19        LogicalOperator::TopK(tk) => format!(
20            "TopK(limit={}, offset={}, {} keys)",
21            tk.limit,
22            tk.offset,
23            tk.sort_keys.len()
24        ),
25        LogicalOperator::Limit(l) => format!("Limit({})", l.limit),
26        LogicalOperator::Aggregate(a) => {
27            format!("Aggregate({} aggs, {} group_by)", a.aggregates.len(), a.group_by.len())
28        }
29        LogicalOperator::Union(u) => format!("Union({})", if u.all { "ALL" } else { "DISTINCT" }),
30        LogicalOperator::Flatten(_) => "Flatten".to_string(),
31        LogicalOperator::TableFunctionCall(tf) => format!("TableFunctionCall({})", tf.function_name),
32        LogicalOperator::CopyFrom(cf) => format!("CopyFrom({})", cf.table_name),
33        LogicalOperator::BatchInsert(bi) => format!("BatchInsert({}, {} rows)", bi.table_name, bi.rows.len()),
34        LogicalOperator::IndexLookup(il) => format!("IndexLookup({})", il.table_name),
35        LogicalOperator::Delete(dl) => format!("Delete({})", dl.table_name),
36        LogicalOperator::Set(sl) => format!(
37            "Set({}.{})",
38            sl.table_name,
39            sl.items.first().map(|i| i.column_name.as_str()).unwrap_or("?")
40        ),
41        LogicalOperator::OptionalMatch(_) => "OptionalMatch".to_string(),
42        LogicalOperator::OptionalExtend(oe) => {
43            format!("OptionalExtend({} via {})", oe.rel_var, oe.rel_table_name)
44        }
45        LogicalOperator::Unwind(uw) => format!("Unwind({})", uw.variable),
46        LogicalOperator::Foreach(fe) => format!("Foreach({})", fe.variable),
47        LogicalOperator::Merge(m) => format!("Merge({})", m.table_name),
48        LogicalOperator::MergeRel(mr) => format!("MergeRel({})", mr.rel_table_name),
49        LogicalOperator::SemiJoin(_) => "SemiJoin".to_string(),
50        LogicalOperator::AntiJoin(_) => "AntiJoin".to_string(),
51        LogicalOperator::VectorSimilarityScan(vs) => format!("VectorSimilarityScan(k={})", vs.top_k),
52        LogicalOperator::ArtIndexRangeScan(ars) => format!("ArtIndexRangeScan({})", ars.table_name),
53        LogicalOperator::Explain(_) => "Explain".to_string(),
54        LogicalOperator::Intersect(_) => "Intersect".to_string(),
55        LogicalOperator::RecursiveExtend(re) => {
56            format!("RecursiveExtend({}..{})", re.lower_bound, re.upper_bound)
57        }
58        LogicalOperator::Accumulate(ac) => format!("Accumulate({:?})", ac.accumulate_type),
59        LogicalOperator::ExpressionsScan(es) => format!("ExpressionsScan({} vars)", es.expressions.len()),
60        LogicalOperator::CountRelTable(crt) => format!("CountRelTable({})", crt.table_name),
61        LogicalOperator::CreateNodeTable(ct) => format!("CreateNodeTable({})", ct.name),
62        LogicalOperator::CreateRelTable(ct) => format!("CreateRelTable({})", ct.name),
63        LogicalOperator::DropTable(dt) => format!("DropTable({})", dt.name),
64        LogicalOperator::AlterTable(at) => format!("AlterTable({})", at.table_name),
65        LogicalOperator::CreateIndex(ci) => format!("CreateIndex({})", ci.index_name),
66        LogicalOperator::DropIndex(di) => format!("DropIndex({})", di.index_name),
67        LogicalOperator::CreateVectorIndex(vi) => format!("CreateVectorIndex({})", vi.index_name),
68        LogicalOperator::CreateSequence(cs) => format!("CreateSequence({})", cs.name),
69        LogicalOperator::DropSequence(ds) => format!("DropSequence({})", ds.name),
70        LogicalOperator::CreateDml(cd) => format!("CreateDml({})", cd.table_name),
71        LogicalOperator::CreateNode(cn) => format!("CreateNode({})", cn.table_name),
72        LogicalOperator::CreateRel(cr) => format!("CreateRel({})", cr.table_name),
73        LogicalOperator::Extend(ex) => match &ex.fts_query {
74            Some(fq) => format!(
75                "Extend({}->{} via {}) FTS[{}({})]",
76                ex.bound_node_var, ex.dst_node_var, ex.rel_table_name, fq.index_name, fq.query_string
77            ),
78            None => format!(
79                "Extend({}->{} via {})",
80                ex.bound_node_var, ex.dst_node_var, ex.rel_table_name
81            ),
82        },
83        LogicalOperator::ExportDatabase(ed) => format!("ExportDatabase({})", ed.file_path),
84        LogicalOperator::ImportDatabase(id) => format!("ImportDatabase({})", id.file_path),
85        LogicalOperator::CreateFtsIndex(c) => format!("CreateFtsIndex({})", c.index_name),
86        LogicalOperator::FtsScan(s) => format!("FtsScan({})", s.index_name),
87        _ => format!("{:?}", op),
88    };
89
90    let card_str = format!("[cardinality={}]", op.cardinality());
91    let mut result = format!("{indent}{prefix}{op_name} {card_str}\n");
92
93    let children = op.children();
94    for (i, child) in children.iter().enumerate() {
95        let child_str = serialize_plan_tree(child, depth + 1);
96        if i == children.len() - 1 {
97            let adjusted = child_str.replacen("├─ ", "└─ ", 1);
98            result.push_str(&adjusted);
99        } else {
100            result.push_str(&child_str);
101        }
102    }
103
104    result
105}