Skip to main content

linkrs_query/optimizer/analysis/
reference_count.rs

1//! Reference Count Analysis Module
2//!
3//! Identify the sub-plan nodes that are referenced multiple times in the execution plan, to provide data support for the selection of the materialization strategy.
4
5use std::collections::HashMap;
6
7use crate::planning::plan::core::nodes::base::plan_node_traits::{
8    MultipleInputNode, SingleInputNode,
9};
10use crate::planning::plan::core::nodes::PlanNodeEnum;
11
12use super::fingerprint::FingerprintCalculator;
13
14/// Unique identifier for the sub-plan
15#[derive(Debug, Clone, PartialEq, Eq, Hash)]
16pub struct SubplanId(pub u64);
17
18impl SubplanId {
19    /// Create a new sub-plan ID.
20    pub fn new(value: u64) -> Self {
21        Self(value)
22    }
23
24    /// Obtain the ID value
25    pub fn value(&self) -> u64 {
26        self.0
27    }
28}
29
30/// Sub-plan reference information
31#[derive(Debug, Clone)]
32pub struct SubplanReferenceInfo {
33    /// The unique identifier of the sub-plan
34    pub subplan_id: SubplanId,
35    /// Sub-plan root node ID
36    pub root_node_id: i64,
37    /// Number of citations
38    pub reference_count: usize,
39    /// Reference location (list of parent node IDs)
40    pub reference_locations: Vec<i64>,
41    /// Estimate the number of output lines.
42    pub estimated_output_rows: u64,
43    /// The number of nodes included in the sub-plan
44    pub node_count: usize,
45}
46
47impl SubplanReferenceInfo {
48    /// Create new sub-plan reference information.
49    pub fn new(subplan_id: SubplanId, root_node_id: i64) -> Self {
50        Self {
51            subplan_id,
52            root_node_id,
53            reference_count: 0,
54            reference_locations: Vec::new(),
55            estimated_output_rows: 0,
56            node_count: 0,
57        }
58    }
59
60    /// Increase the reference count
61    pub fn add_reference(&mut self, location: i64) {
62        self.reference_count += 1;
63        if !self.reference_locations.contains(&location) {
64            self.reference_locations.push(location);
65        }
66    }
67}
68
69/// Results of reference count analysis
70#[derive(Debug, Clone)]
71pub struct ReferenceCountAnalysis {
72    /// All sub-plans that have been referenced multiple times (number of references >= 2)
73    pub repeated_subplans: Vec<SubplanReferenceInfo>,
74    /// Mapping from node IDs to reference information
75    pub node_reference_map: HashMap<i64, SubplanReferenceInfo>,
76}
77
78impl ReferenceCountAnalysis {
79    /// Create an empty analysis result.
80    pub fn new() -> Self {
81        Self {
82            repeated_subplans: Vec::new(),
83            node_reference_map: HashMap::new(),
84        }
85    }
86
87    /// Obtain the reference information for the specified node.
88    pub fn get_node_info(&self, node_id: i64) -> Option<&SubplanReferenceInfo> {
89        self.node_reference_map.get(&node_id)
90    }
91
92    /// Check whether the sub-plan is referenced multiple times.
93    pub fn is_repeated(&self, node_id: i64) -> bool {
94        self.node_reference_map
95            .get(&node_id)
96            .map(|info| info.reference_count >= 2)
97            .unwrap_or(false)
98    }
99
100    /// Obtain the number of sub-plans that have been referenced multiple times.
101    pub fn repeated_count(&self) -> usize {
102        self.repeated_subplans.len()
103    }
104}
105
106impl Default for ReferenceCountAnalysis {
107    fn default() -> Self {
108        Self::new()
109    }
110}
111
112/// Analyze the context
113struct AnalysisContext {
114    /// Mapping from fingerprints to reference information
115    fingerprint_map: HashMap<u64, SubplanReferenceInfo>,
116    /// Mapping from node IDs to fingerprints
117    node_fingerprint_map: HashMap<i64, u64>,
118    /// Node count mapping (used for estimating the size of sub-plans)
119    node_count_map: HashMap<i64, usize>,
120}
121
122impl AnalysisContext {
123    /// Create a new analysis context.
124    fn new() -> Self {
125        Self {
126            fingerprint_map: HashMap::new(),
127            node_fingerprint_map: HashMap::new(),
128            node_count_map: HashMap::new(),
129        }
130    }
131
132    /// Record citations
133    fn record_reference(&mut self, fingerprint: u64, node_id: i64, parent_id: Option<i64>) {
134        // Record the mapping from nodes to fingerprints.
135        self.node_fingerprint_map.insert(node_id, fingerprint);
136
137        // Obtain or create citation information.
138        let info = self
139            .fingerprint_map
140            .entry(fingerprint)
141            .or_insert_with(|| SubplanReferenceInfo::new(SubplanId::new(fingerprint), node_id));
142
143        // Increase the reference count
144        if let Some(parent) = parent_id {
145            info.add_reference(parent);
146        } else {
147            // Root node: The reference count is at least 1.
148            info.reference_count = info.reference_count.max(1);
149        }
150    }
151
152    /// Record the number of nodes.
153    fn record_node_count(&mut self, node_id: i64, count: usize) {
154        self.node_count_map.insert(node_id, count);
155    }
156
157    /// Analysis results:
158    fn into_analysis_result(self) -> ReferenceCountAnalysis {
159        let mut repeated_subplans = Vec::new();
160        let mut node_reference_map = HashMap::new();
161
162        for (fingerprint, mut info) in self.fingerprint_map {
163            // Retain only the sub-plans that are referenced multiple times.
164            if info.reference_count >= 2 {
165                // Update the number of nodes.
166                if let Some(&count) = self.node_count_map.get(&info.root_node_id) {
167                    info.node_count = count;
168                }
169
170                // Add reference information for all nodes that have the same fingerprint.
171                for (node_id, fp) in &self.node_fingerprint_map {
172                    if *fp == fingerprint {
173                        node_reference_map.insert(*node_id, info.clone());
174                    }
175                }
176
177                repeated_subplans.push(info);
178            }
179        }
180
181        ReferenceCountAnalysis {
182            repeated_subplans,
183            node_reference_map,
184        }
185    }
186}
187
188/// Reference Count Analyzer
189///
190/// Analyze the execution plan to identify the sub-plan nodes that are referenced multiple times.
191#[derive(Debug, Clone)]
192pub struct ReferenceCountAnalyzer {
193    /// Fingerprint calculator
194    fingerprint_calculator: FingerprintCalculator,
195}
196
197impl ReferenceCountAnalyzer {
198    /// Create a new reference counting analyzer
199    pub fn new() -> Self {
200        Self {
201            fingerprint_calculator: FingerprintCalculator::new(),
202        }
203    }
204
205    /// Analyze the reference count of the planning plan.
206    ///
207    /// # Parameters
208    /// The root node of the execution plan to be analyzed.
209    ///
210    /// # Return
211    /// Citation Count Analysis Results
212    ///
213    /// # Algorithms
214    /// Post-order traversal of a plan tree
215    /// 2. Calculate a structural fingerprint for each node.
216    /// 3. Count the number of occurrences of each fingerprint.
217    /// 4. Retrieve information about the sub-plans that have been referenced multiple times.
218    pub fn analyze(&self, plan: &PlanNodeEnum) -> ReferenceCountAnalysis {
219        let mut context = AnalysisContext::new();
220        self.analyze_recursive(plan, &mut context, None);
221        context.into_analysis_result()
222    }
223
224    /// Recursive analysis of the plan tree
225    ///
226    /// # Parameters
227    /// `node`: The current node
228    /// “Context” refers to the analysis of the surrounding circumstances, background, or information that is relevant to a particular situation or topic. In translation, understanding the context is crucial in order to provide an accurate and meaningful translation that captures the intended meaning of the original text. This may involve considering the context of the language, the cultural context, the historical context, or any other relevant factors that may affect the interpretation of the text.
229    /// `parent_id`: ID of the parent node (used to record the reference location)
230    ///
231    /// # Back
232    /// Number of nodes
233    fn analyze_recursive(
234        &self,
235        node: &PlanNodeEnum,
236        context: &mut AnalysisContext,
237        parent_id: Option<i64>,
238    ) -> usize {
239        // Calculate the fingerprint of the current node.
240        let fingerprint = self.fingerprint_calculator.calculate_fingerprint(node);
241        let node_id = node.id();
242
243        // Record the citation.
244        context.record_reference(fingerprint.value(), node_id, parent_id);
245
246        // Recursively analyze the child nodes and calculate the total number of nodes.
247        let child_count = match node {
248            // Single-input node - access child nodes via dependencies()
249            PlanNodeEnum::Filter(n) => {
250                let mut total = 1;
251                for dep in n.dependencies() {
252                    let count = self.analyze_recursive(dep, context, Some(node_id));
253                    total += count;
254                }
255                total
256            }
257            PlanNodeEnum::Project(n) => {
258                let mut total = 1;
259                for dep in n.dependencies() {
260                    let count = self.analyze_recursive(dep, context, Some(node_id));
261                    total += count;
262                }
263                total
264            }
265            PlanNodeEnum::Sort(n) => {
266                let mut total = 1;
267                for dep in n.dependencies() {
268                    let count = self.analyze_recursive(dep, context, Some(node_id));
269                    total += count;
270                }
271                total
272            }
273            PlanNodeEnum::Limit(n) => {
274                let mut total = 1;
275                for dep in n.dependencies() {
276                    let count = self.analyze_recursive(dep, context, Some(node_id));
277                    total += count;
278                }
279                total
280            }
281            PlanNodeEnum::TopN(n) => {
282                let mut total = 1;
283                for dep in n.dependencies() {
284                    let count = self.analyze_recursive(dep, context, Some(node_id));
285                    total += count;
286                }
287                total
288            }
289            PlanNodeEnum::Sample(n) => {
290                let mut total = 1;
291                for dep in n.dependencies() {
292                    let count = self.analyze_recursive(dep, context, Some(node_id));
293                    total += count;
294                }
295                total
296            }
297            PlanNodeEnum::Aggregate(n) => {
298                let mut total = 1;
299                for dep in n.dependencies() {
300                    let count = self.analyze_recursive(dep, context, Some(node_id));
301                    total += count;
302                }
303                total
304            }
305            PlanNodeEnum::Dedup(n) => {
306                let mut total = 1;
307                for dep in n.dependencies() {
308                    let count = self.analyze_recursive(dep, context, Some(node_id));
309                    total += count;
310                }
311                total
312            }
313            PlanNodeEnum::Unwind(n) => {
314                let mut total = 1;
315                for dep in n.dependencies() {
316                    let count = self.analyze_recursive(dep, context, Some(node_id));
317                    total += count;
318                }
319                total
320            }
321            PlanNodeEnum::DataCollect(n) => {
322                let mut total = 1;
323                for dep in n.dependencies() {
324                    let count = self.analyze_recursive(dep, context, Some(node_id));
325                    total += count;
326                }
327                total
328            }
329            PlanNodeEnum::Traverse(n) => {
330                // TraverseNode uses the SingleInputNode trait; input() panics when input is None
331                // Here, we directly access the `deps` field to traverse the child nodes.
332                let mut total = 1;
333                for dep in n.dependencies() {
334                    let count = self.analyze_recursive(dep, context, Some(node_id));
335                    total += count;
336                }
337                total
338            }
339            PlanNodeEnum::Expand(n) => {
340                // ExpandNode uses MultipleInputNode; access child nodes via inputs()
341                let mut total = 1;
342                for dep in n.inputs() {
343                    let count = self.analyze_recursive(dep, context, Some(node_id));
344                    total += count;
345                }
346                total
347            }
348            PlanNodeEnum::ExpandAll(n) => {
349                // ExpandAllNode uses MultipleInputNode; access child nodes via inputs()
350                let mut total = 1;
351                for dep in n.inputs() {
352                    let count = self.analyze_recursive(dep, context, Some(node_id));
353                    total += count;
354                }
355                total
356            }
357            PlanNodeEnum::AppendVertices(n) => {
358                // AppendVerticesNode uses MultipleInputNode; access child nodes via inputs()
359                let mut total = 1;
360                for dep in n.inputs() {
361                    let count = self.analyze_recursive(dep, context, Some(node_id));
362                    total += count;
363                }
364                total
365            }
366            // ArgumentNode and PassThroughNode are nodes that accept no input.
367            PlanNodeEnum::Argument(_) => 1,
368            PlanNodeEnum::PassThrough(_) => 1,
369            PlanNodeEnum::PatternApply(n) => {
370                let left_count = self.analyze_recursive(n.left_input(), context, Some(node_id));
371                let right_count = self.analyze_recursive(n.right_input(), context, Some(node_id));
372                1 + left_count + right_count
373            }
374            PlanNodeEnum::CorrelatedApply(n) => {
375                let left_count = self.analyze_recursive(n.left_input(), context, Some(node_id));
376                let right_count = self.analyze_recursive(n.right_input(), context, Some(node_id));
377                1 + left_count + right_count
378            }
379            PlanNodeEnum::RollUpApply(n) => {
380                let left_count = self.analyze_recursive(n.left_input(), context, Some(node_id));
381                let right_count = self.analyze_recursive(n.right_input(), context, Some(node_id));
382                1 + left_count + right_count
383            }
384            PlanNodeEnum::Assign(n) => {
385                let mut total = 1;
386                for dep in n.dependencies() {
387                    let count = self.analyze_recursive(dep, context, Some(node_id));
388                    total += count;
389                }
390                total
391            }
392            PlanNodeEnum::Minus(n) => {
393                let main_count = self.analyze_recursive(n.input(), context, Some(node_id));
394                let minus_count = self.analyze_recursive(n.minus_input(), context, Some(node_id));
395                1 + main_count + minus_count
396            }
397            PlanNodeEnum::Intersect(n) => {
398                let main_count = self.analyze_recursive(n.input(), context, Some(node_id));
399                let intersect_count =
400                    self.analyze_recursive(n.intersect_input(), context, Some(node_id));
401                1 + main_count + intersect_count
402            }
403
404            // Dual-input node
405            PlanNodeEnum::InnerJoin(n) => {
406                let left_count = self.analyze_recursive(n.left_input(), context, Some(node_id));
407                let right_count = self.analyze_recursive(n.right_input(), context, Some(node_id));
408                1 + left_count + right_count
409            }
410            PlanNodeEnum::LeftJoin(n) => {
411                let left_count = self.analyze_recursive(n.left_input(), context, Some(node_id));
412                let right_count = self.analyze_recursive(n.right_input(), context, Some(node_id));
413                1 + left_count + right_count
414            }
415            PlanNodeEnum::CrossJoin(n) => {
416                let left_count = self.analyze_recursive(n.left_input(), context, Some(node_id));
417                let right_count = self.analyze_recursive(n.right_input(), context, Some(node_id));
418                1 + left_count + right_count
419            }
420            PlanNodeEnum::FullOuterJoin(n) => {
421                let left_count = self.analyze_recursive(n.left_input(), context, Some(node_id));
422                let right_count = self.analyze_recursive(n.right_input(), context, Some(node_id));
423                1 + left_count + right_count
424            }
425            PlanNodeEnum::Union(n) => {
426                let mut total = 1;
427                for dep in n.dependencies() {
428                    let count = self.analyze_recursive(dep, context, Some(node_id));
429                    total += count;
430                }
431                total
432            }
433
434            // Add more nodes.
435            PlanNodeEnum::Select(n) => {
436                let mut total = 1; // Current node
437                if let Some(ref branch) = n.if_branch() {
438                    let count = self.analyze_recursive(branch, context, Some(node_id));
439                    total += count;
440                }
441                if let Some(ref branch) = n.else_branch() {
442                    let count = self.analyze_recursive(branch, context, Some(node_id));
443                    total += count;
444                }
445                total
446            }
447            PlanNodeEnum::Loop(n) => {
448                let mut total = 1; // Current node
449                if let Some(ref body) = n.body() {
450                    let count = self.analyze_recursive(body, context, Some(node_id));
451                    total += count;
452                }
453                total
454            }
455            PlanNodeEnum::RecursiveCte(n) => {
456                let mut total = 1; // Current node
457                total += self.analyze_recursive(n.anchor(), context, Some(node_id));
458                if let Some(step) = n.step() {
459                    total += self.analyze_recursive(step, context, Some(node_id));
460                }
461                total
462            }
463
464            // Zero-input nodes (leaf nodes)
465            _ => 1, // Only the current node.
466        };
467
468        // Record the number of nodes.
469        context.record_node_count(node_id, child_count);
470
471        child_count
472    }
473}
474
475impl Default for ReferenceCountAnalyzer {
476    fn default() -> Self {
477        Self::new()
478    }
479}
480
481#[cfg(test)]
482mod tests {
483    use super::*;
484
485    #[test]
486    fn test_reference_count_analyzer_new() {
487        let _analyzer = ReferenceCountAnalyzer::new();
488        // Verification of successful creation.
489    }
490
491    #[test]
492    fn test_subplan_id() {
493        let id = SubplanId::new(12345);
494        assert_eq!(id.value(), 12345);
495    }
496
497    #[test]
498    fn test_subplan_reference_info() {
499        let mut info = SubplanReferenceInfo::new(SubplanId::new(1), 100);
500        assert_eq!(info.reference_count, 0);
501
502        info.add_reference(200);
503        assert_eq!(info.reference_count, 1);
504        assert!(info.reference_locations.contains(&200));
505
506        info.add_reference(200); // Duplicate the addition.
507        assert_eq!(info.reference_count, 2);
508        assert_eq!(info.reference_locations.len(), 1); // Positions must not be duplicated.
509    }
510
511    #[test]
512    fn test_reference_count_analysis() {
513        let analysis = ReferenceCountAnalysis::new();
514        assert_eq!(analysis.repeated_count(), 0);
515        assert!(!analysis.is_repeated(1));
516    }
517}