85 lines
2.7 KiB
Rust
85 lines
2.7 KiB
Rust
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<AppState>,
|
|
Extension(_claims): Extension<Claims>,
|
|
Path(identifier): Path<String>,
|
|
Query(params): Query<HashMap<String, String>>,
|
|
) -> Result<Json<Value>, 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<String> = 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<Value> = 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");
|
|
}
|
|
}
|