简体   繁体   中英

Allowing an &[impl Trait] to behave like an &[&dyn Trait]

Forgive in advance for the bad title. I will try to be clear in the description.

I am making an application that requires to work with tokio_postresql and tiberius . I need to provide query parameters for both connectors. This are their signatures.

postgresql

tokio_postgres::client::Client
pub async fn query<T>(&self, statement: &T, params: &[&dyn ToSql + Sync]) -> Result<Vec<Row>, Error>

tiberius

tiberius::query::Query
pub fn bind(&mut self, param: impl IntoSql<'a> + 'a)

As you may observe, tokio_postres admits a reference to an array a trait objets, which is really convenient. But, my bottleneck is with the param of tiberius.

Here's my code:

#[async_trait]
pub trait Transaction<T: Debug> {
    /// Performs the necessary to execute a query against the database
    async fn query<'a>(stmt: String, params: &'a [&'a (dyn QueryParameters<'a> + Sync)], datasource_name: &'a str) 
        -> Result<DatabaseResult<T>, Box<(dyn std::error::Error + Sync + Send + 'static)>>
    {
        let database_connection = if datasource_name == "" {
            DatabaseConnection::new(&DEFAULT_DATASOURCE.properties).await
        } else { // Get the specified one
            DatabaseConnection::new(
                &DATASOURCES.iter()
                .find( |ds| ds.name == datasource_name)
                .expect(&format!("No datasource found with the specified parameter: `{}`", datasource_name))
                .properties
            ).await
        };

        if let Err(_db_conn) = database_connection {
            todo!();
        } else {
            // No errors
            let db_conn = database_connection.ok().unwrap();
             
            match db_conn.database_type {
                DatabaseType::PostgreSql => {
                    let mut m_params: Vec<&(dyn ToSql + Sync)> = Vec::new();
                    for p in params.iter() {
                        m_params.push(&p as &(dyn ToSql + Sync))
                    }
                    postgres_query_launcher::launch::<T>(db_conn, stmt, params).await
                },
                DatabaseType::SqlServer =>
                    sqlserver_query_launcher::launch::<T>(db_conn, stmt, params).await
            }
        }
    }
}

where QueryParameters :

pub trait QueryParameters<'a> {}

impl<'a> QueryParameters<'a> for i32 {}
impl<'a> QueryParameters<'a> for i64 {}
impl<'a> QueryParameters<'a> for &'a str {}
impl<'a> QueryParameters<'a> for String {}
impl<'a> QueryParameters<'a> for &'a String {}
impl<'a> QueryParameters<'a> for &'a [u8] {}

impl<'a> QueryParameters<'a> for &'a (dyn ToSql + Sync + Send) {}
impl<'a> QueryParameters<'a> for &'a dyn IntoSql<'a> {}

1st question:

  • I want to cast the &'a dyn QueryParameters<'a> to &'a (dyn ToSql + Sync) . Is this possible to cast from some trait to another?

2nd question:

  • The .bind() method of the tiberius client, only accept values that impl IntoSql<'a> . But I need to mix in my collection different values that already implements IntoSql<'a , but they have different type. I would like to know how to... cast??? those values of type &'a dyn QueryParameters<'a> to the values accepted by the function.

Are those things possible?

NOTE : The launch method from both modules are just a wrapper over the method calls provided above, but they accept as parameter params: &'a[&'a dyn QueryParameters<'a>]

Edit:

