linkrs_query/optimizer/decision/
types.rs1use std::time::Instant;
7
8#[derive(Debug, Clone, PartialEq)]
10pub struct OptimizationDecision {
11 pub traversal_start: TraversalStartDecision,
13 pub index_selection: IndexSelectionDecision,
15 pub join_order: JoinOrderDecision,
17 pub rewrite_rules: Vec<RewriteRuleId>,
19 pub stats_version: u64,
21 pub index_version: u64,
23 pub created_at: Instant,
25}
26
27impl OptimizationDecision {
28 pub fn new(
30 traversal_start: TraversalStartDecision,
31 index_selection: IndexSelectionDecision,
32 join_order: JoinOrderDecision,
33 stats_version: u64,
34 index_version: u64,
35 ) -> Self {
36 Self {
37 traversal_start,
38 index_selection,
39 join_order,
40 rewrite_rules: Vec::new(),
41 stats_version,
42 index_version,
43 created_at: Instant::now(),
44 }
45 }
46
47 pub fn is_valid(&self, current_stats_version: u64, current_index_version: u64) -> bool {
49 self.stats_version == current_stats_version && self.index_version == current_index_version
50 }
51
52 pub fn age_secs(&self) -> u64 {
54 self.created_at.elapsed().as_secs()
55 }
56}
57
58#[derive(Debug, Clone, PartialEq, Eq, Hash)]
60pub struct TraversalStartDecision {
61 pub start_variable: String,
63 pub access_path: AccessPath,
65 pub estimated_selectivity_scaled: u64, pub estimated_cost_scaled: u64, }
70
71impl TraversalStartDecision {
72 pub fn new(
74 start_variable: String,
75 access_path: AccessPath,
76 estimated_selectivity: f64,
77 estimated_cost: f64,
78 ) -> Self {
79 Self {
80 start_variable,
81 access_path,
82 estimated_selectivity_scaled: (estimated_selectivity * 1_000_000.0) as u64,
83 estimated_cost_scaled: (estimated_cost * 1_000_000.0) as u64,
84 }
85 }
86
87 pub fn estimated_selectivity(&self) -> f64 {
89 self.estimated_selectivity_scaled as f64 / 1_000_000.0
90 }
91
92 pub fn estimated_cost(&self) -> f64 {
94 self.estimated_cost_scaled as f64 / 1_000_000.0
95 }
96}
97
98#[derive(Debug, Clone, PartialEq, Eq, Hash)]
100pub enum AccessPath {
101 ExplicitVid {
103 vid_description: String,
105 },
106 IndexScan {
108 index_name: String,
110 property_name: String,
112 predicate_description: String,
114 },
115 TagIndex {
117 tag_name: String,
119 },
120 FullScan {
122 entity_type: EntityType,
124 },
125 VariableBinding {
127 source_variable: String,
129 },
130}
131
132#[derive(Debug, Clone, PartialEq, Eq, Hash)]
134pub enum EntityType {
135 Vertex {
137 tag_name: Option<String>,
139 },
140 Edge {
142 edge_type: Option<String>,
144 },
145}
146
147#[derive(Debug, Clone, PartialEq, Eq, Hash)]
149pub struct IndexSelectionDecision {
150 pub entity_indexes: Vec<EntityIndexChoice>,
152}
153
154impl IndexSelectionDecision {
155 pub fn empty() -> Self {
157 Self {
158 entity_indexes: Vec::new(),
159 }
160 }
161
162 pub fn add_choice(&mut self, choice: EntityIndexChoice) {
164 self.entity_indexes.push(choice);
165 }
166}
167
168#[derive(Debug, Clone, PartialEq, Eq, Hash)]
170pub struct EntityIndexChoice {
171 pub entity_name: String,
173 pub selected_index: IndexChoice,
175 pub selectivity_scaled: u64,
177}
178
179impl EntityIndexChoice {
180 pub fn new(entity_name: String, selected_index: IndexChoice, selectivity: f64) -> Self {
182 Self {
183 entity_name,
184 selected_index,
185 selectivity_scaled: (selectivity * 1_000_000.0) as u64,
186 }
187 }
188
189 pub fn selectivity(&self) -> f64 {
191 self.selectivity_scaled as f64 / 1_000_000.0
192 }
193}
194
195#[derive(Debug, Clone, PartialEq, Eq, Hash)]
197pub enum IndexChoice {
198 PrimaryKey,
200 PropertyIndex {
202 property_name: String,
204 index_name: String,
206 },
207 CompositeIndex {
209 property_names: Vec<String>,
211 index_name: String,
213 },
214 None,
216}
217
218#[derive(Debug, Clone, PartialEq, Eq, Hash)]
220pub struct JoinOrderDecision {
221 pub join_order: Vec<String>,
223 pub join_algorithms: Vec<JoinAlgorithm>,
225}
226
227impl JoinOrderDecision {
228 pub fn empty() -> Self {
230 Self {
231 join_order: Vec::new(),
232 join_algorithms: Vec::new(),
233 }
234 }
235
236 pub fn add_join_step(&mut self, variable: String, algorithm: JoinAlgorithm) {
238 self.join_order.push(variable);
239 self.join_algorithms.push(algorithm);
240 }
241}
242
243#[derive(Debug, Clone, PartialEq, Eq, Hash)]
245pub enum JoinAlgorithm {
246 HashJoin {
248 build_side: String,
250 probe_side: String,
252 },
253 NestedLoopJoin {
255 outer: String,
257 inner: String,
259 },
260 IndexJoin {
262 indexed_side: String,
264 },
265}
266
267#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
269pub enum RewriteRuleId {
270 PushFilterDown,
272 PushProjectDown,
274 PushLimitDown,
276 MergeOperations,
278 EliminateRedundancy,
280 AggregateOptimization,
282}
283
284#[cfg(test)]
285mod tests {
286 use super::*;
287
288 #[test]
289 fn test_traversal_start_decision() {
290 let decision = TraversalStartDecision::new(
291 "n".to_string(),
292 AccessPath::TagIndex {
293 tag_name: "Person".to_string(),
294 },
295 0.1,
296 100.0,
297 );
298
299 assert_eq!(decision.start_variable, "n");
300 assert!((decision.estimated_selectivity() - 0.1).abs() < 0.0001);
301 assert!((decision.estimated_cost() - 100.0).abs() < 0.0001);
302 }
303
304 #[test]
305 fn test_optimization_decision_validity() {
306 let decision = OptimizationDecision::new(
307 TraversalStartDecision::new(
308 "n".to_string(),
309 AccessPath::FullScan {
310 entity_type: EntityType::Vertex { tag_name: None },
311 },
312 1.0,
313 1000.0,
314 ),
315 IndexSelectionDecision::empty(),
316 JoinOrderDecision::empty(),
317 1,
318 1,
319 );
320
321 assert!(decision.is_valid(1, 1));
322 assert!(!decision.is_valid(2, 1));
323 assert!(!decision.is_valid(1, 2));
324 }
325}