Skip to main content

linkrs_query/optimizer/heuristic/
rule.rs

1//! Rewriting the definition of a trait
2//!
3//! This module provides the definition of a trait that encompasses heuristic rules for rewriting code.
4//! Heuristic rules do not rely on cost calculations and always generate either better or equivalent plans.
5//!
6//! This is a version that has been separated from the optimizer layer and focuses on the requirements of the planner layer.
7
8use crate::optimizer::heuristic::context::RewriteContext;
9use crate::optimizer::heuristic::pattern::Pattern;
10use crate::optimizer::heuristic::result::{MatchedResult, RewriteResult, TransformResult};
11use crate::planning::plan::PlanNodeEnum;
12
13/// Rewrite the rule for the `trait`.
14///
15/// All heuristic rewriting rules must implement this trait.
16/// The rules identify specific structures in the plan tree through pattern matching, and then the transformations are applied.
17///
18/// # Example
19/// ```rust
20/// use linkrs_query::optimizer::heuristic::context::RewriteContext;
21/// use linkrs_query::optimizer::heuristic::pattern::Pattern;
22/// use linkrs_query::optimizer::heuristic::result::{RewriteResult, TransformResult};
23/// use linkrs_query::optimizer::heuristic::rule::RewriteRule;
24/// use linkrs_query::planning::plan::PlanNodeEnum;
25///
26/// #[derive(Debug)]
27/// struct MyRule;
28///
29/// impl RewriteRule for MyRule {
30///     fn name(&self) -> &'static str { "MyRule" }
31///
32///     fn pattern(&self) -> Pattern {
33///         Pattern::new_with_name("Filter")
34///     }
35///
36///     fn apply(&self, _ctx: &mut RewriteContext, _node: &PlanNodeEnum) -> RewriteResult<Option<TransformResult>> {
37///         Ok(None)
38///     }
39/// }
40/// ```
41pub trait RewriteRule: std::fmt::Debug + Send + Sync {
42    /// Rule Name
43    fn name(&self) -> &'static str;
44
45    /// Return the pattern of the rule.
46    ///
47    /// Used for matching the specific structure of the planning tree
48    fn pattern(&self) -> Pattern;
49
50    /// Apply the rule for rewriting the text.
51    ///
52    /// # Parameters
53    /// `ctx`: Rewrite the context.
54    /// `node`: The current planned node.
55    ///
56    /// # Return
57    /// - `Ok(Some(result))`: rewrite succeeded, returns the conversion result
58    /// - `Ok(None)`: no match, keeps the original node
59    /// - `Err(e)`: rewrite failed
60    fn apply(
61        &self,
62        ctx: &mut RewriteContext,
63        node: &PlanNodeEnum,
64    ) -> RewriteResult<Option<TransformResult>>;
65
66    /// Matching pattern
67    ///
68    /// Check whether the current node matches the pattern specified by the rule.
69    fn match_pattern(&self, node: &PlanNodeEnum) -> RewriteResult<Option<MatchedResult>> {
70        if self.pattern().matches(node) {
71            let mut result = MatchedResult::new();
72            result.add_node(node.clone());
73
74            // Add dependency nodes
75            for dep in node.dependencies() {
76                result.add_dependency(dep.clone());
77            }
78
79            result.set_root_node(node.clone());
80            Ok(Some(result))
81        } else {
82            Ok(None)
83        }
84    }
85
86    /// Check whether the rules are matched.
87    ///
88    /// Convenient method: It only returns a signal indicating whether a match was found or not; no detailed information is provided.
89    fn matches(&self, node: &PlanNodeEnum) -> bool {
90        self.pattern().matches(node)
91    }
92}
93
94/// Basic rewriting rules for traits
95///
96/// The `trait` is used to identify the basic overriding rules.
97pub trait BaseRewriteRule: RewriteRule {}
98
99/// Merging Rules Trait
100///
101/// Rules for merging two consecutive operations
102pub trait MergeRule: RewriteRule {
103    /// Check whether it is possible to merge these elements.
104    fn can_merge(&self, parent: &PlanNodeEnum, child: &PlanNodeEnum) -> bool;
105
106    /// Create a merged node.
107    fn create_merged_node(
108        &self,
109        ctx: &mut RewriteContext,
110        parent: &PlanNodeEnum,
111        child: &PlanNodeEnum,
112    ) -> RewriteResult<Option<TransformResult>>;
113}
114
115/// “Push Down Rule” trait
116///
117/// Rules used to push operations down to the lowest level of the planning tree
118pub trait PushDownRule: RewriteRule {
119    /// Check whether it is possible to perform a push-down operation.
120    fn can_push_down(&self, node: &PlanNodeEnum, target: &PlanNodeEnum) -> bool;
121
122    /// Perform the push-down operation.
123    fn push_down(
124        &self,
125        ctx: &mut RewriteContext,
126        node: &PlanNodeEnum,
127        target: &PlanNodeEnum,
128    ) -> RewriteResult<Option<TransformResult>>;
129}
130
131/// Remove the “rule” trait.
132///
133/// Rules for eliminating redundant operations
134pub trait EliminationRule: RewriteRule {
135    /// Check whether it is possible to eliminate this element/aspect.
136    fn can_eliminate(&self, node: &PlanNodeEnum) -> bool;
137
138    /// Perform the elimination operation.
139    fn eliminate(
140        &self,
141        ctx: &mut RewriteContext,
142        node: &PlanNodeEnum,
143    ) -> RewriteResult<Option<TransformResult>>;
144}
145
146/// Rule Packaging Container
147///
148/// Used to wrap specific rule types into a unified interface
149#[derive(Debug)]
150pub struct RuleWrapper<T: RewriteRule> {
151    inner: T,
152}
153
154impl<T: RewriteRule> RuleWrapper<T> {
155    pub fn new(rule: T) -> Self {
156        Self { inner: rule }
157    }
158
159    pub fn into_inner(self) -> T {
160        self.inner
161    }
162}
163
164impl<T: RewriteRule> RewriteRule for RuleWrapper<T> {
165    fn name(&self) -> &'static str {
166        self.inner.name()
167    }
168
169    fn pattern(&self) -> Pattern {
170        self.inner.pattern()
171    }
172
173    fn apply(
174        &self,
175        ctx: &mut RewriteContext,
176        node: &PlanNodeEnum,
177    ) -> RewriteResult<Option<TransformResult>> {
178        self.inner.apply(ctx, node)
179    }
180}
181
182/// Rule Adaptation Adapter trait
183///
184/// It is allowed to convert specific rules into wrappers.
185pub trait IntoRuleWrapper: RewriteRule + Sized {
186    fn into_wrapper(self) -> RuleWrapper<Self> {
187        RuleWrapper::new(self)
188    }
189}
190
191impl<T: RewriteRule + Sized> IntoRuleWrapper for T {}
192
193#[cfg(test)]
194mod tests {
195    use super::*;
196    use crate::planning::plan::core::nodes::access::graph_scan_node::ScanVerticesNode;
197
198    #[derive(Debug)]
199    struct TestRule;
200
201    impl RewriteRule for TestRule {
202        fn name(&self) -> &'static str {
203            "TestRule"
204        }
205
206        fn pattern(&self) -> Pattern {
207            Pattern::new_with_name("ScanVertices")
208        }
209
210        fn apply(
211            &self,
212            _ctx: &mut RewriteContext,
213            _node: &PlanNodeEnum,
214        ) -> RewriteResult<Option<TransformResult>> {
215            Ok(None)
216        }
217    }
218
219    #[test]
220    fn test_rule_name() {
221        let rule = TestRule;
222        assert_eq!(rule.name(), "TestRule");
223    }
224
225    #[test]
226    fn test_rule_matches() {
227        let rule = TestRule;
228        let node = PlanNodeEnum::ScanVertices(ScanVerticesNode::new(1, "default"));
229
230        assert!(rule.matches(&node));
231    }
232
233    #[test]
234    fn test_rule_wrapper() {
235        let rule = TestRule;
236        let wrapper = rule.into_wrapper();
237
238        assert_eq!(wrapper.name(), "TestRule");
239    }
240}