pub async fn launch<'a, T>(
        db_conn: DatabaseConnection,
        stmt: String,
        params: &'a [&'a dyn QueryParameters<'a>],
    ) -> Result<DatabaseResult<T>, Box<(dyn std::error::Error + Send + Sync + 'static)>> 
        where 
            T: Debug
    {
        let mut sql_server_query = Query::new(stmt);
        params.into_iter().for_each( |param| sql_server_query.bind( param ));

        let client: &mut Client<TcpStream> = &mut db_conn.sqlserver_connection
            .expect("Error querying the SqlServer database") // TODO Better msg
            .client;

        let _results: Vec<Row> = sql_server_query.query(client).await?
            .into_results().await?
            .into_iter()
            .flatten()
            .collect::<Vec<_>>();

        Ok(DatabaseResult::new(vec![]))
    }

that's the more conflictive part for me. .bind(impl IntoSql<'a> + 'a), so I should call this method for every parameter that I want to bind. I would like to cast ' &dyn QueryParameters<'a> .bind(impl IntoSql<'a> + 'a), so I should call this method for every parameter that I want to bind. I would like to cast ' &dyn QueryParameters<'a> to impl... , but I don't know if that's is even possible.

But, if I change the method signature to:

pub async fn launch<'a, T>(
        db_conn: DatabaseConnection,
        stmt: String,
        params: &'a [impl IntoSql<'a> + 'a],
    ) -> Result<DatabaseResult<T>, Box<(dyn std::error::Error + Send + Sync + 'static)>> 

I just only can accept values of the same type. Imagine a insert query, for example. I need to be flexible to accept both i32, i64, &str... depending on the column type. So this isn't valid for my case.

Edit 2

I've found a way to solve the postgres side of the issue.

trait AsAny {
    fn as_any(&self) -> &dyn std::any::Any;
}
impl AsAny for i32 {
    fn as_any(&self) -> &dyn std::any::Any {
        self
    }
}

pub trait QueryParameters<'a> {
    fn as_postgres_param(&self) -> &(dyn ToSql + Sync + 'a);
}

impl<'a> QueryParameters<'a> for i32 {
    fn as_postgres_param(&self) -> &(dyn ToSql + Sync + 'a) {
        let a: Box<&dyn AsAny> = Box::new(self);
        match a.as_any().downcast_ref::<i32>() {
            Some(b) => b,
            None => panic!("Bad conversion of parameters"),
        }
    }
}

I don't know if it's elegant, or harms performance (sure it does), but I can write now:

let mut m_params: Vec<&(dyn ToSql + Sync)> = Vec::new();
for param in params {
    m_params.push(param.as_postgres_param());
}

let query_result = client.query(&stmt, m_params.as_slice()).await;

But I can't figure out still how to work with the impl IntoSql<'a> + 'a of tiberius

Essentially, you need a &dyn QueryParameter to work as both a &dyn ToSql and an impl IntoSql , right? Lets start from scratch:

trait QueryParameter {}

The &dyn ToSql part is easy since you can use the trick shown in this answer . You need your QueryParameter trait to have an associated function to convert from &self to &dyn Sql . Like so:

trait QueryParameter {
    fn as_to_sql(&self) -> &dyn ToSql;

The impl IntoSql is trickier since consuming trait objects is a dicey affair. However, to implement the trait, we only need to construct a ColumnData . And we'll see in a second that its just that simple:

trait QueryParameter {
    fn as_column_data(&self) -> ColumnData<'_>;

because we can next implement IntoSql for &dyn QueryParameter like I mentioned in your other question :

impl<'a> IntoSql<'a> for &'a dyn QueryParameter {
    fn into_sql(self) -> ColumnData<'a> {
        self.as_column_data()
    }
}

And besides implementation for QueryParameter itself, that's it! We need to sprinkle in some Sync since ToSql and IntoSql require them, but this is a (mostly) working example:

use tiberius::{ColumnData, IntoSql, Query};
use tokio_postgres::types::ToSql;

trait QueryParameter: Sync {
    fn as_to_sql(&self) -> &(dyn ToSql + Sync);
    fn as_column_data(&self) -> ColumnData<'_>;
}

impl QueryParameter for i32 {
    fn as_to_sql(&self) -> &(dyn ToSql + Sync) { self }
    fn as_column_data(&self) -> ColumnData<'_> { ColumnData::I32(Some(*self)) }
}

impl QueryParameter for i64 {
    fn as_to_sql(&self) -> &(dyn ToSql + Sync) { self }
    fn as_column_data(&self) -> ColumnData<'_> { ColumnData::I64(Some(*self)) }
}

impl QueryParameter for &'_ str {
    fn as_to_sql(&self) -> &(dyn ToSql + Sync) { self }
    fn as_column_data(&self) -> ColumnData<'_> { ColumnData::String(Some((*self).into())) }
}

impl QueryParameter for String {
    fn as_to_sql(&self) -> &(dyn ToSql + Sync) { self }
    fn as_column_data(&self) -> ColumnData<'_> { ColumnData::String(Some(self.into())) }
}

impl<'a> IntoSql<'a> for &'a dyn QueryParameter {
    fn into_sql(self) -> ColumnData<'a> {
        self.as_column_data()
    }
}

async fn via_tiberius(stmt: &str, params: &[&dyn QueryParameter]) {
    let mut client: tiberius::Client<_> = todo!();
    let mut query = Query::new(stmt);
    for &param in params {
        query.bind(param)
    }
    let _ = query.execute(&mut client).await;
}

async fn via_tokio_postgres(stmt: &str, params: &[&dyn QueryParameter]) {
    let client: tokio_postgres::Client = todo!();
    let params: Vec<_> = params.iter().map(|p| p.as_to_sql()).collect();
    let _ = client.query(stmt, &params).await;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM