1use crate::algebra::Algebra;
7use anyhow::Result;
8use std::sync::atomic::{AtomicU64, Ordering};
9use std::sync::Arc;
10
11#[derive(Clone)]
13pub struct StreamingAnalyzer {
14 config: StreamingConfig,
15 current_memory_bytes: Arc<AtomicU64>,
16 spill_policies: Vec<SpillPolicy>,
17}
18
19#[derive(Debug, Clone)]
21pub struct StreamingConfig {
22 pub enable_streaming: bool,
23 pub memory_threshold_mb: usize, pub spill_threshold_percent: f64, pub streaming_batch_size: usize, }
27
28impl Default for StreamingConfig {
29 fn default() -> Self {
30 Self {
31 enable_streaming: true,
32 memory_threshold_mb: 2048,
33 spill_threshold_percent: 0.8,
34 streaming_batch_size: 1000,
35 }
36 }
37}
38
39#[derive(Debug, Clone)]
41pub struct StreamingStrategy {
42 pub strategy_type: StreamingType,
43 pub memory_limit: usize,
44 pub batch_size: usize,
45 pub spill_threshold: f64,
46 pub parallelism_degree: usize,
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
51pub enum StreamingType {
52 PipelineBreaker,
53 HashJoinStreaming,
54 SortMergeStreaming,
55 NestedLoopStreaming,
56 IndexNestedLoop,
57 HybridStreaming,
58}
59
60#[derive(Debug, Clone)]
62pub struct SpillPolicy {
63 pub policy_type: SpillType,
64 pub threshold: f64,
65 pub target_operators: Vec<String>,
66 pub cost_factor: f64,
67}
68
69#[derive(Debug, Clone, PartialEq, Eq)]
71pub enum SpillType {
72 LeastRecentlyUsed,
73 LargestFirst,
74 CostBased,
75 PredictiveBased,
76}
77
78#[derive(Debug, Clone, Default)]
80pub struct StreamingOpportunities {
81 pub streamable_scans: Vec<OperatorId>,
82 pub streamable_filters: Vec<OperatorId>,
83 pub streamable_projects: Vec<OperatorId>,
84 pub requires_materialization: Vec<(OperatorId, &'static str)>,
85 pub pipeline_breakers: Vec<OperatorId>,
86 pub estimated_memory_savings_mb: usize,
87}
88
89#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
91pub struct OperatorId(u64);
92
93impl OperatorId {
94 fn new(id: u64) -> Self {
95 Self(id)
96 }
97}
98
99#[derive(Debug, Clone)]
101pub struct QueryPlan {
102 operators: Vec<Operator>,
103 operator_id_counter: u64,
104}
105
106impl QueryPlan {
107 pub fn from_algebra(algebra: &Algebra) -> Self {
108 let mut plan = Self {
109 operators: Vec::new(),
110 operator_id_counter: 0,
111 };
112 plan.build_from_algebra(algebra);
113 plan
114 }
115
116 fn build_from_algebra(&mut self, algebra: &Algebra) {
117 match algebra {
118 Algebra::Bgp(patterns) => {
119 let id = self.next_id();
120 self.operators.push(Operator::Scan(ScanOperator {
121 id,
122 patterns: patterns.len(),
123 }));
124 }
125 Algebra::Filter { pattern, .. } => {
126 self.build_from_algebra(pattern);
127 let id = self.next_id();
128 self.operators.push(Operator::Filter(FilterOperator { id }));
129 }
130 Algebra::Project { pattern, variables } => {
131 self.build_from_algebra(pattern);
132 let id = self.next_id();
133 self.operators.push(Operator::Project(ProjectOperator {
134 id,
135 num_vars: variables.len(),
136 }));
137 }
138 Algebra::Join { left, right } => {
139 self.build_from_algebra(left);
140 self.build_from_algebra(right);
141 let id = self.next_id();
142 self.operators
143 .push(Operator::HashJoin(HashJoinOperator { id }));
144 }
145 Algebra::LeftJoin { left, right, .. } => {
146 self.build_from_algebra(left);
147 self.build_from_algebra(right);
148 let id = self.next_id();
149 self.operators
150 .push(Operator::HashJoin(HashJoinOperator { id }));
151 }
152 Algebra::Union { left, right } => {
153 self.build_from_algebra(left);
154 self.build_from_algebra(right);
155 let id = self.next_id();
156 self.operators.push(Operator::Union(UnionOperator { id }));
157 }
158 Algebra::OrderBy { pattern, .. } => {
159 self.build_from_algebra(pattern);
160 let id = self.next_id();
161 self.operators.push(Operator::Sort(SortOperator { id }));
162 }
163 Algebra::Group { pattern, .. } => {
164 self.build_from_algebra(pattern);
165 let id = self.next_id();
166 self.operators
167 .push(Operator::Aggregation(AggregationOperator { id }));
168 }
169 Algebra::Distinct { pattern } => {
170 self.build_from_algebra(pattern);
171 let id = self.next_id();
172 self.operators
173 .push(Operator::Distinct(DistinctOperator { id }));
174 }
175 Algebra::Slice { pattern, .. } => {
176 self.build_from_algebra(pattern);
177 let id = self.next_id();
178 self.operators.push(Operator::Limit(LimitOperator { id }));
179 }
180 _ => {
181 let id = self.next_id();
183 self.operators
184 .push(Operator::Generic(GenericOperator { id }));
185 }
186 }
187 }
188
189 fn next_id(&mut self) -> OperatorId {
190 let id = OperatorId::new(self.operator_id_counter);
191 self.operator_id_counter += 1;
192 id
193 }
194
195 pub fn operators(&self) -> &[Operator] {
196 &self.operators
197 }
198}
199
200#[derive(Debug, Clone)]
202pub enum Operator {
203 Scan(ScanOperator),
204 Filter(FilterOperator),
205 Project(ProjectOperator),
206 HashJoin(HashJoinOperator),
207 SortMergeJoin(SortMergeJoinOperator),
208 Sort(SortOperator),
209 Aggregation(AggregationOperator),
210 Distinct(DistinctOperator),
211 Union(UnionOperator),
212 Limit(LimitOperator),
213 Generic(GenericOperator),
214}
215
216impl Operator {
217 pub fn id(&self) -> OperatorId {
218 match self {
219 Operator::Scan(op) => op.id,
220 Operator::Filter(op) => op.id,
221 Operator::Project(op) => op.id,
222 Operator::HashJoin(op) => op.id,
223 Operator::SortMergeJoin(op) => op.id,
224 Operator::Sort(op) => op.id,
225 Operator::Aggregation(op) => op.id,
226 Operator::Distinct(op) => op.id,
227 Operator::Union(op) => op.id,
228 Operator::Limit(op) => op.id,
229 Operator::Generic(op) => op.id,
230 }
231 }
232}
233
234#[derive(Debug, Clone)]
235pub struct ScanOperator {
236 pub id: OperatorId,
237 pub patterns: usize,
238}
239
240#[derive(Debug, Clone)]
241pub struct FilterOperator {
242 pub id: OperatorId,
243}
244
245#[derive(Debug, Clone)]
246pub struct ProjectOperator {
247 pub id: OperatorId,
248 pub num_vars: usize,
249}
250
251#[derive(Debug, Clone)]
252pub struct HashJoinOperator {
253 pub id: OperatorId,
254}
255
256#[derive(Debug, Clone)]
257pub struct SortMergeJoinOperator {
258 pub id: OperatorId,
259}
260
261#[derive(Debug, Clone)]
262pub struct SortOperator {
263 pub id: OperatorId,
264}
265
266#[derive(Debug, Clone)]
267pub struct AggregationOperator {
268 pub id: OperatorId,
269}
270
271#[derive(Debug, Clone)]
272pub struct DistinctOperator {
273 pub id: OperatorId,
274}
275
276#[derive(Debug, Clone)]
277pub struct UnionOperator {
278 pub id: OperatorId,
279}
280
281#[derive(Debug, Clone)]
282pub struct LimitOperator {
283 pub id: OperatorId,
284}
285
286#[derive(Debug, Clone)]
287pub struct GenericOperator {
288 pub id: OperatorId,
289}
290
291impl StreamingAnalyzer {
292 pub fn new(config: StreamingConfig) -> Self {
294 Self {
295 config,
296 current_memory_bytes: Arc::new(AtomicU64::new(0)),
297 spill_policies: Vec::new(),
298 }
299 }
300
301 pub fn analyze(&self, plan: &QueryPlan) -> StreamingOpportunities {
303 let mut opportunities = StreamingOpportunities::default();
304
305 for op in plan.operators() {
307 match op {
308 Operator::Scan(_) => {
309 opportunities.streamable_scans.push(op.id());
311 }
312 Operator::Filter(_)
313 if self.is_input_streamable(op, &opportunities) => {
315 opportunities.streamable_filters.push(op.id());
316 }
317 Operator::Project(_) => {
318 opportunities.streamable_projects.push(op.id());
320 }
321 Operator::HashJoin(_) => {
322 opportunities
324 .requires_materialization
325 .push((op.id(), "build_side"));
326 }
327 Operator::SortMergeJoin(_) => {
328 opportunities
330 .requires_materialization
331 .push((op.id(), "both_sides"));
332 }
333 Operator::Sort(_) => {
334 opportunities.pipeline_breakers.push(op.id());
336 }
337 Operator::Aggregation(_) => {
338 opportunities.pipeline_breakers.push(op.id());
340 }
341 Operator::Distinct(_) => {
342 opportunities.pipeline_breakers.push(op.id());
344 }
345 Operator::Union(_) => {
346 opportunities.pipeline_breakers.push(op.id());
348 }
349 _ => {}
350 }
351 }
352
353 opportunities.estimated_memory_savings_mb = self.estimate_memory_savings(&opportunities);
355
356 opportunities
357 }
358
359 fn is_input_streamable(
361 &self,
362 _operator: &Operator,
363 opportunities: &StreamingOpportunities,
364 ) -> bool {
365 !opportunities.streamable_scans.is_empty() || !opportunities.streamable_filters.is_empty()
367 }
368
369 fn estimate_memory_savings(&self, opportunities: &StreamingOpportunities) -> usize {
371 let num_streamable = opportunities.streamable_scans.len()
373 + opportunities.streamable_filters.len()
374 + opportunities.streamable_projects.len();
375 num_streamable * 100 }
377
378 pub fn should_stream(&self, _operator: &Operator, estimated_size: usize) -> bool {
380 if !self.config.enable_streaming {
381 return false;
382 }
383
384 let threshold_bytes = self.config.memory_threshold_mb * 1024 * 1024;
386 estimated_size > threshold_bytes
387 }
388
389 pub fn find_pipeline_breakers(&self, plan: &QueryPlan) -> Vec<OperatorId> {
391 let mut breakers = vec![];
392
393 for op in plan.operators() {
394 if self.is_pipeline_breaker(op) {
395 breakers.push(op.id());
396 }
397 }
398
399 breakers
400 }
401
402 fn is_pipeline_breaker(&self, op: &Operator) -> bool {
403 matches!(
404 op,
405 Operator::Sort(_)
406 | Operator::Aggregation(_)
407 | Operator::Distinct(_)
408 | Operator::Union(_)
409 )
410 }
411
412 pub fn convert_to_streaming(&self, operator: &mut Operator) -> Result<()> {
414 match operator {
415 Operator::HashJoin(_) => {
416 Ok(())
419 }
420 Operator::Aggregation(_) => {
421 Ok(())
424 }
425 _ => Ok(()),
426 }
427 }
428
429 pub fn analyze_streaming_potential(
431 &self,
432 algebra: &Algebra,
433 ) -> Result<Option<StreamingStrategy>> {
434 let plan = QueryPlan::from_algebra(algebra);
435 let opportunities = self.analyze(&plan);
436
437 if !opportunities.streamable_scans.is_empty() {
439 Ok(Some(StreamingStrategy {
440 strategy_type: StreamingType::HashJoinStreaming,
441 memory_limit: self.config.memory_threshold_mb * 1024 * 1024,
442 batch_size: self.config.streaming_batch_size,
443 spill_threshold: self.config.spill_threshold_percent,
444 parallelism_degree: std::thread::available_parallelism()
445 .map(|n| n.get())
446 .unwrap_or(1),
447 }))
448 } else {
449 Ok(None)
450 }
451 }
452
453 pub fn memory_threshold(&self) -> usize {
455 self.config.memory_threshold_mb * 1024 * 1024
456 }
457
458 pub fn set_memory_threshold(&mut self, threshold_mb: usize) {
460 self.config.memory_threshold_mb = threshold_mb;
461 }
462
463 pub fn add_spill_policy(&mut self, policy: SpillPolicy) {
465 self.spill_policies.push(policy);
466 }
467
468 pub fn spill_policies(&self) -> &[SpillPolicy] {
470 &self.spill_policies
471 }
472
473 pub fn optimizations_count(&self) -> usize {
475 if self.config.enable_streaming {
477 1
478 } else {
479 0
480 }
481 }
482
483 pub fn current_memory_usage(&self) -> usize {
485 self.current_memory_bytes.load(Ordering::Relaxed) as usize
486 }
487
488 pub fn should_spill(&self) -> bool {
490 let current_usage = self.current_memory_usage();
491 let threshold =
492 (self.memory_threshold() as f64 * self.config.spill_threshold_percent) as usize;
493 current_usage > threshold
494 }
495
496 pub fn analyze_query_complexity(&self, algebra: &Algebra) -> QueryComplexity {
498 let mut complexity = QueryComplexity::default();
499 self.compute_complexity(algebra, &mut complexity);
500 complexity
501 }
502
503 fn compute_complexity(&self, algebra: &Algebra, complexity: &mut QueryComplexity) {
504 match algebra {
505 Algebra::Bgp(patterns) => {
506 complexity.num_patterns += patterns.len();
507 }
508 Algebra::Join { left, right }
509 | Algebra::Union { left, right }
510 | Algebra::LeftJoin { left, right, .. } => {
511 complexity.num_joins += 1;
512 self.compute_complexity(left, complexity);
513 self.compute_complexity(right, complexity);
514 }
515 Algebra::Filter { pattern, .. } => {
516 complexity.num_filters += 1;
517 self.compute_complexity(pattern, complexity);
518 }
519 Algebra::OrderBy { pattern, .. } => {
520 complexity.num_sorts += 1;
521 self.compute_complexity(pattern, complexity);
522 }
523 Algebra::Group { pattern, .. } => {
524 complexity.num_aggregations += 1;
525 self.compute_complexity(pattern, complexity);
526 }
527 _ => {}
528 }
529 }
530}
531
532#[derive(Debug, Clone, Default)]
534pub struct QueryComplexity {
535 pub num_patterns: usize,
536 pub num_joins: usize,
537 pub num_filters: usize,
538 pub num_sorts: usize,
539 pub num_aggregations: usize,
540}
541
542impl QueryComplexity {
543 pub fn total_complexity(&self) -> usize {
544 self.num_patterns
545 + self.num_joins * 2
546 + self.num_filters
547 + self.num_sorts * 3
548 + self.num_aggregations * 2
549 }
550}
551
552#[cfg(test)]
553mod tests {
554 use super::*;
555
556 #[test]
557 fn test_streaming_analyzer_creation() {
558 let config = StreamingConfig::default();
559 let analyzer = StreamingAnalyzer::new(config);
560 assert_eq!(analyzer.memory_threshold(), 2048 * 1024 * 1024);
561 }
562
563 #[test]
564 fn test_add_spill_policy_is_retained() {
565 let config = StreamingConfig::default();
566 let mut analyzer = StreamingAnalyzer::new(config);
567 assert!(analyzer.spill_policies().is_empty());
568
569 analyzer.add_spill_policy(SpillPolicy {
570 policy_type: SpillType::LeastRecentlyUsed,
571 threshold: 0.75,
572 target_operators: vec!["scan-1".to_string()],
573 cost_factor: 1.5,
574 });
575 analyzer.add_spill_policy(SpillPolicy {
576 policy_type: SpillType::CostBased,
577 threshold: 0.9,
578 target_operators: vec!["sort-2".to_string()],
579 cost_factor: 2.0,
580 });
581
582 let policies = analyzer.spill_policies();
583 assert_eq!(policies.len(), 2);
584 assert_eq!(policies[0].policy_type, SpillType::LeastRecentlyUsed);
585 assert_eq!(policies[1].policy_type, SpillType::CostBased);
586 assert_eq!(policies[1].target_operators, vec!["sort-2".to_string()]);
587 }
588
589 #[test]
590 fn test_should_stream_decision() {
591 let config = StreamingConfig {
592 enable_streaming: true,
593 memory_threshold_mb: 1024,
594 spill_threshold_percent: 0.8,
595 streaming_batch_size: 1000,
596 };
597 let analyzer = StreamingAnalyzer::new(config);
598
599 assert!(!analyzer.should_stream(
601 &Operator::Scan(ScanOperator {
602 id: OperatorId::new(1),
603 patterns: 1
604 }),
605 100 * 1024 * 1024
606 ));
607
608 assert!(analyzer.should_stream(
610 &Operator::Scan(ScanOperator {
611 id: OperatorId::new(1),
612 patterns: 1
613 }),
614 2048 * 1024 * 1024
615 ));
616 }
617
618 #[test]
619 fn test_pipeline_breaker_detection() {
620 let config = StreamingConfig::default();
621 let analyzer = StreamingAnalyzer::new(config);
622
623 assert!(analyzer.is_pipeline_breaker(&Operator::Sort(SortOperator {
624 id: OperatorId::new(1)
625 })));
626 assert!(
627 analyzer.is_pipeline_breaker(&Operator::Aggregation(AggregationOperator {
628 id: OperatorId::new(2)
629 }))
630 );
631 assert!(
632 !analyzer.is_pipeline_breaker(&Operator::Filter(FilterOperator {
633 id: OperatorId::new(3)
634 }))
635 );
636 }
637}