clickhouse_arrow/native/
progress.rs

1/// Query execution progress.
2/// Values are delta and must be summed.
3///
4/// See <https://clickhouse.com/codebrowser/ClickHouse/src/IO/Progress.h.html>
5#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
6pub struct Progress {
7    pub read_rows:           u64,
8    pub read_bytes:          u64,
9    pub total_rows_to_read:  u64,
10    pub total_bytes_to_read: Option<u64>,
11    pub written_rows:        Option<u64>,
12    pub written_bytes:       Option<u64>,
13    pub elapsed_ns:          Option<u64>,
14}
15
16impl std::ops::Add for Progress {
17    type Output = Progress;
18
19    fn add(self, rhs: Self) -> Self::Output {
20        let sum_opt = |opt1, opt2| match (opt1, opt2) {
21            (Some(a), Some(b)) => Some(a + b),
22            (Some(a), None) => Some(a),
23            (None, Some(b)) => Some(b),
24            (None, None) => None,
25        };
26        Self::Output {
27            read_rows:           self.read_rows + rhs.read_rows,
28            read_bytes:          self.read_bytes + rhs.read_bytes,
29            total_rows_to_read:  self.total_rows_to_read + rhs.total_rows_to_read,
30            total_bytes_to_read: sum_opt(self.total_bytes_to_read, rhs.total_bytes_to_read),
31            written_rows:        sum_opt(self.written_rows, rhs.written_rows),
32            written_bytes:       sum_opt(self.written_bytes, rhs.written_bytes),
33            elapsed_ns:          sum_opt(self.elapsed_ns, rhs.elapsed_ns),
34        }
35    }
36}
37
38impl std::fmt::Display for Progress {
39    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        write!(f, "Progress | Read | Remaining | W Rows | W Bytes | Elapsed")?;
41
42        let Self {
43            read_rows,
44            read_bytes,
45            total_rows_to_read,
46            total_bytes_to_read: _,
47            written_rows,
48            written_bytes,
49            elapsed_ns,
50        } = self;
51
52        write!(
53            f,
54            "{read_rows}/{read_bytes} | {total_rows_to_read} | {written_rows:?} | \
55             {written_bytes:?} | {elapsed_ns:?}"
56        )?;
57
58        Ok(())
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[test]
67    fn test_progress_default() {
68        let progress = Progress::default();
69        assert_eq!(progress.read_rows, 0);
70        assert_eq!(progress.read_bytes, 0);
71        assert_eq!(progress.total_rows_to_read, 0);
72        assert_eq!(progress.total_bytes_to_read, None);
73        assert_eq!(progress.written_rows, None);
74        assert_eq!(progress.written_bytes, None);
75        assert_eq!(progress.elapsed_ns, None);
76    }
77
78    #[test]
79    fn test_progress_creation() {
80        let progress = Progress {
81            read_rows:           100,
82            read_bytes:          1024,
83            total_rows_to_read:  1000,
84            total_bytes_to_read: Some(10240),
85            written_rows:        Some(50),
86            written_bytes:       Some(512),
87            elapsed_ns:          Some(1_000_000),
88        };
89
90        assert_eq!(progress.read_rows, 100);
91        assert_eq!(progress.read_bytes, 1024);
92        assert_eq!(progress.total_rows_to_read, 1000);
93        assert_eq!(progress.total_bytes_to_read, Some(10240));
94        assert_eq!(progress.written_rows, Some(50));
95        assert_eq!(progress.written_bytes, Some(512));
96        assert_eq!(progress.elapsed_ns, Some(1_000_000));
97    }
98
99    #[test]
100    fn test_progress_clone_copy() {
101        let progress = Progress {
102            read_rows:           123,
103            read_bytes:          456,
104            total_rows_to_read:  789,
105            total_bytes_to_read: Some(1011),
106            written_rows:        Some(121),
107            written_bytes:       Some(314),
108            elapsed_ns:          Some(1516),
109        };
110
111        let cloned = progress;
112        let copied = progress;
113
114        assert_eq!(progress, cloned);
115        assert_eq!(progress, copied);
116    }
117
118    #[test]
119    fn test_progress_debug() {
120        let progress = Progress {
121            read_rows:           100,
122            read_bytes:          1024,
123            total_rows_to_read:  1000,
124            total_bytes_to_read: Some(10240),
125            written_rows:        Some(50),
126            written_bytes:       Some(512),
127            elapsed_ns:          Some(1_000_000),
128        };
129
130        let debug_str = format!("{progress:?}");
131        assert!(debug_str.contains("Progress"));
132        assert!(debug_str.contains("100"));
133        assert!(debug_str.contains("1024"));
134    }
135
136    #[test]
137    fn test_progress_add_all_some() {
138        let progress1 = Progress {
139            read_rows:           100,
140            read_bytes:          1024,
141            total_rows_to_read:  1000,
142            total_bytes_to_read: Some(10240),
143            written_rows:        Some(50),
144            written_bytes:       Some(512),
145            elapsed_ns:          Some(1_000_000),
146        };
147
148        let progress2 = Progress {
149            read_rows:           200,
150            read_bytes:          2048,
151            total_rows_to_read:  2000,
152            total_bytes_to_read: Some(20480),
153            written_rows:        Some(100),
154            written_bytes:       Some(1024),
155            elapsed_ns:          Some(2_000_000),
156        };
157
158        let result = progress1 + progress2;
159
160        assert_eq!(result.read_rows, 300);
161        assert_eq!(result.read_bytes, 3072);
162        assert_eq!(result.total_rows_to_read, 3000);
163        assert_eq!(result.total_bytes_to_read, Some(30720));
164        assert_eq!(result.written_rows, Some(150));
165        assert_eq!(result.written_bytes, Some(1536));
166        assert_eq!(result.elapsed_ns, Some(3_000_000));
167    }
168
169    #[test]
170    fn test_progress_add_mixed_options() {
171        let progress1 = Progress {
172            read_rows:           100,
173            read_bytes:          1024,
174            total_rows_to_read:  1000,
175            total_bytes_to_read: Some(10240),
176            written_rows:        Some(50),
177            written_bytes:       None,
178            elapsed_ns:          Some(1_000_000),
179        };
180
181        let progress2 = Progress {
182            read_rows:           200,
183            read_bytes:          2048,
184            total_rows_to_read:  2000,
185            total_bytes_to_read: None,
186            written_rows:        None,
187            written_bytes:       Some(1024),
188            elapsed_ns:          None,
189        };
190
191        let result = progress1 + progress2;
192
193        assert_eq!(result.read_rows, 300);
194        assert_eq!(result.read_bytes, 3072);
195        assert_eq!(result.total_rows_to_read, 3000);
196        assert_eq!(result.total_bytes_to_read, Some(10240)); // Some + None = Some
197        assert_eq!(result.written_rows, Some(50)); // Some + None = Some
198        assert_eq!(result.written_bytes, Some(1024)); // None + Some = Some
199        assert_eq!(result.elapsed_ns, Some(1_000_000)); // Some + None = Some
200    }
201
202    #[test]
203    fn test_progress_add_all_none() {
204        let progress1 = Progress {
205            read_rows:           100,
206            read_bytes:          1024,
207            total_rows_to_read:  1000,
208            total_bytes_to_read: None,
209            written_rows:        None,
210            written_bytes:       None,
211            elapsed_ns:          None,
212        };
213
214        let progress2 = Progress {
215            read_rows:           200,
216            read_bytes:          2048,
217            total_rows_to_read:  2000,
218            total_bytes_to_read: None,
219            written_rows:        None,
220            written_bytes:       None,
221            elapsed_ns:          None,
222        };
223
224        let result = progress1 + progress2;
225
226        assert_eq!(result.read_rows, 300);
227        assert_eq!(result.read_bytes, 3072);
228        assert_eq!(result.total_rows_to_read, 3000);
229        assert_eq!(result.total_bytes_to_read, None); // None + None = None
230        assert_eq!(result.written_rows, None); // None + None = None
231        assert_eq!(result.written_bytes, None); // None + None = None
232        assert_eq!(result.elapsed_ns, None); // None + None = None
233    }
234
235    #[test]
236    fn test_progress_display() {
237        let progress = Progress {
238            read_rows:           100,
239            read_bytes:          1024,
240            total_rows_to_read:  1000,
241            total_bytes_to_read: Some(10240),
242            written_rows:        Some(50),
243            written_bytes:       Some(512),
244            elapsed_ns:          Some(1_000_000),
245        };
246
247        let display_str = format!("{progress}");
248
249        // Check that the display string contains expected components
250        assert!(display_str.contains("Progress"));
251        assert!(display_str.contains("Read"));
252        assert!(display_str.contains("Remaining"));
253        assert!(display_str.contains("W Rows"));
254        assert!(display_str.contains("W Bytes"));
255        assert!(display_str.contains("Elapsed"));
256        assert!(display_str.contains("100/1024"));
257        assert!(display_str.contains("1000"));
258        assert!(display_str.contains("Some(50)"));
259        assert!(display_str.contains("Some(512)"));
260        assert!(display_str.contains("Some(1000000)"));
261    }
262
263    #[test]
264    fn test_progress_display_with_nones() {
265        let progress = Progress {
266            read_rows:           100,
267            read_bytes:          1024,
268            total_rows_to_read:  1000,
269            total_bytes_to_read: None,
270            written_rows:        None,
271            written_bytes:       None,
272            elapsed_ns:          None,
273        };
274
275        let display_str = format!("{progress}");
276
277        // Check that None values are displayed as "None"
278        assert!(display_str.contains("None"));
279        assert!(display_str.contains("100/1024"));
280        assert!(display_str.contains("1000"));
281    }
282
283    #[test]
284    fn test_progress_equality() {
285        let progress1 = Progress {
286            read_rows:           100,
287            read_bytes:          1024,
288            total_rows_to_read:  1000,
289            total_bytes_to_read: Some(10240),
290            written_rows:        Some(50),
291            written_bytes:       Some(512),
292            elapsed_ns:          Some(1_000_000),
293        };
294
295        let progress2 = Progress {
296            read_rows:           100,
297            read_bytes:          1024,
298            total_rows_to_read:  1000,
299            total_bytes_to_read: Some(10240),
300            written_rows:        Some(50),
301            written_bytes:       Some(512),
302            elapsed_ns:          Some(1_000_000),
303        };
304
305        let progress3 = Progress {
306            read_rows:           200, // Different value
307            read_bytes:          1024,
308            total_rows_to_read:  1000,
309            total_bytes_to_read: Some(10240),
310            written_rows:        Some(50),
311            written_bytes:       Some(512),
312            elapsed_ns:          Some(1_000_000),
313        };
314
315        assert_eq!(progress1, progress2);
316        assert_ne!(progress1, progress3);
317    }
318}