Skip to content

limitation in how parameters are passed to *_raw methods and ToSql #683

Closed
@vemoo

Description

@vemoo

I was trying to implement inserting a lot of rows in batches and I think I found a limitation in the way parameters are passed to the *_raw methods. Here's some code to explain what I was trying to do (the code is incomplete, I've added just enough to ilustrate the issue):

struct Data {
    id: u16,
    data: serde_json::Value,
}

async fn insert_data(cnn:&tokio_postgres::Client, xs:&[Data]) -> Result<()> {
    let sql = ... // sql to insert xs.len() rows
    let params = ... // create some iterator from xs
    cnn.query_raw(sql, prams).await?;
    Ok(())
}

Next I tried to implement an interator that returns all the parameters for all rows from xs. So I created the following:

struct DataParams<'a> {
    xs:&'a [Data]
}

impl<'a> Iterator for DataParams<'a> {
    type Item = &'a dyn tokio_postgres::types::ToSql;
    ...
}

But this doesn't work because u16 and serde_json::Value don't implement ToSql and I need to convert or wrap them in a type that does implement it, so i cannot return an element with lifetime 'a. Then I had the following idea:

enum DataParam<'a> {    
    Id(i32),
    Data(tokio_postgres::types::Json(&'a serde_json::Value)),
}
impl<'a> Iterator for DataParams<'a> {
    type Item = DataParam<'a>;
    ...
}
impl<'a> tokio_postgres::types::ToSql for DataParam<'a> {
    ...
}

but also ends up not working because query_raw needs an iterator that returns references to dyn tokio_postgres::types::ToSql, and I don't think that can be achieved with this strategy.

As a workaround I ended up creating a Vec<DataParam<'a>> and creating the iterator from it, but i started looking at the rust-postgres code to see if query_raw could be changed to receive an iterator to some type that implements ToSql. After some investigation I think that can be done but it requires adding:

impl ToSql for &dyn ToSql {
    fn to_sql(&self, ty: &Type, w: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
        (*self).to_sql_checked(ty, w)
    }

    fn accepts(_ty: &Type) -> bool {
        true
    }

    to_sql_checked!();
}

that feels a bit wrong.

Is this something that's worth investigating? or maybe I missed something, or the workaround of using a Vec is good enough?

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