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}