Mercury/src/routes/admin/queries.rs
Matthew L McPeak a289f7a23e
Some checks failed
ci / test (push) Failing after 6s
ci / build-ui (push) Successful in 13s
ci / publish (push) Has been skipped
Initial Commit
2026-06-17 19:55:23 -04:00

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");
}
}