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());
109 }
110 }
111 }
112
113 let batch_size_actual = rows.len();
114 all_batches.push(batch_result);
115 progress.update(batch_size_actual as u64);
116
117 if let Some(percentage) = progress.percentage() {
118 log::info!(
119 "{} streaming progress: {:.1}% ({}/{} rows)",
120 $db_name,
121 percentage,
122 progress.processed_rows,
123 $total_rows
124 );
125 }
126
127 offset += $batch_size;
128 if batch_size_actual < $batch_size {
129 break;
130 }
131 }
132
133 merge_column_batches(all_batches)
134 }};
135}
136
137#[macro_export]
139macro_rules! process_rows_to_columns {
140 ($rows:expr) => {{
141 use sqlx::{Column, Row};
142
143 if $rows.is_empty() {
144 std::collections::HashMap::new()
145 } else {
146 let columns = $rows[0].columns();
147 let mut result: std::collections::HashMap<String, Vec<String>> =
148 std::collections::HashMap::with_capacity(columns.len());
149
150 for col in columns {
151 result.insert(col.name().to_string(), Vec::with_capacity($rows.len()));
152 }
153
154 for row in &$rows {
155 for (i, col) in columns.iter().enumerate() {
156 let value: Option<String> = $crate::db_column_to_string!(row, i);
157 if let Some(column_data) = result.get_mut(col.name()) {
158 column_data.push(value.unwrap_or_default());
159 }
160 }
161 }
162
163 result
164 }
165 }};
166}
167
168#[allow(dead_code)]
170pub fn build_count_query(query: &str) -> Result<String, DataProfilerError> {
171 if query.trim().to_uppercase().starts_with("SELECT") {
172 let validated_query = validate_base_query(query)?;
173 Ok(format!(
174 "SELECT COUNT(*) FROM ({}) as count_subquery",
175 validated_query
176 ))
177 } else {
178 validate_sql_identifier(query)?;
179 Ok(format!("SELECT COUNT(*) FROM {}", query))
180 }
181}
182
183#[allow(dead_code)]
185pub fn build_batch_query(
186 query: &str,
187 batch_size: usize,
188 offset: usize,
189) -> Result<String, DataProfilerError> {
190 let validated_query = if query.trim().to_uppercase().starts_with("SELECT") {
191 validate_base_query(query)?
192 } else {
193 validate_sql_identifier(query)?;
194 format!("SELECT * FROM {}", query)
195 };
196 Ok(format!(
197 "{} LIMIT {} OFFSET {}",
198 validated_query, batch_size, offset
199 ))
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205
206 #[test]
207 fn test_build_count_query_table() {
208 let result = build_count_query("users").unwrap();
209 assert_eq!(result, "SELECT COUNT(*) FROM users");
210 }
211
212 #[test]
213 fn test_build_count_query_select() {
214 let result = build_count_query("SELECT * FROM users WHERE active = true").unwrap();
215 assert!(result.contains("SELECT COUNT(*) FROM"));
216 assert!(result.contains("count_subquery"));
217 }
218
219 #[test]
220 fn test_build_batch_query() {
221 let result = build_batch_query("users", 100, 0).unwrap();
222 assert_eq!(result, "SELECT * FROM users LIMIT 100 OFFSET 0");
223 }
224}