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:
&'a dyn QueryParameters<'a>
to &'a (dyn ToSql + Sync)
. Is this possible to cast from some trait to another?2nd question:
.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 ¶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;
}
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.