dataprof_db/connectors/
common.rs1use crate::DataProfilerError;
7use crate::security::{validate_base_query, validate_sql_identifier};
8
9#[allow(dead_code)]
11pub fn not_connected_error() -> DataProfilerError {
12 DataProfilerError::database_connection("Not connected to database")
13}
14
15#[allow(dead_code)]
17pub fn feature_not_enabled_error(db_name: &str, feature: &str) -> DataProfilerError {
18 DataProfilerError::database_feature_disabled(db_name, feature)
19}
20
21#[macro_export]
43macro_rules! db_column_to_string {
44 ($row:expr, $index:expr) => {{
45 let row = $row;
46 let index = $index;
47
48 if let Ok(v) = row.try_get::<Option<String>, _>(index) {
49 v
50 } else if let Ok(v) = row.try_get::<Option<i64>, _>(index) {
51 v.map(|x| x.to_string())
52 } else if let Ok(v) = row.try_get::<Option<i32>, _>(index) {
53 v.map(|x| x.to_string())
54 } else if let Ok(v) = row.try_get::<Option<i16>, _>(index) {
55 v.map(|x| x.to_string())
56 } else if let Ok(v) = row.try_get::<Option<f64>, _>(index) {
57 v.map(|x| format!("{:?}", x))
61 } else if let Ok(v) = row.try_get::<Option<f32>, _>(index) {
62 v.map(|x| format!("{:?}", x))
63 } else if let Ok(v) = row.try_get::<Option<bool>, _>(index) {
64 v.map(|x| x.to_string())
65 } else {
66 None
67 }
68 }};
69}
70
71#[macro_export]
73macro_rules! streaming_profile_loop {
74 ($pool:expr, $query:expr, $batch_size:expr, $total_rows:expr, $db_name:literal) => {{
75 use sqlx::{Column, Row};
76 use $crate::connectors::common::build_batch_query;
77 use $crate::streaming::{StreamingProgress, merge_column_batches};
78
79 let mut progress = StreamingProgress::new(Some($total_rows as u64));
80 let mut all_batches: Vec<std::collections::HashMap<String, Vec<String>>> = Vec::new();
81 let mut offset = 0usize;
82
83 loop {
84 let batch_query = build_batch_query($query, $batch_size, offset)?;
85 let rows = sqlx::query(&batch_query)
86 .fetch_all($pool)
87 .await
88 .map_err(|e| $crate::DataProfilerError::DatabaseQueryError {
89 message: format!("Batch query execution failed: {}", e),
90 })?;
91
92 if rows.is_empty() {
93 break;
94 }
95
96 let columns = rows[0].columns();
97 let mut batch_result: std::collections::HashMap<String, Vec<String>> =
98 std::collections::HashMap::with_capacity(columns.len());
99
100 for col in columns {
101 batch_result.insert(col.name().to_string(), Vec::with_capacity(rows.len()));
102 }
103
104 for row in &rows {
105 for (i, col) in columns.iter().enumerate() {
106 let value: Option<String> = $crate::db_column_to_string!(row, i);
107 if let Some(column_data) = batch_result.get_mut(col.name()) {
108 column_data.push(value.unwrap_or_default());
112 }
113 }
114 }
115
116 let batch_size_actual = rows.len();
117 all_batches.push(batch_result);
118 progress.update(batch_size_actual as u64);
119
120 if let Some(percentage) = progress.percentage() {
121 log::info!(
122 "{} streaming progress: {:.1}% ({}/{} rows)",
123 $db_name,
124 percentage,
125 progress.processed_rows,
126 $total_rows
127 );
128 }
129
130 offset += $batch_size;
131 if batch_size_actual < $batch_size {
132 break;
133 }
134 }
135
136 merge_column_batches(all_batches)
137 }};
138}
139
140#[macro_export]
142macro_rules! process_rows_to_columns {
143 ($rows:expr) => {{
144 use sqlx::{Column, Row};
145
146 if $rows.is_empty() {
147 std::collections::HashMap::new()
148 } else {
149 let columns = $rows[0].columns();
150 let mut result: std::collections::HashMap<String, Vec<String>> =
151 std::collections::HashMap::with_capacity(columns.len());
152
153 for col in columns {
154 result.insert(col.name().to_string(), Vec::with_capacity($rows.len()));
155 }
156
157 for row in &$rows {
158 for (i, col) in columns.iter().enumerate() {
159 let value: Option<String> = $crate::db_column_to_string!(row, i);
160 if let Some(column_data) = result.get_mut(col.name()) {
161 column_data.push(value.unwrap_or_default());
165 }
166 }
167 }
168
169 result
170 }
171 }};
172}
173
174#[allow(dead_code)]
176pub fn build_count_query(query: &str) -> Result<String, DataProfilerError> {
177 if query.trim().to_uppercase().starts_with("SELECT") {
178 let validated_query = validate_base_query(query)?;
179 Ok(format!(
180 "SELECT COUNT(*) FROM ({}) as count_subquery",
181 validated_query
182 ))
183 } else {
184 validate_sql_identifier(query)?;
185 Ok(format!("SELECT COUNT(*) FROM {}", query))
186 }
187}
188
189#[allow(dead_code)]
191pub fn build_batch_query(
192 query: &str,
193 batch_size: usize,
194 offset: usize,
195) -> Result<String, DataProfilerError> {
196 let validated_query = if query.trim().to_uppercase().starts_with("SELECT") {
197 validate_base_query(query)?
198 } else {
199 validate_sql_identifier(query)?;
200 format!("SELECT * FROM {}", query)
201 };
202 Ok(format!(
203 "{} LIMIT {} OFFSET {}",
204 validated_query, batch_size, offset
205 ))
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211
212 #[test]
213 fn test_build_count_query_table() {
214 let result = build_count_query("users").unwrap();
215 assert_eq!(result, "SELECT COUNT(*) FROM users");
216 }
217
218 #[test]
219 fn test_build_count_query_select() {
220 let result = build_count_query("SELECT * FROM users WHERE active = true").unwrap();
221 assert!(result.contains("SELECT COUNT(*) FROM"));
222 assert!(result.contains("count_subquery"));
223 }
224
225 #[test]
226 fn test_build_batch_query() {
227 let result = build_batch_query("users", 100, 0).unwrap();
228 assert_eq!(result, "SELECT * FROM users LIMIT 100 OFFSET 0");
229 }
230}