use axum::{ extract::{Extension, Path, Query, State}, http::StatusCode, Json, }; use serde_json::Value; use std::collections::HashMap; use crate::{auth::Claims, models::query::StoredQuery, state::AppState}; pub async fn execute_query( State(state): State, Extension(_claims): Extension, Path(identifier): Path, Query(params): Query>, ) -> Result, StatusCode> { let stored = sqlx::query_as::<_, StoredQuery>( "SELECT id, identifier, sql_template, description, created_at, updated_at FROM queries WHERE identifier = $1" ) .bind(&identifier) .fetch_optional(&state.pool) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? .ok_or(StatusCode::NOT_FOUND)?; let mut sql = stored.sql_template.clone(); let mut bound_vals: Vec = Vec::new(); let mut idx = 1usize; let mut sorted_params: Vec<(String, String)> = params.into_iter().collect(); sorted_params.sort_by(|a, b| b.0.len().cmp(&a.0.len()).then(a.0.cmp(&b.0))); for (name, val) in &sorted_params { let placeholder = format!(":{}", name); if sql.contains(&placeholder) { sql = sql.replace(&placeholder, &format!("${}", idx)); bound_vals.push(val.clone()); idx += 1; } } let mut q = sqlx::query(&sql); for val in &bound_vals { q = q.bind(val.as_str()); } let rows = q.fetch_all(&state.pool).await.map_err(|e| { tracing::error!("query execution error: {}", e); StatusCode::INTERNAL_SERVER_ERROR })?; let json_rows: Vec = rows .into_iter() .map(crate::routes::crud::pg_row_to_json) .collect(); Ok(Json(Value::Array(json_rows))) } #[cfg(test)] mod tests { #[test] fn test_prefix_param_substitution_order() { let template = "SELECT * FROM t WHERE user_id = :user_id AND username = :username"; let mut params: Vec<(String, String)> = vec![ ("user_id".into(), "42".into()), ("username".into(), "alice".into()), ]; params.sort_by(|a, b| b.0.len().cmp(&a.0.len()).then(a.0.cmp(&b.0))); let mut sql = template.to_string(); for (i, (name, _)) in params.iter().enumerate() { sql = sql.replace(&format!(":{}", name), &format!("${}", i + 1)); } assert!( !sql.contains(":username"), "placeholder not replaced: {}", sql ); assert!( !sql.contains(":user_id"), "placeholder not replaced: {}", sql ); // username (len 8) comes first → $1; user_id (len 7) → $2 assert_eq!(sql, "SELECT * FROM t WHERE user_id = $2 AND username = $1"); } }