Skip to main content

linkrs_query/optimizer/heuristic/
result.rs

1//! Rewrite the definition of the result.
2//!
3//! Define the return type of the rule that rewrites the content.
4//! This is a simplified version that has been separated from the optimizer layer.
5
6/// Rewrite the error type
7#[derive(Debug, thiserror::Error)]
8pub enum RewriteError {
9    #[error("Invalid plan node: {0}")]
10    InvalidNode(String),
11
12    #[error("Rewrite failed: {0}")]
13    RewriteFailed(String),
14
15    #[error("Unsupported node types: {0}")]
16    UnsupportedNodeType(String),
17
18    #[error("Optimizer error: {0}")]
19    OptimizerError(String),
20
21    #[error("Loop detection: node {0}")]
22    CycleDetected(usize),
23
24    #[error("Invalid program structure: {0}")]
25    InvalidPlanStructure(String),
26}
27
28impl RewriteError {
29    pub fn invalid_node(msg: impl Into<String>) -> Self {
30        Self::InvalidNode(msg.into())
31    }
32
33    pub fn rewrite_failed(msg: impl Into<String>) -> Self {
34        Self::RewriteFailed(msg.into())
35    }
36
37    pub fn unsupported_node_type(name: impl Into<String>) -> Self {
38        Self::UnsupportedNodeType(name.into())
39    }
40
41    pub fn optimizer_error(msg: impl Into<String>) -> Self {
42        Self::OptimizerError(msg.into())
43    }
44
45    pub fn cycle_detected(node_id: usize) -> Self {
46        Self::CycleDetected(node_id)
47    }
48
49    pub fn invalid_plan_structure(msg: impl Into<String>) -> Self {
50        Self::InvalidPlanStructure(msg.into())
51    }
52}
53
54/// Rewrite the result in the desired format.
55pub type RewriteResult<T> = std::result::Result<T, RewriteError>;
56
57/// Record the results after the application of the rule rewriting rules.
58#[derive(Debug, Default, Clone)]
59pub struct TransformResult {
60    /// Should the current node be deleted?
61    pub erase_curr: bool,
62    /// Should all related nodes be deleted?
63    pub erase_all: bool,
64    /// New list of replacement plan nodes
65    pub new_nodes: Vec<crate::planning::plan::PlanNodeEnum>,
66    /// New dependencies
67    pub new_dependencies: Vec<usize>,
68    /// Whether the rule folded constant expressions into literals.
69    pub has_folded: bool,
70}
71
72impl TransformResult {
73    /// Create a new conversion result.
74    pub fn new() -> Self {
75        Self::default()
76    }
77
78    /// Set whether to delete the current node.
79    pub fn with_erase_curr(mut self, erase_curr: bool) -> Self {
80        self.erase_curr = erase_curr;
81        self
82    }
83
84    /// Set whether to delete all nodes.
85    pub fn with_erase_all(mut self, erase_all: bool) -> Self {
86        self.erase_all = erase_all;
87        self
88    }
89
90    /// Add a new planning node
91    pub fn add_new_node(&mut self, node: crate::planning::plan::PlanNodeEnum) {
92        self.new_nodes.push(node);
93    }
94
95    /// Add a new dependency
96    pub fn add_new_dependency(&mut self, dep_id: usize) {
97        self.new_dependencies.push(dep_id);
98    }
99
100    /// Set the deleted marker
101    pub fn with_erased(mut self) -> Self {
102        self.erase_curr = true;
103        self
104    }
105
106    /// Mark that the rule folded constant expressions into literals.
107    pub fn mark_folded(&mut self) {
108        self.has_folded = true;
109    }
110
111    /// Check whether there are any new nodes.
112    pub fn has_new_nodes(&self) -> bool {
113        !self.new_nodes.is_empty()
114    }
115
116    /// Retrieve the first new node (if it exists).
117    pub fn first_new_node(&self) -> Option<&crate::planning::plan::PlanNodeEnum> {
118        self.new_nodes.first()
119    }
120}
121
122/// Matching results
123///
124/// Results of the record mode matching
125#[derive(Debug, Default, Clone)]
126pub struct MatchedResult {
127    /// List of matching nodes
128    pub nodes: Vec<crate::planning::plan::PlanNodeEnum>,
129    /// List of dependent nodes
130    pub dependencies: Vec<crate::planning::plan::PlanNodeEnum>,
131    /// Root node
132    pub root_node: Option<crate::planning::plan::PlanNodeEnum>,
133}
134
135impl MatchedResult {
136    /// Create new matching results.
137    pub fn new() -> Self {
138        Self::default()
139    }
140
141    /// Add matching nodes
142    pub fn add_node(&mut self, node: crate::planning::plan::PlanNodeEnum) {
143        self.nodes.push(node);
144    }
145
146    /// Add dependency nodes
147    pub fn add_dependency(&mut self, node: crate::planning::plan::PlanNodeEnum) {
148        self.dependencies.push(node);
149    }
150
151    /// Setting the root node
152    pub fn set_root_node(&mut self, node: crate::planning::plan::PlanNodeEnum) {
153        self.root_node = Some(node);
154    }
155
156    /// Check whether there are any matching nodes.
157    pub fn has_matches(&self) -> bool {
158        !self.nodes.is_empty()
159    }
160
161    /// Retrieve the first matching node.
162    pub fn first_node(&self) -> Option<&crate::planning::plan::PlanNodeEnum> {
163        self.nodes.first()
164    }
165
166    /// Obtain the first dependent node.
167    pub fn first_dependency(&self) -> Option<&crate::planning::plan::PlanNodeEnum> {
168        self.dependencies.first()
169    }
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175    use crate::planning::plan::core::nodes::access::graph_scan_node::ScanVerticesNode;
176
177    #[test]
178    fn test_transform_result() {
179        let mut result = TransformResult::new();
180        assert!(!result.has_new_nodes());
181        assert!(!result.has_folded);
182
183        let node =
184            crate::planning::plan::PlanNodeEnum::ScanVertices(ScanVerticesNode::new(1, "default"));
185        result.add_new_node(node);
186
187        assert!(result.has_new_nodes());
188        assert!(result.first_new_node().is_some());
189
190        result.mark_folded();
191        assert!(result.has_folded);
192    }
193
194    #[test]
195    fn test_matched_result() {
196        let mut result = MatchedResult::new();
197        assert!(!result.has_matches());
198
199        let node =
200            crate::planning::plan::PlanNodeEnum::ScanVertices(ScanVerticesNode::new(1, "default"));
201        result.add_node(node);
202
203        assert!(result.has_matches());
204        assert!(result.first_node().is_some());
205    }
206
207    #[test]
208    fn test_rewrite_error() {
209        let err = RewriteError::invalid_node("test node");
210        assert!(err.to_string().contains("test node"));
211
212        let err = RewriteError::cycle_detected(42);
213        assert!(err.to_string().contains("42"));
214    }
215}