簡體   English   中英

允許 &[impl Trait] 表現得像 &[&dyn Trait]

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

提前原諒不好的標題。 我會盡量在描述中清楚。

我正在制作一個需要使用tokio_postresqltiberius的應用程序。 我需要為兩個連接器提供查詢參數。 這是他們的簽名。

postgresql

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

提比略

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

正如您可能觀察到的, tokio_postres承認對數組的引用是一個 trait 對象,這非常方便。 但是,我的瓶頸在於 tiberius 的參數。

這是我的代碼:

#[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
            }
        }
    }
}

其中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> {}

第一個問題:

  • 我想將&'a dyn QueryParameters<'a>轉換為&'a (dyn ToSql + Sync) 這是否可以從某種特征轉換為另一種特征?

第二個問題:

  • tiberius客戶端的.bind()方法,只接受 impl IntoSql<'a>值。 但是我需要在我的集合中混合已經實現IntoSql<'a不同值,但它們具有不同的類型。 我想知道如何...投??? &'a dyn QueryParameters<'a>類型的值轉換為 function 接受的值。

這些事情可能嗎?

注意:兩個模塊的launch方法只是對上面提供的方法調用的包裝,但它們接受作為參數params: &'a[&'a dyn QueryParameters<'a>]

編輯:

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![]))
    }

這對我來說是更矛盾的部分。 .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>轉換為impl... ,但我不知道這是否可能。

但是,如果我將方法簽名更改為:

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)>> 

我只能接受相同類型的值。 例如,想象一個插入查詢。 我需要靈活地接受 i32、i64 和 &str...,具體取決於列類型。 所以這對我的情況無效。

編輯 2

我找到了解決問題的postgres方面的方法。

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"),
        }
    }
}

我不知道它是否優雅,或者會損害性能(確實如此),但我現在可以寫:

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;

但我仍然不知道如何使用impl IntoSql<'a> + 'a of tiberius

本質上,您需要一個&dyn QueryParameter來作為&dyn ToSqlimpl IntoSql ,對嗎? 讓我們從頭開始:

trait QueryParameter {}

&dyn ToSql部分很簡單,因為您可以使用此答案中顯示的技巧。 您需要QueryParameter特征具有關聯的 function 才能從&self轉換為&dyn Sql 像這樣:

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

impl IntoSql比較棘手,因為使用 trait 對象是一件冒險的事情。 但是,要實現 trait,我們只需要構造一個ColumnData 我們馬上就會看到它就是這么簡單:

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

因為我們接下來可以為&dyn QueryParameter實現IntoSql ,就像我在您的其他問題中提到的那樣:

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

除了QueryParameter本身的實現之外,就是這樣! 我們需要添加一些Sync ,因為ToSqlIntoSql需要它們,但這是一個(大部分)工作示例:

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;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM