1use std::collections::HashMap;
11
12use crate::optimizer::analysis::{
13 ExpressionAnalysis, ExpressionAnalyzer, FingerprintCalculator, PlanFingerprint,
14 ReferenceCountAnalysis, SubplanId, SubplanReferenceInfo,
15};
16use crate::planning::plan::core::nodes::PlanNodeEnum;
17use linkrs_core::types::ContextualExpression;
18
19#[derive(Debug, Clone)]
21pub struct BatchPlanAnalysis {
22 pub reference_count: ReferenceCountAnalysis,
24 pub expression_summary: AggregatedExpressionAnalysis,
26 pub fingerprints: HashMap<i64, PlanFingerprint>,
28}
29
30#[derive(Debug, Clone, Default)]
32pub struct AggregatedExpressionAnalysis {
33 pub is_fully_deterministic: bool,
35 pub total_complexity: u32,
37 pub all_referenced_properties: Vec<String>,
39 pub all_referenced_variables: Vec<String>,
41 pub all_called_functions: Vec<String>,
43 pub contains_aggregates: bool,
45 pub contains_subqueries: bool,
47 pub total_expression_nodes: u32,
49}
50
51struct AnalysisContext {
53 fingerprint_calculator: FingerprintCalculator,
55 expression_analyzer: ExpressionAnalyzer,
57 fingerprint_map: HashMap<u64, SubplanReferenceInfo>,
59 node_fingerprint_map: HashMap<i64, u64>,
60 node_count_map: HashMap<i64, usize>,
61 expression_analyses: Vec<ExpressionAnalysis>,
63 fingerprints: HashMap<i64, PlanFingerprint>,
65}
66
67impl AnalysisContext {
68 fn new() -> Self {
69 Self {
70 fingerprint_calculator: FingerprintCalculator::new(),
71 expression_analyzer: ExpressionAnalyzer::new(),
72 fingerprint_map: HashMap::new(),
73 node_fingerprint_map: HashMap::new(),
74 node_count_map: HashMap::new(),
75 expression_analyses: Vec::new(),
76 fingerprints: HashMap::new(),
77 }
78 }
79
80 fn record_reference(&mut self, fingerprint: u64, node_id: i64, parent_id: Option<i64>) {
82 self.node_fingerprint_map.insert(node_id, fingerprint);
83
84 let info = self
85 .fingerprint_map
86 .entry(fingerprint)
87 .or_insert_with(|| SubplanReferenceInfo::new(SubplanId::new(fingerprint), node_id));
88
89 if let Some(parent) = parent_id {
90 info.add_reference(parent);
91 } else {
92 info.reference_count = info.reference_count.max(1);
93 }
94 }
95
96 fn record_node_count(&mut self, node_id: i64, count: usize) {
98 self.node_count_map.insert(node_id, count);
99 }
100
101 fn record_fingerprint(&mut self, node_id: i64, fingerprint: PlanFingerprint) {
103 self.fingerprints.insert(node_id, fingerprint);
104 }
105
106 fn analyze_expression(&mut self, ctx_expr: &ContextualExpression) {
108 let analysis = self.expression_analyzer.analyze(ctx_expr);
109 self.expression_analyses.push(analysis);
110 }
111
112 fn build_reference_analysis(&self) -> ReferenceCountAnalysis {
114 let mut repeated_subplans = Vec::new();
115 let mut node_reference_map = HashMap::new();
116
117 for (fingerprint, info) in &self.fingerprint_map {
118 if info.reference_count >= 2 {
119 let mut info_clone = info.clone();
120 if let Some(&count) = self.node_count_map.get(&info.root_node_id) {
121 info_clone.node_count = count;
122 }
123
124 for (node_id, fp) in &self.node_fingerprint_map {
125 if *fp == *fingerprint {
126 node_reference_map.insert(*node_id, info_clone.clone());
127 }
128 }
129
130 repeated_subplans.push(info_clone);
131 }
132 }
133
134 ReferenceCountAnalysis {
135 repeated_subplans,
136 node_reference_map,
137 }
138 }
139
140 fn build_expression_summary(&self) -> AggregatedExpressionAnalysis {
142 let mut summary = AggregatedExpressionAnalysis {
143 is_fully_deterministic: true,
144 ..Default::default()
145 };
146
147 let mut properties_set = std::collections::HashSet::new();
148 let mut variables_set = std::collections::HashSet::new();
149 let mut functions_set = std::collections::HashSet::new();
150
151 for analysis in &self.expression_analyses {
152 if !analysis.is_deterministic {
153 summary.is_fully_deterministic = false;
154 }
155 summary.total_complexity += analysis.complexity_score;
156 summary.total_expression_nodes += analysis.node_count;
157 summary.contains_aggregates |= analysis.contains_aggregate;
158 summary.contains_subqueries |= analysis.contains_subquery;
159
160 for prop in &analysis.referenced_properties {
161 properties_set.insert(prop.clone());
162 }
163 for var in &analysis.referenced_variables {
164 variables_set.insert(var.clone());
165 }
166 for func in &analysis.called_functions {
167 functions_set.insert(func.clone());
168 }
169 }
170
171 summary.all_referenced_properties = properties_set.into_iter().collect();
172 summary.all_referenced_variables = variables_set.into_iter().collect();
173 summary.all_called_functions = functions_set.into_iter().collect();
174
175 summary
176 }
177}
178
179#[derive(Debug, Clone, Default)]
181pub struct BatchPlanAnalyzer;
182
183impl BatchPlanAnalyzer {
184 pub fn new() -> Self {
186 Self
187 }
188
189 pub fn analyze(&self, root: &PlanNodeEnum) -> BatchPlanAnalysis {
198 let mut context = AnalysisContext::new();
199 self.analyze_recursive(root, &mut context, None);
200
201 BatchPlanAnalysis {
202 reference_count: context.build_reference_analysis(),
203 expression_summary: context.build_expression_summary(),
204 fingerprints: context.fingerprints,
205 }
206 }
207
208 fn analyze_recursive(
212 &self,
213 node: &PlanNodeEnum,
214 context: &mut AnalysisContext,
215 parent_id: Option<i64>,
216 ) -> usize {
217 let fingerprint = context.fingerprint_calculator.calculate_fingerprint(node);
219 let node_id = node.id();
220
221 context.record_fingerprint(node_id, fingerprint);
223
224 context.record_reference(fingerprint.value(), node_id, parent_id);
226
227 self.analyze_node_expressions(node, context);
229
230 let child_count = self.analyze_children(node, context, node_id);
232 let total_count = child_count + 1;
233
234 context.record_node_count(node_id, total_count);
236
237 total_count
238 }
239
240 fn analyze_node_expressions(&self, node: &PlanNodeEnum, context: &mut AnalysisContext) {
242 use crate::planning::plan::core::nodes::*;
243
244 match node {
245 PlanNodeEnum::Filter(n) => {
246 context.analyze_expression(n.condition());
247 }
248 PlanNodeEnum::Project(n) => {
249 for col in n.columns() {
250 context.analyze_expression(&col.expression);
251 }
252 }
253 PlanNodeEnum::Aggregate(n) => {
254 for key in n.group_keys() {
257 let _ = key;
260 }
261 }
262 PlanNodeEnum::Sort(n) => {
263 for _item in n.sort_items() {
266 }
269 }
270 PlanNodeEnum::InnerJoin(n) => {
271 for key in n.hash_keys() {
272 context.analyze_expression(key);
273 }
274 for key in n.probe_keys() {
275 context.analyze_expression(key);
276 }
277 }
278 PlanNodeEnum::LeftJoin(n) => {
279 for key in n.hash_keys() {
280 context.analyze_expression(key);
281 }
282 for key in n.probe_keys() {
283 context.analyze_expression(key);
284 }
285 }
286 PlanNodeEnum::Loop(n) => {
287 context.analyze_expression(n.condition());
288 }
289 PlanNodeEnum::Select(n) => {
290 context.analyze_expression(n.condition());
291 }
292 _ => {}
293 }
294 }
295
296 fn analyze_children(
298 &self,
299 node: &PlanNodeEnum,
300 context: &mut AnalysisContext,
301 node_id: i64,
302 ) -> usize {
303 use crate::planning::plan::core::nodes::*;
304
305 match node {
306 PlanNodeEnum::Start(_)
308 | PlanNodeEnum::GetVertices(_)
309 | PlanNodeEnum::GetEdges(_)
310 | PlanNodeEnum::GetNeighbors(_)
311 | PlanNodeEnum::ScanVertices(_)
312 | PlanNodeEnum::ScanEdges(_)
313 | PlanNodeEnum::IndexScan(_)
314 | PlanNodeEnum::Argument(_) => 0,
315
316 PlanNodeEnum::Filter(n) => self.analyze_recursive(n.input(), context, Some(node_id)),
318 PlanNodeEnum::Project(n) => self.analyze_recursive(n.input(), context, Some(node_id)),
319 PlanNodeEnum::Sort(n) => self.analyze_recursive(n.input(), context, Some(node_id)),
320 PlanNodeEnum::Limit(n) => self.analyze_recursive(n.input(), context, Some(node_id)),
321 PlanNodeEnum::TopN(n) => self.analyze_recursive(n.input(), context, Some(node_id)),
322 PlanNodeEnum::Sample(n) => self.analyze_recursive(n.input(), context, Some(node_id)),
323 PlanNodeEnum::Aggregate(n) => self.analyze_recursive(n.input(), context, Some(node_id)),
324 PlanNodeEnum::Dedup(n) => self.analyze_recursive(n.input(), context, Some(node_id)),
325 PlanNodeEnum::PassThrough(_) => {
326 0
328 }
329 PlanNodeEnum::DataCollect(n) => {
330 self.analyze_recursive(n.input(), context, Some(node_id))
331 }
332 PlanNodeEnum::Unwind(n) => self.analyze_recursive(n.input(), context, Some(node_id)),
333 PlanNodeEnum::Materialize(n) => {
334 self.analyze_recursive(n.input(), context, Some(node_id))
335 }
336
337 PlanNodeEnum::InnerJoin(n) => {
339 let left_count = self.analyze_recursive(n.left_input(), context, Some(node_id));
340 let right_count = self.analyze_recursive(n.right_input(), context, Some(node_id));
341 left_count + right_count
342 }
343 PlanNodeEnum::LeftJoin(n) => {
344 let left_count = self.analyze_recursive(n.left_input(), context, Some(node_id));
345 let right_count = self.analyze_recursive(n.right_input(), context, Some(node_id));
346 left_count + right_count
347 }
348 PlanNodeEnum::CrossJoin(n) => {
349 let left_count = self.analyze_recursive(n.left_input(), context, Some(node_id));
350 let right_count = self.analyze_recursive(n.right_input(), context, Some(node_id));
351 left_count + right_count
352 }
353 PlanNodeEnum::FullOuterJoin(n) => {
354 let left_count = self.analyze_recursive(n.left_input(), context, Some(node_id));
355 let right_count = self.analyze_recursive(n.right_input(), context, Some(node_id));
356 left_count + right_count
357 }
358
359 PlanNodeEnum::Union(n) => n
361 .dependencies()
362 .iter()
363 .map(|dep| self.analyze_recursive(dep, context, Some(node_id)))
364 .sum(),
365 PlanNodeEnum::Minus(n) => {
366 let main_count = self.analyze_recursive(n.input(), context, Some(node_id));
367 let minus_count = self.analyze_recursive(n.minus_input(), context, Some(node_id));
368 main_count + minus_count
369 }
370 PlanNodeEnum::Intersect(n) => {
371 let main_count = self.analyze_recursive(n.input(), context, Some(node_id));
372 let intersect_count =
373 self.analyze_recursive(n.intersect_input(), context, Some(node_id));
374 main_count + intersect_count
375 }
376
377 PlanNodeEnum::Expand(n) => n
379 .inputs()
380 .iter()
381 .map(|dep| self.analyze_recursive(dep, context, Some(node_id)))
382 .sum(),
383 PlanNodeEnum::ExpandAll(n) => n
384 .inputs()
385 .iter()
386 .map(|dep| self.analyze_recursive(dep, context, Some(node_id)))
387 .sum(),
388 PlanNodeEnum::Traverse(n) => {
389 n.dependencies()
392 .iter()
393 .map(|dep| self.analyze_recursive(dep, context, Some(node_id)))
394 .sum()
395 }
396 PlanNodeEnum::AppendVertices(n) => n
397 .inputs()
398 .iter()
399 .map(|dep| self.analyze_recursive(dep, context, Some(node_id)))
400 .sum(),
401
402 PlanNodeEnum::Loop(n) => {
404 let body_count = n
405 .body()
406 .as_ref()
407 .map(|body| self.analyze_recursive(body.as_ref(), context, Some(node_id)))
408 .unwrap_or(0);
409 body_count
410 }
411 PlanNodeEnum::RecursiveCte(n) => {
412 let anchor_count = self.analyze_recursive(n.anchor(), context, Some(node_id));
413 let step_count = n
414 .step()
415 .map(|step| self.analyze_recursive(step, context, Some(node_id)))
416 .unwrap_or(0);
417 anchor_count + step_count
418 }
419 PlanNodeEnum::Select(n) => {
420 let if_count = n
421 .if_branch()
422 .as_ref()
423 .map(|branch| self.analyze_recursive(branch.as_ref(), context, Some(node_id)))
424 .unwrap_or(0);
425 let else_count = n
426 .else_branch()
427 .as_ref()
428 .map(|branch| self.analyze_recursive(branch.as_ref(), context, Some(node_id)))
429 .unwrap_or(0);
430 if_count + else_count
431 }
432
433 PlanNodeEnum::PatternApply(n) => {
435 let left_count = self.analyze_recursive(n.left_input(), context, Some(node_id));
436 let right_count = self.analyze_recursive(n.right_input(), context, Some(node_id));
437 left_count + right_count
438 }
439 PlanNodeEnum::CorrelatedApply(n) => {
440 let left_count = self.analyze_recursive(n.left_input(), context, Some(node_id));
441 let right_count = self.analyze_recursive(n.right_input(), context, Some(node_id));
442 left_count + right_count
443 }
444 PlanNodeEnum::RollUpApply(n) => {
445 let left_count = self.analyze_recursive(n.left_input(), context, Some(node_id));
446 let right_count = self.analyze_recursive(n.right_input(), context, Some(node_id));
447 left_count + right_count
448 }
449
450 PlanNodeEnum::ShortestPath(n) => {
452 self.analyze_recursive(n.left_input(), context, Some(node_id))
453 + self.analyze_recursive(n.right_input(), context, Some(node_id))
454 }
455 PlanNodeEnum::MultiShortestPath(n) => {
456 self.analyze_recursive(n.left_input(), context, Some(node_id))
457 + self.analyze_recursive(n.right_input(), context, Some(node_id))
458 }
459 PlanNodeEnum::BFSShortest(n) => {
460 self.analyze_recursive(n.left_input(), context, Some(node_id))
461 + self.analyze_recursive(n.right_input(), context, Some(node_id))
462 }
463 PlanNodeEnum::AllPaths(n) => {
464 self.analyze_recursive(n.left_input(), context, Some(node_id))
465 + self.analyze_recursive(n.right_input(), context, Some(node_id))
466 }
467
468 PlanNodeEnum::Assign(n) => n
470 .dependencies()
471 .iter()
472 .map(|dep| self.analyze_recursive(dep, context, Some(node_id)))
473 .sum(),
474
475 _ => 0,
477 }
478 }
479}
480
481#[cfg(test)]
482mod tests {
483 use super::*;
484 use crate::planning::plan::core::nodes::StartNode;
485
486 #[test]
487 fn test_batch_analyzer_creation() {
488 let _analyzer = BatchPlanAnalyzer::new();
489 let _default_analyzer = BatchPlanAnalyzer;
490 }
491
492 #[test]
493 fn test_analyze_simple_plan() {
494 let analyzer = BatchPlanAnalyzer::new();
495 let root = PlanNodeEnum::Start(StartNode::new());
496
497 let result = analyzer.analyze(&root);
498
499 assert_eq!(result.reference_count.repeated_count(), 0);
500 assert!(result.expression_summary.is_fully_deterministic);
501 assert_eq!(result.expression_summary.total_expression_nodes, 0);
502 }
503}