sql_cli/services/
data_loader_service.rs1use 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
10pub struct DataLoaderService {
13 case_insensitive: bool,
14}
15
16impl DataLoaderService {
17 #[must_use]
18 pub fn new(case_insensitive: bool) -> Self {
19 Self { case_insensitive }
20 }
21
22 pub fn load_file(&self, file_path: &str) -> Result<DataLoadResult> {
25 let path = Path::new(file_path);
26 let extension = path
27 .extension()
28 .and_then(|e| e.to_str())
29 .ok_or_else(|| anyhow::anyhow!("File has no extension: {}", file_path))?;
30
31 let raw_table_name = path
32 .file_stem()
33 .and_then(|s| s.to_str())
34 .unwrap_or("data")
35 .to_string();
36
37 let table_name = enhanced_tui_helpers::sanitize_table_name(&raw_table_name);
39
40 match extension.to_lowercase().as_str() {
41 "csv" => self.load_csv(file_path, &table_name, &raw_table_name),
42 "json" => self.load_json(file_path, &table_name, &raw_table_name),
43 _ => Err(anyhow::anyhow!(
44 "Unsupported file type: {}. Use .csv or .json files.",
45 extension
46 )),
47 }
48 }
49
50 fn load_csv(
52 &self,
53 file_path: &str,
54 table_name: &str,
55 raw_table_name: &str,
56 ) -> Result<DataLoadResult> {
57 info!("Loading CSV file: {}", file_path);
58 let start = std::time::Instant::now();
59
60 let datatable = match crate::data::advanced_csv_loader::AdvancedCsvLoader::new()
62 .load_csv_optimized(file_path, table_name)
63 {
64 Ok(dt) => {
65 info!("Successfully loaded CSV with advanced optimizations");
66 dt
67 }
68 Err(e) => {
69 warn!(
70 "Advanced CSV loader failed: {}, falling back to standard loader",
71 e
72 );
73 crate::data::datatable_loaders::load_csv_to_datatable(file_path, table_name)?
74 }
75 };
76
77 self.create_result(
78 datatable,
79 file_path.to_string(),
80 table_name.to_string(),
81 raw_table_name.to_string(),
82 start.elapsed(),
83 )
84 }
85
86 fn load_json(
88 &self,
89 file_path: &str,
90 table_name: &str,
91 raw_table_name: &str,
92 ) -> Result<DataLoadResult> {
93 info!("Loading JSON file: {}", file_path);
94 let start = std::time::Instant::now();
95
96 let datatable =
97 crate::data::datatable_loaders::load_json_to_datatable(file_path, table_name)?;
98
99 self.create_result(
100 datatable,
101 file_path.to_string(),
102 table_name.to_string(),
103 raw_table_name.to_string(),
104 start.elapsed(),
105 )
106 }
107
108 pub fn load_with_client(&self, file_path: &str) -> Result<DataLoadResult> {
110 let mut csv_client = CsvApiClient::new();
111 csv_client.set_case_insensitive(self.case_insensitive);
112
113 let path = Path::new(file_path);
114 let raw_table_name = path
115 .file_stem()
116 .and_then(|s| s.to_str())
117 .unwrap_or("data")
118 .to_string();
119
120 let table_name = enhanced_tui_helpers::sanitize_table_name(&raw_table_name);
122
123 let extension = path
124 .extension()
125 .and_then(|e| e.to_str())
126 .ok_or_else(|| anyhow::anyhow!("File has no extension: {}", file_path))?;
127
128 let start = std::time::Instant::now();
129
130 match extension.to_lowercase().as_str() {
131 "csv" => csv_client.load_csv(file_path, &table_name)?,
132 "json" => csv_client.load_json(file_path, &table_name)?,
133 _ => return Err(anyhow::anyhow!("Unsupported file type: {}", extension)),
134 }
135
136 let datatable = csv_client
138 .get_datatable()
139 .ok_or_else(|| anyhow::anyhow!("Failed to load data from {}", file_path))?;
140
141 self.create_result(
142 datatable,
143 file_path.to_string(),
144 table_name,
145 raw_table_name,
146 start.elapsed(),
147 )
148 }
149
150 fn create_result(
152 &self,
153 datatable: DataTable,
154 source_path: String,
155 table_name: String,
156 raw_table_name: String,
157 load_time: std::time::Duration,
158 ) -> Result<DataLoadResult> {
159 let initial_row_count = datatable.row_count();
161 let initial_column_count = datatable.column_count();
162
163 let dataview = DataView::new(Arc::new(datatable));
165
166 Ok(DataLoadResult {
167 dataview,
168 source_path,
169 table_name,
170 raw_table_name,
171 initial_row_count,
172 initial_column_count,
173 load_time,
174 })
175 }
176
177 pub fn set_case_insensitive(&mut self, case_insensitive: bool) {
179 self.case_insensitive = case_insensitive;
180 }
181}
182
183pub struct DataLoadResult {
185 pub dataview: DataView,
187
188 pub source_path: String,
190
191 pub table_name: String,
193
194 pub raw_table_name: String,
196
197 pub initial_row_count: usize,
199
200 pub initial_column_count: usize,
202
203 pub load_time: std::time::Duration,
205}
206
207impl DataLoadResult {
208 #[must_use]
210 pub fn status_message(&self) -> String {
211 format!(
212 "Loaded {} ({} rows, {} columns) in {} ms",
213 self.source_path,
214 self.initial_row_count,
215 self.initial_column_count,
216 self.load_time.as_millis()
217 )
218 }
219}