use super::errors::DataSourceError;
use super::traits::*;
use super::types::*;
use async_trait::async_trait;
use polars::prelude::*;
use std::sync::{Arc, Mutex};
use tokio::sync::RwLock;
#[cfg(feature = "database")]
use clickhouse::Client as ClickHouseClient;
#[cfg(feature = "database")]
use sqlx::{PgPool, Postgres, Row};
pub struct PostgresAdapter {
config: ConnectionConfig,
#[cfg(feature = "database")]
pool: Option<Arc<PgPool>>,
#[cfg(not(feature = "database"))]
pool: Option<Arc<RwLock<MockConnectionPool>>>,
}
impl PostgresAdapter {
pub fn new(config: ConnectionConfig) -> Self {
Self { config, pool: None }
}
pub async fn initialize(&mut self) -> Result<(), DataSourceError> {
#[cfg(feature = "database")]
{
let pool = PgPool::connect(&self.config.connection_string)
.await
.map_err(|e| DataSourceError::ConnectionFailed(e.to_string()))?;
self.pool = Some(Arc::new(pool));
}
#[cfg(not(feature = "database"))]
{
let pool = MockConnectionPool::new(&self.config).await?;
self.pool = Some(Arc::new(RwLock::new(pool)));
}
Ok(())
}
}
#[async_trait]
impl DataSource for PostgresAdapter {
fn name(&self) -> &str {
"PostgreSQL"
}
fn source_type(&self) -> &str {
"postgresql"
}
async fn connect(
&self,
_config: &ConnectionConfig,
) -> Result<Box<dyn Connection>, DataSourceError> {
let pool = self
.pool
.as_ref()
.ok_or_else(|| DataSourceError::ConnectionFailed("Pool not initialized".to_string()))?;
let conn = PostgresConnection::new(pool.clone()).await?;
Ok(Box::new(conn))
}
async fn test_connection(&self, _config: &ConnectionConfig) -> Result<bool, DataSourceError> {
match self.connect(_config).await {
Ok(_) => Ok(true),
Err(_) => Ok(false),
}
}
fn supported_features(&self) -> Vec<String> {
vec![
"transactions".to_string(),
"prepared_statements".to_string(),
"schema_introspection".to_string(),
"json_support".to_string(),
"array_support".to_string(),
]
}
fn connection_limits(&self) -> ConnectionLimits {
ConnectionLimits::new()
.with_max_connections(100)
.with_max_query_time(std::time::Duration::from_secs(300))
.with_max_result_size(100_000_000)
.with_max_parameters(1000)
}
async fn get_stats(&self) -> Result<DataSourceStats, DataSourceError> {
Ok(DataSourceStats::new())
}
}
pub struct PostgresConnection {
#[cfg(feature = "database")]
pool: Arc<PgPool>,
#[cfg(not(feature = "database"))]
pool: Arc<RwLock<MockConnectionPool>>,
stats: Arc<Mutex<ConnectionStats>>,
}
impl PostgresConnection {
pub async fn new(
#[cfg(feature = "database")] pool: Arc<PgPool>,
#[cfg(not(feature = "database"))] pool: Arc<RwLock<MockConnectionPool>>,
) -> Result<Self, DataSourceError> {
Ok(Self {
pool,
stats: Arc::new(Mutex::new(ConnectionStats::new(
uuid::Uuid::new_v4().to_string(),
))),
})
}
}
#[async_trait]
impl Connection for PostgresConnection {
async fn execute_query(&self, _query: &str) -> Result<DataFrame, DataSourceError> {
let start_time = std::time::Instant::now();
#[cfg(feature = "database")]
{
let rows = sqlx::query(_query)
.fetch_all(&*self.pool)
.await
.map_err(|e| DataSourceError::QueryFailed(e.to_string()))?;
let mut data: Vec<Vec<String>> = Vec::new();
let mut columns: Vec<String> = Vec::new();
if let Some(first_row) = rows.first() {
for (i, column) in first_row.columns().iter().enumerate() {
columns.push(column.name().to_string());
}
for row in &rows {
let mut row_data = Vec::new();
for i in 0..columns.len() {
let value = row
.try_get::<String, _>(i)
.unwrap_or_else(|_| "NULL".to_string());
row_data.push(value);
}
data.push(row_data);
}
}
let execution_time = start_time.elapsed();
self.stats
.lock()
.unwrap()
.record_successful_query(execution_time);
let mut df_builder = DataFrame::new(Vec::new()).unwrap();
for (i, column) in columns.iter().enumerate() {
let series: Series = data
.iter()
.map(|row| row[i].clone())
.collect::<Vec<String>>()
.into_iter()
.collect();
df_builder = df_builder.with_column(series.with_name(column)).unwrap();
}
Ok(df_builder)
}
#[cfg(not(feature = "database"))]
{
let execution_time = start_time.elapsed();
self.stats
.lock()
.unwrap()
.record_successful_query(execution_time);
Ok(DataFrame::new(Vec::new()).unwrap())
}
}
async fn execute_query_with_params(
&self,
query: &str,
_params: &[&dyn ToSql],
) -> Result<DataFrame, DataSourceError> {
self.execute_query(query).await
}
async fn execute_statement(&self, _statement: &str) -> Result<u64, DataSourceError> {
let start_time = std::time::Instant::now();
#[cfg(feature = "database")]
{
let result = sqlx::query(_statement)
.execute(&*self.pool)
.await
.map_err(|e| DataSourceError::QueryFailed(e.to_string()))?;
let execution_time = start_time.elapsed();
self.stats
.lock()
.unwrap()
.record_successful_query(execution_time);
Ok(result.rows_affected())
}
#[cfg(not(feature = "database"))]
{
let execution_time = start_time.elapsed();
self.stats
.lock()
.unwrap()
.record_successful_query(execution_time);
Ok(1) }
}
async fn execute_statement_with_params(
&self,
statement: &str,
_params: &[&dyn ToSql],
) -> Result<u64, DataSourceError> {
self.execute_statement(statement).await
}
async fn begin_transaction(&self) -> Result<Box<dyn Transaction>, DataSourceError> {
Ok(Box::new(MockTransaction::new()))
}
async fn get_schema(&self) -> Result<super::types::Schema, DataSourceError> {
let mut schema = super::types::Schema::new();
let mut table = TableInfo::new("users".to_string());
table.add_column(ColumnInfo::new("id".to_string(), "INTEGER".to_string()));
table.add_column(ColumnInfo::new("name".to_string(), "VARCHAR".to_string()));
table.add_primary_key("id".to_string());
schema.add_table(table);
Ok(schema)
}
async fn get_table_info(&self, table_name: &str) -> Result<TableInfo, DataSourceError> {
let mut table = TableInfo::new(table_name.to_string());
table.add_column(ColumnInfo::new("id".to_string(), "INTEGER".to_string()));
table.add_column(ColumnInfo::new("name".to_string(), "VARCHAR".to_string()));
Ok(table)
}
async fn list_tables(&self) -> Result<Vec<String>, DataSourceError> {
Ok(vec![
"users".to_string(),
"orders".to_string(),
"products".to_string(),
])
}
async fn list_views(&self) -> Result<Vec<String>, DataSourceError> {
Ok(vec!["user_summary".to_string()])
}
async fn table_exists(&self, table_name: &str) -> Result<bool, DataSourceError> {
let tables = self.list_tables().await?;
Ok(tables.contains(&table_name.to_string()))
}
async fn get_stats(&self) -> Result<ConnectionStats, DataSourceError> {
Ok(self.stats.lock().unwrap().clone())
}
async fn close(&self) -> Result<(), DataSourceError> {
Ok(())
}
}
pub struct ClickHouseAdapter {
config: ConnectionConfig,
#[cfg(feature = "database")]
client: Option<ClickHouseClient>,
#[cfg(not(feature = "database"))]
client: Option<Arc<RwLock<MockClickHouseClient>>>,
}
impl ClickHouseAdapter {
pub fn new(config: ConnectionConfig) -> Self {
Self {
config,
client: None,
}
}
pub async fn initialize(&mut self) -> Result<(), DataSourceError> {
#[cfg(feature = "database")]
{
let client = ClickHouseClient::default()
.with_url(&self.config.connection_string)
.map_err(|e| DataSourceError::ConnectionFailed(e.to_string()))?;
self.client = Some(client);
}
#[cfg(not(feature = "database"))]
{
let client = MockClickHouseClient::new(&self.config).await?;
self.client = Some(Arc::new(RwLock::new(client)));
}
Ok(())
}
}
#[async_trait]
impl DataSource for ClickHouseAdapter {
fn name(&self) -> &str {
"ClickHouse"
}
fn source_type(&self) -> &str {
"clickhouse"
}
async fn connect(
&self,
_config: &ConnectionConfig,
) -> Result<Box<dyn Connection>, DataSourceError> {
let client = self.client.as_ref().ok_or_else(|| {
DataSourceError::ConnectionFailed("Client not initialized".to_string())
})?;
let conn = ClickHouseConnection::new(client.clone()).await?;
Ok(Box::new(conn))
}
async fn test_connection(&self, _config: &ConnectionConfig) -> Result<bool, DataSourceError> {
match self.connect(_config).await {
Ok(_) => Ok(true),
Err(_) => Ok(false),
}
}
fn supported_features(&self) -> Vec<String> {
vec![
"columnar_storage".to_string(),
"compression".to_string(),
"distributed_queries".to_string(),
"materialized_views".to_string(),
"replication".to_string(),
]
}
fn connection_limits(&self) -> ConnectionLimits {
ConnectionLimits::new()
.with_max_connections(50)
.with_max_query_time(std::time::Duration::from_secs(600))
.with_max_result_size(1_000_000_000) .with_max_parameters(10000)
}
async fn get_stats(&self) -> Result<DataSourceStats, DataSourceError> {
Ok(DataSourceStats::new())
}
}
pub struct ClickHouseConnection {
#[cfg(feature = "database")]
client: ClickHouseClient,
#[cfg(not(feature = "database"))]
client: Arc<RwLock<MockClickHouseClient>>,
stats: Arc<Mutex<ConnectionStats>>,
}
impl ClickHouseConnection {
pub async fn new(
#[cfg(feature = "database")] client: ClickHouseClient,
#[cfg(not(feature = "database"))] client: Arc<RwLock<MockClickHouseClient>>,
) -> Result<Self, DataSourceError> {
Ok(Self {
client,
stats: Arc::new(Mutex::new(ConnectionStats::new(
uuid::Uuid::new_v4().to_string(),
))),
})
}
}
#[async_trait]
impl Connection for ClickHouseConnection {
async fn execute_query(&self, _query: &str) -> Result<DataFrame, DataSourceError> {
let start_time = std::time::Instant::now();
let execution_time = start_time.elapsed();
self.stats
.lock()
.unwrap()
.record_successful_query(execution_time);
Ok(DataFrame::new(Vec::new()).unwrap())
}
async fn execute_query_with_params(
&self,
query: &str,
_params: &[&dyn ToSql],
) -> Result<DataFrame, DataSourceError> {
self.execute_query(query).await
}
async fn execute_statement(&self, _statement: &str) -> Result<u64, DataSourceError> {
let start_time = std::time::Instant::now();
let execution_time = start_time.elapsed();
self.stats
.lock()
.unwrap()
.record_successful_query(execution_time);
Ok(1) }
async fn execute_statement_with_params(
&self,
statement: &str,
_params: &[&dyn ToSql],
) -> Result<u64, DataSourceError> {
self.execute_statement(statement).await
}
async fn begin_transaction(&self) -> Result<Box<dyn Transaction>, DataSourceError> {
Ok(Box::new(MockTransaction::new()))
}
async fn get_schema(&self) -> Result<super::types::Schema, DataSourceError> {
let mut schema = super::types::Schema::new();
let mut table = TableInfo::new("events".to_string());
table.add_column(ColumnInfo::new(
"timestamp".to_string(),
"DateTime".to_string(),
));
table.add_column(ColumnInfo::new("user_id".to_string(), "UInt64".to_string()));
table.add_column(ColumnInfo::new(
"event_type".to_string(),
"String".to_string(),
));
schema.add_table(table);
Ok(schema)
}
async fn get_table_info(&self, table_name: &str) -> Result<TableInfo, DataSourceError> {
let mut table = TableInfo::new(table_name.to_string());
table.add_column(ColumnInfo::new("id".to_string(), "UInt64".to_string()));
table.add_column(ColumnInfo::new("data".to_string(), "String".to_string()));
Ok(table)
}
async fn list_tables(&self) -> Result<Vec<String>, DataSourceError> {
Ok(vec![
"events".to_string(),
"users".to_string(),
"sessions".to_string(),
])
}
async fn list_views(&self) -> Result<Vec<String>, DataSourceError> {
Ok(vec!["daily_events".to_string()])
}
async fn table_exists(&self, table_name: &str) -> Result<bool, DataSourceError> {
let tables = self.list_tables().await?;
Ok(tables.contains(&table_name.to_string()))
}
async fn get_stats(&self) -> Result<ConnectionStats, DataSourceError> {
Ok(self.stats.lock().unwrap().clone())
}
async fn close(&self) -> Result<(), DataSourceError> {
Ok(())
}
}
#[cfg(not(feature = "database"))]
pub struct MockConnectionPool {
config: ConnectionConfig,
}
#[cfg(not(feature = "database"))]
impl MockConnectionPool {
pub async fn new(config: &ConnectionConfig) -> Result<Self, DataSourceError> {
Ok(Self {
config: config.clone(),
})
}
}
#[cfg(not(feature = "database"))]
pub struct MockClickHouseClient {
config: ConnectionConfig,
}
#[cfg(not(feature = "database"))]
impl MockClickHouseClient {
pub async fn new(config: &ConnectionConfig) -> Result<Self, DataSourceError> {
Ok(Self {
config: config.clone(),
})
}
}
pub struct MockTransaction {
active: bool,
}
impl MockTransaction {
pub fn new() -> Self {
Self { active: true }
}
}
#[async_trait]
impl Transaction for MockTransaction {
async fn execute_query(&self, _query: &str) -> Result<DataFrame, DataSourceError> {
Ok(DataFrame::new(Vec::new()).unwrap())
}
async fn execute_query_with_params(
&self,
query: &str,
_params: &[&dyn ToSql],
) -> Result<DataFrame, DataSourceError> {
self.execute_query(query).await
}
async fn execute_statement(&self, _statement: &str) -> Result<u64, DataSourceError> {
Ok(1)
}
async fn execute_statement_with_params(
&self,
statement: &str,
_params: &[&dyn ToSql],
) -> Result<u64, DataSourceError> {
self.execute_statement(statement).await
}
async fn commit(&self) -> Result<(), DataSourceError> {
Ok(())
}
async fn rollback(&self) -> Result<(), DataSourceError> {
Ok(())
}
fn is_active(&self) -> bool {
self.active
}
}