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 thetiberius
client, only accept values that implIntoSql<'a>
. But I need to mix in my collection different values that already implementsIntoSql<'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>
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