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<$crate::QueryColumns> = 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 column_names: Vec<String> = columns
98 .iter()
99 .map(|column| column.name().to_string())
100 .collect();
101 dataprof_core::validate_unique_column_names(
102 &column_names,
103 concat!($db_name, " query result"),
104 )?;
105 let mut batch_result =
108 $crate::QueryColumns::with_names(column_names.clone(), rows.len());
109
110 for row in &rows {
111 for i in 0..columns.len() {
112 let value: Option<String> = $crate::db_column_to_string!(row, i);
113 batch_result.push_value(i, value.unwrap_or_default());
117 }
118 }
119
120 let batch_size_actual = rows.len();
121 all_batches.push(batch_result);
122 progress.update(batch_size_actual as u64);
123
124 if let Some(percentage) = progress.percentage() {
125 log::info!(
126 "{} streaming progress: {:.1}% ({}/{} rows)",
127 $db_name,
128 percentage,
129 progress.processed_rows,
130 $total_rows
131 );
132 }
133
134 offset += $batch_size;
135 if batch_size_actual < $batch_size {
136 break;
137 }
138 }
139
140 Ok(merge_column_batches(all_batches))
141 }};
142}
143
144#[macro_export]
146macro_rules! process_rows_to_columns {
147 ($rows:expr) => {{
148 use sqlx::{Column, Row};
149
150 if $rows.is_empty() {
151 Ok($crate::QueryColumns::new())
152 } else {
153 let columns = $rows[0].columns();
154 let column_names: Vec<String> = columns
155 .iter()
156 .map(|column| column.name().to_string())
157 .collect();
158 match dataprof_core::validate_unique_column_names(
159 &column_names,
160 "database query result",
161 ) {
162 Err(error) => Err(error),
163 Ok(()) => {
164 let mut result = $crate::QueryColumns::with_names(column_names, $rows.len());
167
168 for row in &$rows {
169 for i in 0..columns.len() {
170 let value: Option<String> = $crate::db_column_to_string!(row, i);
171 result.push_value(i, value.unwrap_or_default());
175 }
176 }
177
178 Ok(result)
179 }
180 }
181 }
182 }};
183}
184
185#[allow(dead_code)]
187pub fn build_count_query(query: &str) -> Result<String, DataProfilerError> {
188 if query.trim().to_uppercase().starts_with("SELECT") {
189 let validated_query = validate_base_query(query)?;
190 Ok(format!(
191 "SELECT COUNT(*) FROM ({}) as count_subquery",
192 validated_query
193 ))
194 } else {
195 validate_sql_identifier(query)?;
196 Ok(format!("SELECT COUNT(*) FROM {}", query))
197 }
198}
199
200#[allow(dead_code)]
202pub fn build_batch_query(
203 query: &str,
204 batch_size: usize,
205 offset: usize,
206) -> Result<String, DataProfilerError> {
207 let validated_query = if query.trim().to_uppercase().starts_with("SELECT") {
208 validate_base_query(query)?
209 } else {
210 validate_sql_identifier(query)?;
211 format!("SELECT * FROM {}", query)
212 };
213 Ok(format!(
214 "{} LIMIT {} OFFSET {}",
215 validated_query, batch_size, offset
216 ))
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 #[test]
224 fn test_build_count_query_table() {
225 let result = build_count_query("users").unwrap();
226 assert_eq!(result, "SELECT COUNT(*) FROM users");
227 }
228
229 #[test]
230 fn test_build_count_query_select() {
231 let result = build_count_query("SELECT * FROM users WHERE active = true").unwrap();
232 assert!(result.contains("SELECT COUNT(*) FROM"));
233 assert!(result.contains("count_subquery"));
234 }
235
236 #[test]
237 fn test_build_batch_query() {
238 let result = build_batch_query("users", 100, 0).unwrap();
239 assert_eq!(result, "SELECT * FROM users LIMIT 100 OFFSET 0");
240 }
241}