sfackler / rust-postgres

Native PostgreSQL driver for the Rust programming language
Apache License 2.0
3.43k stars 436 forks source link

Problem with dynamic non-string params (via JSON) #1030

Closed jawj closed 1 year ago

jawj commented 1 year ago

I'm very new to Rust, so apologies in advance if I've missed something obvious here. I'm trying to run dynamic queries created from JSON data, and I just can't seem to figure out what's going wrong.

Here's the key bit of code:

#[derive(serde::Deserialize)]
struct QueryData {
    query: String,
    params: Vec<serde_json::Value>
}
let query_data: QueryData = serde_json::from_slice(&data)?;

let query = &query_data.query;
let query_params = query_data.params.iter().map(|value| {
    let boxed: Box<dyn ToSql + Sync + Send> = match value {
        Value::Null => Box::new(None::<bool>),
        Value::Bool(b) => Box::new(b.clone()),
        Value::Number(n) => Box::new(n.as_f64().unwrap()),
        Value::String(s) => Box::new(s.clone()),
        _ => panic!("wrong parameter type")
    };
    boxed
}).collect::<Vec<_>>();

let pg_rows: Vec<Row> = client
    .query_raw(query, query_params)
    .await?
    .try_collect::<Vec<Row>>()
    .await?;

This compiles and runs, and when the query params are all strings (e.g. the incoming JSON is {"query":"SELECT $1","params":["xyz"]}) it all works fine. But any non-string query param makes it blow up.

For example, {"query":"SELECT $1","params":[123]} blows up with:

error serializing parameter 0: cannot convert between the Rust type `f64` and the Postgres type `text`

Similarly, {"query":"SELECT $1","params":[true]} gives:

error serializing parameter 0: cannot convert between the Rust type `bool` and the Postgres type `text`

And {"query":"SELECT $1","params":[null]}:

error serializing parameter 0: cannot convert between the Rust type `core::option::Option<bool>` and the Postgres type `text`

I don't understand why it's apparently trying to convert to the Postgres type text: that's not what I want or thought I was asking for. Any hints very much appreciated.

sfackler commented 1 year ago

The query doesn't contain any information about what the type of $1 should be, so it looks like postgres defaults to text. You could edit the query to specify the type with e.g. SELECT $1::BOOLEAN or use https://docs.rs/postgres/latest/postgres/struct.Client.html#method.prepare_typed to specify the parameter type.

jawj commented 1 year ago

OK, many thanks.