pub mod database;
pub mod errors;
pub mod traits;
pub mod types;
pub use database::*;
pub use errors::*;
pub use traits::*;
pub use types::*;
use polars::prelude::*;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
pub struct DataSourceManager {
adapters: HashMap<String, Box<dyn DataSource>>,
connections: HashMap<String, Arc<RwLock<Box<dyn Connection>>>>,
stats: DataSourceStats,
}
impl DataSourceManager {
pub fn new() -> Self {
Self {
adapters: HashMap::new(),
connections: HashMap::new(),
stats: DataSourceStats::new(),
}
}
pub fn add_adapter(&mut self, name: String, adapter: Box<dyn DataSource>) {
self.adapters.insert(name, adapter);
}
pub fn get_adapter(&self, name: &str) -> Option<&Box<dyn DataSource>> {
self.adapters.get(name)
}
pub async fn connect(
&mut self,
adapter_name: &str,
config: &ConnectionConfig,
) -> Result<String, DataSourceError> {
let adapter = self.adapters.get(adapter_name).ok_or_else(|| {
DataSourceError::ConnectionFailed(format!("Adapter '{}' not found", adapter_name))
})?;
let connection = adapter.connect(config).await?;
let connection_id = uuid::Uuid::new_v4().to_string();
self.connections
.insert(connection_id.clone(), Arc::new(RwLock::new(connection)));
self.stats.total_connections += 1;
self.stats.active_connections += 1;
self.stats.update_activity();
Ok(connection_id)
}
pub async fn execute_query(
&mut self,
connection_id: &str,
query: &str,
) -> Result<DataFrame, DataSourceError> {
let connection = self.connections.get(connection_id).ok_or_else(|| {
DataSourceError::ConnectionFailed(format!("Connection '{}' not found", connection_id))
})?;
let start_time = std::time::Instant::now();
let result = connection.read().await.execute_query(query).await;
let _execution_time = start_time.elapsed();
match result {
Ok(df) => {
self.stats.total_queries += 1;
self.stats.update_activity();
Ok(df)
}
Err(e) => {
self.stats.failed_queries += 1;
self.stats.update_activity();
Err(e)
}
}
}
pub async fn execute_query_with_params(
&mut self,
connection_id: &str,
query: &str,
params: &[&dyn ToSql],
) -> Result<DataFrame, DataSourceError> {
let connection = self.connections.get(connection_id).ok_or_else(|| {
DataSourceError::ConnectionFailed(format!("Connection '{}' not found", connection_id))
})?;
let start_time = std::time::Instant::now();
let result = connection
.read()
.await
.execute_query_with_params(query, params)
.await;
let _execution_time = start_time.elapsed();
match result {
Ok(df) => {
self.stats.total_queries += 1;
self.stats.update_activity();
Ok(df)
}
Err(e) => {
self.stats.failed_queries += 1;
self.stats.update_activity();
Err(e)
}
}
}
pub async fn get_schema(&self, connection_id: &str) -> Result<types::Schema, DataSourceError> {
let connection = self.connections.get(connection_id).ok_or_else(|| {
DataSourceError::ConnectionFailed(format!("Connection '{}' not found", connection_id))
})?;
connection.read().await.get_schema().await
}
pub async fn list_tables(&self, connection_id: &str) -> Result<Vec<String>, DataSourceError> {
let connection = self.connections.get(connection_id).ok_or_else(|| {
DataSourceError::ConnectionFailed(format!("Connection '{}' not found", connection_id))
})?;
connection.read().await.list_tables().await
}
pub async fn close_connection(&mut self, connection_id: &str) -> Result<(), DataSourceError> {
if let Some(connection) = self.connections.remove(connection_id) {
connection.read().await.close().await?;
self.stats.active_connections = self.stats.active_connections.saturating_sub(1);
self.stats.update_activity();
}
Ok(())
}
pub fn get_stats(&self) -> &DataSourceStats {
&self.stats
}
pub fn list_adapters(&self) -> Vec<String> {
self.adapters.keys().cloned().collect()
}
pub fn list_connections(&self) -> Vec<String> {
self.connections.keys().cloned().collect()
}
pub async fn test_connection(
&self,
adapter_name: &str,
config: &ConnectionConfig,
) -> Result<bool, DataSourceError> {
let adapter = self.adapters.get(adapter_name).ok_or_else(|| {
DataSourceError::ConnectionFailed(format!("Adapter '{}' not found", adapter_name))
})?;
adapter.test_connection(config).await
}
}
impl Default for DataSourceManager {
fn default() -> Self {
Self::new()
}
}
pub struct DataSourceFactory;
impl DataSourceFactory {
pub fn create_postgres_adapter(config: ConnectionConfig) -> PostgresAdapter {
PostgresAdapter::new(config)
}
pub fn create_clickhouse_adapter(config: ConnectionConfig) -> ClickHouseAdapter {
ClickHouseAdapter::new(config)
}
pub fn create_manager_with_defaults() -> DataSourceManager {
let manager = DataSourceManager::new();
manager
}
}
pub struct DataSourceConfigBuilder {
config: ConnectionConfig,
}
impl DataSourceConfigBuilder {
pub fn new(connection_string: String) -> Self {
Self {
config: ConnectionConfig::new(connection_string),
}
}
pub fn max_connections(mut self, max_connections: u32) -> Self {
self.config.max_connections = Some(max_connections);
self
}
pub fn connection_timeout(mut self, timeout: std::time::Duration) -> Self {
self.config.connection_timeout = Some(timeout);
self
}
pub fn query_timeout(mut self, timeout: std::time::Duration) -> Self {
self.config.query_timeout = Some(timeout);
self
}
pub fn ssl_mode(mut self, ssl_mode: String) -> Self {
self.config.ssl_mode = Some(ssl_mode);
self
}
pub fn credentials(mut self, username: String, password: String) -> Self {
self.config.credentials = Some(Credentials::new(username, password));
self
}
pub fn build(self) -> ConnectionConfig {
self.config
}
}
pub mod utils {
use super::*;
pub fn validate_connection_string(connection_string: &str) -> Result<(), DataSourceError> {
if connection_string.is_empty() {
return Err(DataSourceError::InvalidConfiguration(
"Connection string cannot be empty".to_string(),
));
}
if !connection_string.contains("://") {
return Err(DataSourceError::InvalidConfiguration(
"Connection string must contain protocol (://)".to_string(),
));
}
Ok(())
}
pub fn parse_connection_string(
connection_string: &str,
) -> Result<ConnectionComponents, DataSourceError> {
validate_connection_string(connection_string)?;
let parts: Vec<&str> = connection_string.split("://").collect();
if parts.len() != 2 {
return Err(DataSourceError::InvalidConfiguration(
"Invalid connection string format".to_string(),
));
}
let protocol = parts[0].to_string();
let rest = parts[1];
let mut components = ConnectionComponents {
protocol,
host: "localhost".to_string(),
port: None,
database: None,
username: None,
password: None,
params: HashMap::new(),
};
if let Some(at_pos) = rest.find('@') {
let auth_part = &rest[..at_pos];
let host_part = &rest[at_pos + 1..];
if let Some(colon_pos) = auth_part.find(':') {
components.username = Some(auth_part[..colon_pos].to_string());
components.password = Some(auth_part[colon_pos + 1..].to_string());
} else {
components.username = Some(auth_part.to_string());
}
if let Some(slash_pos) = host_part.find('/') {
let host_port = &host_part[..slash_pos];
components.database = Some(host_part[slash_pos + 1..].to_string());
if let Some(colon_pos) = host_port.find(':') {
components.host = host_port[..colon_pos].to_string();
if let Ok(port) = host_port[colon_pos + 1..].parse::<u16>() {
components.port = Some(port);
}
} else {
components.host = host_port.to_string();
}
} else {
components.host = host_part.to_string();
}
}
Ok(components)
}
#[derive(Debug, Clone)]
pub struct ConnectionComponents {
pub protocol: String,
pub host: String,
pub port: Option<u16>,
pub database: Option<String>,
pub username: Option<String>,
pub password: Option<String>,
pub params: HashMap<String, String>,
}
}