sql_cli/data/
data_provider.rs

1//! Data provider traits for abstracting data access
2//!
3//! This module defines the core traits that allow the TUI to work with
4//! data without knowing the underlying implementation (Buffer, `CSVClient`, `DataTable`, etc.)
5
6use std::fmt::Debug;
7use std::hash::Hash;
8
9/// Filter specification for `DataView`
10#[derive(Debug, Clone)]
11pub enum FilterSpec {
12    /// SQL WHERE clause filter
13    WhereClause(String),
14    /// Fuzzy text search across all columns
15    FuzzySearch(String),
16    /// Column-specific filter
17    ColumnFilter { column: usize, pattern: String },
18    /// Custom filter function
19    Custom(String),
20}
21
22/// Sort order for columns
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum SortOrder {
25    Ascending,
26    Descending,
27}
28
29/// Data type for columns
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
31pub enum DataType {
32    Text,
33    Integer,
34    Float,
35    Date,
36    Boolean,
37    Json,
38    Mixed,
39    Unknown,
40}
41
42/// Column statistics
43#[derive(Debug, Clone)]
44pub struct ColumnStats {
45    pub null_count: usize,
46    pub unique_count: usize,
47    pub min_value: Option<String>,
48    pub max_value: Option<String>,
49    pub mean_value: Option<f64>,
50}
51
52/// Core trait for read-only data access
53///
54/// This trait defines the minimal interface that any data source must provide
55/// to be usable by the TUI for rendering and display.
56pub trait DataProvider: Send + Sync + Debug {
57    /// Get a single row by index
58    /// Returns None if the index is out of bounds
59    fn get_row(&self, index: usize) -> Option<Vec<String>>;
60
61    /// Get the column names/headers
62    fn get_column_names(&self) -> Vec<String>;
63
64    /// Get the total number of rows
65    fn get_row_count(&self) -> usize;
66
67    /// Get the total number of columns
68    fn get_column_count(&self) -> usize;
69
70    /// Get multiple rows for efficient rendering
71    /// This is an optimization to avoid multiple `get_row` calls
72    fn get_visible_rows(&self, start: usize, count: usize) -> Vec<Vec<String>> {
73        let mut rows = Vec::new();
74        let end = (start + count).min(self.get_row_count());
75
76        for i in start..end {
77            if let Some(row) = self.get_row(i) {
78                rows.push(row);
79            }
80        }
81
82        rows
83    }
84
85    /// Get the display width for each column
86    /// Used for rendering column widths in the TUI
87    fn get_column_widths(&self) -> Vec<usize> {
88        // Default implementation: calculate from first 100 rows
89        let mut widths = vec![0; self.get_column_count()];
90        let sample_size = 100.min(self.get_row_count());
91
92        // Start with column name widths
93        for (i, name) in self.get_column_names().iter().enumerate() {
94            if i < widths.len() {
95                widths[i] = name.len();
96            }
97        }
98
99        // Check first 100 rows for max width
100        for row_idx in 0..sample_size {
101            if let Some(row) = self.get_row(row_idx) {
102                for (col_idx, value) in row.iter().enumerate() {
103                    if col_idx < widths.len() {
104                        widths[col_idx] = widths[col_idx].max(value.len());
105                    }
106                }
107            }
108        }
109
110        widths
111    }
112
113    /// Get a single cell value
114    /// Returns None if row or column index is out of bounds
115    fn get_cell_value(&self, row: usize, col: usize) -> Option<String> {
116        self.get_row(row).and_then(|r| r.get(col).cloned())
117    }
118
119    /// Get a display-formatted cell value
120    /// Returns empty string if indices are out of bounds
121    fn get_display_value(&self, row: usize, col: usize) -> String {
122        self.get_cell_value(row, col).unwrap_or_default()
123    }
124
125    /// Get the data type of a specific column
126    /// This should be cached/determined at load time, not computed on each call
127    fn get_column_type(&self, _column_index: usize) -> DataType {
128        // Default implementation: Unknown
129        // Implementations should override with actual type detection
130        DataType::Unknown
131    }
132
133    /// Get data types for all columns
134    /// Returns a vector where index corresponds to column index
135    fn get_column_types(&self) -> Vec<DataType> {
136        // Default implementation: all Unknown
137        vec![DataType::Unknown; self.get_column_count()]
138    }
139}
140
141/// Extended trait for data views that support filtering and sorting
142///
143/// This trait extends `DataProvider` with mutable operations that change
144/// what data is visible without modifying the underlying data.
145pub trait DataViewProvider: DataProvider {
146    /// Apply a filter to the view
147    /// The filter string format depends on the implementation
148    fn apply_filter(&mut self, filter: &str) -> Result<(), String>;
149
150    /// Clear all filters
151    fn clear_filters(&mut self);
152
153    /// Get the number of rows after filtering
154    fn get_filtered_count(&self) -> usize {
155        // Default: same as total count (no filtering)
156        self.get_row_count()
157    }
158
159    /// Sort by a column
160    fn sort_by(&mut self, column_index: usize, ascending: bool) -> Result<(), String>;
161
162    /// Clear sorting and return to original order
163    fn clear_sort(&mut self);
164
165    /// Check if a row index is visible in the current view
166    fn is_row_visible(&self, row_index: usize) -> bool {
167        row_index < self.get_row_count()
168    }
169
170    /// Get sorted indices for a column (for read-only sorting)
171    /// Returns a vector of indices in sorted order
172    fn get_sorted_indices(&self, _column_index: usize, _ascending: bool) -> Vec<usize> {
173        // Default implementation: return unsorted indices
174        (0..self.get_row_count()).collect()
175    }
176
177    /// Check if data is currently sorted
178    fn is_sorted(&self) -> bool {
179        false
180    }
181
182    /// Get current sort state
183    fn get_sort_state(&self) -> Option<(usize, bool)> {
184        None // Returns (column_index, is_ascending)
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191
192    /// Mock implementation for testing
193    #[derive(Debug)]
194    struct MockDataProvider {
195        columns: Vec<String>,
196        rows: Vec<Vec<String>>,
197    }
198
199    impl DataProvider for MockDataProvider {
200        fn get_row(&self, index: usize) -> Option<Vec<String>> {
201            self.rows.get(index).cloned()
202        }
203
204        fn get_column_names(&self) -> Vec<String> {
205            self.columns.clone()
206        }
207
208        fn get_row_count(&self) -> usize {
209            self.rows.len()
210        }
211
212        fn get_column_count(&self) -> usize {
213            self.columns.len()
214        }
215    }
216
217    #[test]
218    fn test_data_provider_basics() {
219        let provider = MockDataProvider {
220            columns: vec!["ID".to_string(), "Name".to_string(), "Age".to_string()],
221            rows: vec![
222                vec!["1".to_string(), "Alice".to_string(), "30".to_string()],
223                vec!["2".to_string(), "Bob".to_string(), "25".to_string()],
224            ],
225        };
226
227        assert_eq!(provider.get_row_count(), 2);
228        assert_eq!(provider.get_column_count(), 3);
229        assert_eq!(provider.get_column_names(), vec!["ID", "Name", "Age"]);
230        assert_eq!(
231            provider.get_row(0),
232            Some(vec!["1".to_string(), "Alice".to_string(), "30".to_string()])
233        );
234        assert_eq!(provider.get_cell_value(1, 1), Some("Bob".to_string()));
235    }
236
237    #[test]
238    fn test_get_visible_rows() {
239        let provider = MockDataProvider {
240            columns: vec!["Col1".to_string()],
241            rows: (0..10).map(|i| vec![format!("Row{}", i)]).collect(),
242        };
243
244        let visible = provider.get_visible_rows(2, 3);
245        assert_eq!(visible.len(), 3);
246        assert_eq!(visible[0], vec!["Row2"]);
247        assert_eq!(visible[2], vec!["Row4"]);
248    }
249
250    #[test]
251    fn test_column_widths() {
252        let provider = MockDataProvider {
253            columns: vec!["ID".to_string(), "LongColumnName".to_string()],
254            rows: vec![
255                vec!["123456".to_string(), "Short".to_string()],
256                vec!["1".to_string(), "Value".to_string()],
257            ],
258        };
259
260        let widths = provider.get_column_widths();
261        assert_eq!(widths[0], 6); // "123456" is longest
262        assert_eq!(widths[1], 14); // "LongColumnName" is longest
263    }
264}