use crate::sql::db_connection_pool::dbconnection::odbcconn::ODBCConnection;
use crate::sql::db_connection_pool::dbconnection::odbcconn::{ODBCDbConnection, ODBCParameter};
use crate::sql::db_connection_pool::{DbConnectionPool, JoinPushDown};
use async_trait::async_trait;
use odbc_api::{sys::AttrConnectionPooling, Connection, ConnectionOptions, Environment};
use secrecy::{ExposeSecret, SecretBox, SecretString};
use sha2::{Digest, Sha256};
use snafu::prelude::*;
use std::{
collections::HashMap,
sync::{Arc, LazyLock},
};
static ENV: LazyLock<Environment> = LazyLock::new(|| unsafe {
if let Err(e) = Environment::set_connection_pooling(AttrConnectionPooling::DriverAware) {
tracing::error!("Failed to set ODBC connection pooling: {e}");
};
match Environment::new() {
Ok(env) => env,
Err(e) => {
panic!("Failed to create ODBC environment: {e}");
}
}
});
#[derive(Debug, Snafu)]
pub enum Error {
#[snafu(display("Missing ODBC connection string parameter: odbc_connection_string"))]
MissingConnectionString {},
#[snafu(display("Invalid parameter: {parameter_name}"))]
InvalidParameterError { parameter_name: String },
}
pub struct ODBCPool {
pool: &'static Environment,
params: Arc<HashMap<String, SecretString>>,
connection_string: String,
connection_id: String,
}
fn hash_string(val: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(val);
hasher.finalize().iter().fold(String::new(), |mut hash, b| {
hash.push_str(&format!("{b:02x}"));
hash
})
}
impl ODBCPool {
pub fn new(params: HashMap<String, SecretString>) -> Result<Self, Error> {
let connection_string = params
.get("connection_string")
.map(SecretBox::expose_secret)
.map(ToString::to_string)
.context(MissingConnectionStringSnafu)?;
let connection_id = hash_string(&connection_string);
Ok(Self {
params: params.into(),
connection_string,
connection_id,
pool: &ENV,
})
}
#[must_use]
pub fn odbc_environment(&self) -> &'static Environment {
self.pool
}
}
#[async_trait]
impl<'a> DbConnectionPool<Connection<'a>, ODBCParameter> for ODBCPool
where
'a: 'static,
{
async fn connect(
&self,
) -> Result<Box<ODBCDbConnection<'a>>, Box<dyn std::error::Error + Send + Sync>> {
let cxn = self.pool.connect_with_connection_string(
&self.connection_string,
ConnectionOptions::default(),
)?;
let odbc_cxn = ODBCConnection {
conn: Arc::new(cxn.into()),
params: Arc::clone(&self.params),
};
Ok(Box::new(odbc_cxn))
}
fn join_push_down(&self) -> JoinPushDown {
JoinPushDown::AllowedFor(self.connection_id.clone())
}
}