![](/img/trans.png)
[英]Converting an Option<impl Trait> to an Option<Box<dyn Trait>>
[英]Allowing an &[impl Trait] to behave like an &[&dyn Trait]
提前原諒不好的標題。 我會盡量在描述中清楚。
我正在制作一個需要使用tokio_postresql
和tiberius
的應用程序。 我需要為兩個連接器提供查詢參數。 這是他們的簽名。
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 ToSql
和impl 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
,因為ToSql
和IntoSql
需要它們,但這是一個(大部分)工作示例:
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 ¶m 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, ¶ms).await;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.