sql_cli/services/
data_loader_service.rs

1use crate::data::csv_datasource::CsvApiClient;
2use crate::data::data_view::DataView;
3use crate::data::datatable::DataTable;
4use crate::ui::utils::enhanced_tui_helpers;
5use anyhow::Result;
6use std::path::Path;
7use std::sync::Arc;
8use tracing::{info, warn};
9
10/// Service responsible for loading data from various sources
11/// This encapsulates all file loading logic that was previously in the TUI
12pub struct DataLoaderService {
13    case_insensitive: bool,
14}
15
16impl DataLoaderService {
17    pub fn new(case_insensitive: bool) -> Self {
18        Self { case_insensitive }
19    }
20
21    /// Load a file and return a DataView
22    /// The TUI doesn't need to know about file types or loading strategies
23    pub fn load_file(&self, file_path: &str) -> Result<DataLoadResult> {
24        let path = Path::new(file_path);
25        let extension = path
26            .extension()
27            .and_then(|e| e.to_str())
28            .ok_or_else(|| anyhow::anyhow!("File has no extension: {}", file_path))?;
29
30        let raw_table_name = path
31            .file_stem()
32            .and_then(|s| s.to_str())
33            .unwrap_or("data")
34            .to_string();
35
36        // Sanitize the table name to be SQL-friendly
37        let table_name = enhanced_tui_helpers::sanitize_table_name(&raw_table_name);
38
39        match extension.to_lowercase().as_str() {
40            "csv" => self.load_csv(file_path, &table_name, &raw_table_name),
41            "json" => self.load_json(file_path, &table_name, &raw_table_name),
42            _ => Err(anyhow::anyhow!(
43                "Unsupported file type: {}. Use .csv or .json files.",
44                extension
45            )),
46        }
47    }
48
49    /// Load a CSV file
50    fn load_csv(
51        &self,
52        file_path: &str,
53        table_name: &str,
54        raw_table_name: &str,
55    ) -> Result<DataLoadResult> {
56        info!("Loading CSV file: {}", file_path);
57        let start = std::time::Instant::now();
58
59        // Try advanced loader first (with string interning)
60        let datatable = match crate::data::advanced_csv_loader::AdvancedCsvLoader::new()
61            .load_csv_optimized(file_path, table_name)
62        {
63            Ok(dt) => {
64                info!("Successfully loaded CSV with advanced optimizations");
65                dt
66            }
67            Err(e) => {
68                warn!(
69                    "Advanced CSV loader failed: {}, falling back to standard loader",
70                    e
71                );
72                crate::data::datatable_loaders::load_csv_to_datatable(file_path, table_name)?
73            }
74        };
75
76        self.create_result(
77            datatable,
78            file_path.to_string(),
79            table_name.to_string(),
80            raw_table_name.to_string(),
81            start.elapsed(),
82        )
83    }
84
85    /// Load a JSON file
86    fn load_json(
87        &self,
88        file_path: &str,
89        table_name: &str,
90        raw_table_name: &str,
91    ) -> Result<DataLoadResult> {
92        info!("Loading JSON file: {}", file_path);
93        let start = std::time::Instant::now();
94
95        let datatable =
96            crate::data::datatable_loaders::load_json_to_datatable(file_path, table_name)?;
97
98        self.create_result(
99            datatable,
100            file_path.to_string(),
101            table_name.to_string(),
102            raw_table_name.to_string(),
103            start.elapsed(),
104        )
105    }
106
107    /// Load data using CsvApiClient (for additional files)
108    pub fn load_with_client(&self, file_path: &str) -> Result<DataLoadResult> {
109        let mut csv_client = CsvApiClient::new();
110        csv_client.set_case_insensitive(self.case_insensitive);
111
112        let path = Path::new(file_path);
113        let raw_table_name = path
114            .file_stem()
115            .and_then(|s| s.to_str())
116            .unwrap_or("data")
117            .to_string();
118
119        // Sanitize the table name to be SQL-friendly
120        let table_name = enhanced_tui_helpers::sanitize_table_name(&raw_table_name);
121
122        let extension = path
123            .extension()
124            .and_then(|e| e.to_str())
125            .ok_or_else(|| anyhow::anyhow!("File has no extension: {}", file_path))?;
126
127        let start = std::time::Instant::now();
128
129        match extension.to_lowercase().as_str() {
130            "csv" => csv_client.load_csv(file_path, &table_name)?,
131            "json" => csv_client.load_json(file_path, &table_name)?,
132            _ => return Err(anyhow::anyhow!("Unsupported file type: {}", extension)),
133        }
134
135        // Get the DataTable from the client
136        let datatable = csv_client
137            .get_datatable()
138            .ok_or_else(|| anyhow::anyhow!("Failed to load data from {}", file_path))?;
139
140        self.create_result(
141            datatable,
142            file_path.to_string(),
143            table_name,
144            raw_table_name,
145            start.elapsed(),
146        )
147    }
148
149    /// Create a DataLoadResult from a DataTable
150    fn create_result(
151        &self,
152        datatable: DataTable,
153        source_path: String,
154        table_name: String,
155        raw_table_name: String,
156        load_time: std::time::Duration,
157    ) -> Result<DataLoadResult> {
158        // Create initial DataView
159        let initial_row_count = datatable.row_count();
160        let initial_column_count = datatable.column_count();
161
162        // Create DataView
163        let dataview = DataView::new(Arc::new(datatable));
164
165        Ok(DataLoadResult {
166            dataview,
167            source_path,
168            table_name,
169            raw_table_name,
170            initial_row_count,
171            initial_column_count,
172            load_time,
173        })
174    }
175
176    /// Update configuration
177    pub fn set_case_insensitive(&mut self, case_insensitive: bool) {
178        self.case_insensitive = case_insensitive;
179    }
180}
181
182/// Result of loading data
183pub struct DataLoadResult {
184    /// The loaded DataView ready for display
185    pub dataview: DataView,
186
187    /// Path to the source file
188    pub source_path: String,
189
190    /// SQL-friendly table name
191    pub table_name: String,
192
193    /// Original table name (before sanitization)
194    pub raw_table_name: String,
195
196    /// Initial row count (before any filtering)
197    pub initial_row_count: usize,
198
199    /// Initial column count
200    pub initial_column_count: usize,
201
202    /// Time taken to load the file
203    pub load_time: std::time::Duration,
204}
205
206impl DataLoadResult {
207    /// Generate a status message for the load operation
208    pub fn status_message(&self) -> String {
209        format!(
210            "Loaded {} ({} rows, {} columns) in {} ms",
211            self.source_path,
212            self.initial_row_count,
213            self.initial_column_count,
214            self.load_time.as_millis()
215        )
216    }
217}