sql_cli/ui/utils/
column_utils.rs1use crate::buffer::ColumnStatistics;
4use crate::data::data_provider::DataProvider;
5use crate::data_analyzer::{self, DataAnalyzer};
6use std::collections::HashMap;
7
8pub fn calculate_column_statistics(
10 provider: &dyn DataProvider,
11 analyzer: &mut DataAnalyzer,
12 column_index: usize,
13) -> Option<ColumnStatistics> {
14 let headers = provider.get_column_names();
15 if headers.is_empty() || column_index >= headers.len() {
16 return None;
17 }
18
19 let column_name = headers[column_index].clone();
20 let row_count = provider.get_row_count();
21
22 let mut column_data = Vec::with_capacity(row_count);
24 for row_idx in 0..row_count {
25 if let Some(row) = provider.get_row(row_idx) {
26 if column_index < row.len() {
27 column_data.push(row[column_index].clone());
28 } else {
29 column_data.push(String::new());
30 }
31 }
32 }
33
34 let data_refs: Vec<&str> = column_data.iter().map(|s| s.as_str()).collect();
36
37 let analyzer_stats = analyzer.calculate_column_statistics(&column_name, &data_refs);
39
40 Some(ColumnStatistics {
42 column_name: analyzer_stats.column_name,
43 column_type: match analyzer_stats.data_type {
44 data_analyzer::ColumnType::Integer | data_analyzer::ColumnType::Float => {
45 crate::buffer::ColumnType::Numeric
46 }
47 data_analyzer::ColumnType::String
48 | data_analyzer::ColumnType::Boolean
49 | data_analyzer::ColumnType::Date
50 | data_analyzer::ColumnType::Unknown => crate::buffer::ColumnType::String,
51 data_analyzer::ColumnType::Mixed => crate::buffer::ColumnType::Mixed,
52 },
53 total_count: analyzer_stats.total_values,
54 null_count: analyzer_stats.null_values,
55 unique_count: analyzer_stats.unique_values,
56 frequency_map: analyzer_stats.frequency_map,
57 min: analyzer_stats.min_value.and_then(|s| s.parse::<f64>().ok()),
58 max: analyzer_stats.max_value.and_then(|s| s.parse::<f64>().ok()),
59 sum: analyzer_stats.sum_value,
60 mean: analyzer_stats.avg_value,
61 median: analyzer_stats.median_value,
62 })
63}
64
65pub fn calculate_optimal_column_widths(
67 provider: &dyn DataProvider,
68 max_sample_rows: usize,
69) -> Vec<u16> {
70 let column_count = provider.get_column_count();
71 let row_count = provider.get_row_count();
72 let sample_size = row_count.min(max_sample_rows);
73
74 let mut column_widths = vec![0u16; column_count];
75
76 let headers = provider.get_column_names();
78 for (i, header) in headers.iter().enumerate() {
79 if i < column_widths.len() {
80 column_widths[i] = header.len() as u16;
81 }
82 }
83
84 for row_idx in 0..sample_size {
86 if let Some(row) = provider.get_row(row_idx) {
87 for (col_idx, cell) in row.iter().enumerate() {
88 if col_idx < column_widths.len() {
89 column_widths[col_idx] = column_widths[col_idx].max(cell.len() as u16);
90 }
91 }
92 }
93 }
94
95 const MIN_WIDTH: u16 = 5;
97 const MAX_WIDTH: u16 = 50;
98
99 for width in &mut column_widths {
100 *width = (*width).clamp(MIN_WIDTH, MAX_WIDTH);
101 }
102
103 column_widths
104}
105
106pub fn calculate_viewport_column_widths(
108 provider: &dyn DataProvider,
109 viewport_start: usize,
110 viewport_end: usize,
111 max_sample_rows: usize,
112) -> HashMap<usize, u16> {
113 let mut widths = HashMap::new();
114 let row_count = provider.get_row_count();
115 let sample_size = row_count.min(max_sample_rows);
116
117 let headers = provider.get_column_names();
119
120 for col_idx in viewport_start..viewport_end.min(headers.len()) {
122 widths.insert(col_idx, headers[col_idx].len() as u16);
123 }
124
125 for row_idx in 0..sample_size {
127 if let Some(row) = provider.get_row(row_idx) {
128 for col_idx in viewport_start..viewport_end.min(row.len()) {
129 let current_width = widths.get(&col_idx).copied().unwrap_or(0);
130 let cell_width = row[col_idx].len() as u16;
131 widths.insert(col_idx, current_width.max(cell_width));
132 }
133 }
134 }
135
136 const MIN_WIDTH: u16 = 5;
138 const MAX_WIDTH: u16 = 50;
139
140 for width in widths.values_mut() {
141 *width = (*width).clamp(MIN_WIDTH, MAX_WIDTH);
142 }
143
144 widths
145}
146
147pub fn extract_column_values(provider: &dyn DataProvider, column_index: usize) -> Vec<String> {
149 let row_count = provider.get_row_count();
150 let mut values = Vec::with_capacity(row_count);
151
152 for row_idx in 0..row_count {
153 if let Some(row) = provider.get_row(row_idx) {
154 if column_index < row.len() {
155 values.push(row[column_index].clone());
156 } else {
157 values.push(String::new());
158 }
159 }
160 }
161
162 values
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[derive(Debug)]
171 struct MockDataProvider {
172 headers: Vec<String>,
173 rows: Vec<Vec<String>>,
174 }
175
176 impl DataProvider for MockDataProvider {
177 fn get_column_count(&self) -> usize {
178 self.headers.len()
179 }
180
181 fn get_row_count(&self) -> usize {
182 self.rows.len()
183 }
184
185 fn get_column_names(&self) -> Vec<String> {
186 self.headers.clone()
187 }
188
189 fn get_row(&self, index: usize) -> Option<Vec<String>> {
190 self.rows.get(index).cloned()
191 }
192
193 fn get_cell_value(&self, row: usize, col: usize) -> Option<String> {
194 self.rows.get(row).and_then(|r| r.get(col)).cloned()
195 }
196 }
197
198 #[test]
199 fn test_calculate_optimal_widths() {
200 let provider = MockDataProvider {
201 headers: vec![
202 "ID".to_string(),
203 "Name".to_string(),
204 "Description".to_string(),
205 ],
206 rows: vec![
207 vec!["1".to_string(), "Alice".to_string(), "Short".to_string()],
208 vec![
209 "2".to_string(),
210 "Bob".to_string(),
211 "A very long description that should be clamped".to_string(),
212 ],
213 ],
214 };
215
216 let widths = calculate_optimal_column_widths(&provider, 10);
217 assert_eq!(widths.len(), 3);
218 assert!(widths[0] >= 2); assert!(widths[1] >= 4); assert!(widths[2] <= 50); }
222}