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