1use crate::DataProfilerError;
4use crate::query_columns::QueryColumns;
5
6pub use crate::query_columns::merge_column_batches;
10
11#[derive(Debug, Clone)]
13pub struct StreamingConfig {
14 pub batch_size: usize,
15 pub max_memory_mb: usize,
16 pub progress_callback: Option<fn(u64, u64)>,
17}
18
19impl Default for StreamingConfig {
20 fn default() -> Self {
21 Self {
22 batch_size: 10000,
23 max_memory_mb: 512,
24 progress_callback: None,
25 }
26 }
27}
28
29pub fn estimate_memory_usage(columns: &QueryColumns) -> usize {
31 columns
32 .iter()
33 .map(|(name, data)| name.len() + data.iter().map(|s| s.len()).sum::<usize>())
34 .sum::<usize>()
35}
36
37pub fn apply_sampling_if_needed(
39 mut columns: QueryColumns,
40 max_memory_mb: usize,
41 sampling_ratio: f64,
42) -> Result<(QueryColumns, bool), DataProfilerError> {
43 let memory_usage_bytes = estimate_memory_usage(&columns);
44 let memory_usage_mb = memory_usage_bytes / 1_048_576;
45
46 if memory_usage_mb <= max_memory_mb {
47 return Ok((columns, false));
48 }
49
50 let total_rows = columns.row_count();
53 let target_rows = (total_rows as f64 * sampling_ratio) as usize;
54
55 if target_rows == 0 {
56 return Ok((QueryColumns::new(), true));
57 }
58
59 let step = total_rows / target_rows;
60 if step <= 1 {
61 return Ok((columns, false));
62 }
63
64 for column_data in columns.values_mut() {
65 let sampled: Vec<String> = column_data
66 .iter()
67 .step_by(step)
68 .take(target_rows)
69 .cloned()
70 .collect();
71 *column_data = sampled;
72 }
73
74 Ok((columns, true))
75}
76
77pub struct StreamingProgress {
79 pub total_rows: Option<u64>,
80 pub processed_rows: u64,
81 pub batches_processed: u64,
82 pub start_time: std::time::Instant,
83}
84
85impl StreamingProgress {
86 pub fn new(total_rows: Option<u64>) -> Self {
87 Self {
88 total_rows,
89 processed_rows: 0,
90 batches_processed: 0,
91 start_time: std::time::Instant::now(),
92 }
93 }
94
95 pub fn update(&mut self, batch_size: u64) {
96 self.processed_rows += batch_size;
97 self.batches_processed += 1;
98 }
99
100 pub fn percentage(&self) -> Option<f64> {
101 self.total_rows.map(|total| {
102 if total == 0 {
103 100.0
104 } else {
105 (self.processed_rows as f64 / total as f64) * 100.0
106 }
107 })
108 }
109
110 pub fn elapsed(&self) -> std::time::Duration {
111 self.start_time.elapsed()
112 }
113
114 pub fn estimated_total_time(&self) -> Option<std::time::Duration> {
115 if let Some(percentage) = self.percentage()
116 && percentage > 0.0
117 {
118 let elapsed_secs = self.elapsed().as_secs_f64();
119 let total_secs = elapsed_secs * (100.0 / percentage);
120 return Some(std::time::Duration::from_secs_f64(total_secs));
121 }
122 None
123 }
124
125 pub fn rows_per_second(&self) -> f64 {
126 let elapsed_secs = self.elapsed().as_secs_f64();
127 if elapsed_secs > 0.0 {
128 self.processed_rows as f64 / elapsed_secs
129 } else {
130 0.0
131 }
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn test_merge_column_batches() {
141 let batch = |a: [&str; 2], b: [&str; 2]| -> QueryColumns {
142 [
143 (
144 "col1".to_string(),
145 a.iter().map(|v| v.to_string()).collect(),
146 ),
147 (
148 "col2".to_string(),
149 b.iter().map(|v| v.to_string()).collect(),
150 ),
151 ]
152 .into_iter()
153 .collect()
154 };
155
156 let merged = merge_column_batches(vec![
157 batch(["a", "b"], ["1", "2"]),
158 batch(["c", "d"], ["3", "4"]),
159 ]);
160
161 assert_eq!(merged.names().collect::<Vec<_>>(), ["col1", "col2"]);
162 assert_eq!(
163 merged.get("col1").expect("col1 not found"),
164 &vec!["a", "b", "c", "d"]
165 );
166 assert_eq!(
167 merged.get("col2").expect("col2 not found"),
168 &vec!["1", "2", "3", "4"]
169 );
170 }
171
172 #[test]
173 fn test_memory_estimation() {
174 let columns: QueryColumns = [(
175 "test".to_string(),
176 vec!["hello".to_string(), "world".to_string()],
177 )]
178 .into_iter()
179 .collect();
180
181 let memory = estimate_memory_usage(&columns);
182 assert_eq!(memory, 14);
183 }
184
185 #[test]
186 fn test_streaming_progress() {
187 let mut progress = StreamingProgress::new(Some(1000));
188
189 assert_eq!(progress.percentage(), Some(0.0));
190
191 progress.update(250);
192 assert_eq!(progress.percentage(), Some(25.0));
193
194 progress.update(250);
195 assert_eq!(progress.percentage(), Some(50.0));
196
197 assert_eq!(progress.batches_processed, 2);
198 assert_eq!(progress.processed_rows, 500);
199 }
200}