Hey!
I am new to rust, building a Rust CRUD api to play around and I am trying to build SQL queries dynamically. Is that possible?
Something like:
async fn update_query<T, DB, A>(
table_name: &str,
id: Uuid,
user_id: Uuid,
request: &T,
) -> Result<Query<'static, DB, <DB as HasArguments<'_>>::Arguments>, String>
where
T: Serialize,
DB: Database,
// Ensure the database can encode/bind the types we use
for<'q> &'q str: Encode<'q, DB> + Type<DB>,
for<'q> String: Encode<'q, DB> + Type<DB>,
for<'q> bool: Encode<'q, DB> + Type<DB>,
{
let request_json = serde_json::to_value(request)
.map_err(|e| e.to_string())?;
let mut parameters: Vec<Value> = Vec::new();
let mut set_parts: Vec<String> = Vec::new();
if let Some(obj) = request_json.as_object() {
for (key, value) in obj {
if value != &Value::Null {
let param_index = parameters.len() + 1; // SQL parameters are 1-indexed
set_parts.push(format!("{} = ${}", key, param_index));
parameters.push(value.clone());
}
}
}
parameters.push(json!(id));
parameters.push(json!(user_id));
let query_str = format!(
"UPDATE {} SET {} WHERE id = ${} AND user_id = ${}",
table_name,
set_parts.join(", "),
parameters.len() - 1,
parameters.len()
);
let mut query = sqlx::query(&query_str);
for parameter in parameters.iter() {
match parameter {
Value::String(s) => query = query.bind(s),
Value::Bool(b) => query = query.bind(b),
// Add other types as necessary
_ => return Err("Unsupported parameter type".into()),
}
}
Ok(query)
}
Whre I would have different requests, lets imagine my object car:
#[derive(Serialize, Deserialize)]
pub struct UpdateCarRequest {
license_plate: Option<String>,
name: Option<String>,
country: Option<String>,
state: Option<String>,
enabled: Option<bool>,
}
and I could call the main function such as any other query:
update_query("car", car_id, user_id, &car_request.into_inner())
.execute(pool)
.await
Does any of this makes sense? I want essentially be able to define structs with optional values, that if present, can be added to the SQL statement.