Skip to content

Query parameters and background tasks #811

@abizjak

Description

@abizjak

It is a bit unclear to me what the intended way to use query_raw is with a prepared statement where the different parameters have different types, so this issue might be in the wrong path.

Consider the following contrived example

pub async fn query_wrapper(client: &tokio_postgres::Client) -> Result<u64, tokio_postgres::Error> {
    let statement = {
        let statement = "SELECT table.id FROM table WHERE table.one >= $1 AND table.two >= $2";
        client.prepare(statement).await?
    };
    let params = [&1i32 as &(dyn ToSql), &2i32 as &(dyn ToSql)];
    let rows = client.query_raw(&statement, params).await?;
    Ok(rows.fold(0, |x, _| async move {x + 1}).await)
}

This is all fine and the query works, but unfortunately the resulting Future is not Send and consequently the following fails to compile

pub async fn background_wrapper(
    client: tokio_postgres::Client,
) -> tokio::task::JoinHandle<Result<u64, tokio_postgres::Error>> {
    tokio::spawn(async move { query_wrapper(&client).await })
}

Replacing the query_wrapper with

pub async fn query_wrapper(client: &tokio_postgres::Client) -> Result<u64, tokio_postgres::Error> {
    let statement = {
        let statement = "SELECT table.id FROM table WHERE table.one >= $1 AND table.two >= $2";
        client.prepare(statement).await?
    };
    let params = [&1i32 as &i32, &2i32 as &i32];
    let rows = client.query_raw(&statement, params).await?;
    Ok(rows.fold(0, |x, _| async move {x + 1}).await)
}

makes everything work. This is all understandable. The state machine produced by the first query_wrapper contains a value of type [&dyn ToSql; 2], which is not Send.

As far as I can see the only way to pass parameters of different types to query_raw is to use trait objects.

There appear to be two problems here. One is that there is a value of a non-Send type. This could in principle be solved by using

let params = [&1i32 as &(dyn ToSql + Sync), &2i32 as &(dyn ToSql + Sync)];

and on its own that would work but it does not in combination with query_raw. We get the following typechecking error

error[E0277]: the size for values of type `dyn ToSql + Sync` cannot be known at compilation time
  --> src/lib.rs:11:45
   |
11 |     let rows = client.query_raw(&statement, params).await?;
   |                                             ^^^^^^ doesn't have a size known at compile-time
   |
   = help: the trait `Sized` is not implemented for `dyn ToSql + Sync`
   = note: required because of the requirements on the impl of `ToSql` for `&dyn ToSql + Sync`
   = note: required because of the requirements on the impl of `BorrowToSql` for `&dyn ToSql + Sync`

The reason for this is that the compiler is trying to find out whether &(dyn ToSql + Sync) implements BorrowToSql, and it is doing so via the instance

impl<T> BorrowToSql for T
where
    T: ToSql

which then does not work.

One solution to this issue is to add another instance of BorrowToSql,

impl sealed::Sealed for &(dyn ToSql + Sync) {}

impl BorrowToSql for &(dyn ToSql + Sync) {
    #[inline]
    fn borrow_to_sql(&self) -> &dyn ToSql {
        *self
    }
}

I am happy to add a PR implementing this if that is desirable. I don't have an overview of the design of the library so I do not know whether this would prejudice any future extensions.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions