#![allow(deprecated)]
use crate::database::stats::DatabaseStats;
use crate::error::{Result as ThingsResult, ThingsError};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::SqlitePool;
use std::time::Duration;
use tracing::debug;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DatabasePoolConfig {
pub max_connections: u32,
pub min_connections: u32,
pub connect_timeout: Duration,
pub idle_timeout: Duration,
pub max_lifetime: Duration,
pub test_before_acquire: bool,
pub sqlite_optimizations: SqliteOptimizations,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SqliteOptimizations {
pub enable_wal_mode: bool,
pub synchronous_mode: String,
pub cache_size: i32,
pub enable_foreign_keys: bool,
pub journal_mode: String,
pub temp_store: String,
pub mmap_size: i64,
pub enable_query_planner: bool,
}
impl Default for DatabasePoolConfig {
fn default() -> Self {
Self {
max_connections: 10,
min_connections: 1,
connect_timeout: Duration::from_secs(30),
idle_timeout: Duration::from_secs(600), max_lifetime: Duration::from_secs(1800), test_before_acquire: true,
sqlite_optimizations: SqliteOptimizations::default(),
}
}
}
impl Default for SqliteOptimizations {
fn default() -> Self {
Self {
enable_wal_mode: true,
synchronous_mode: "NORMAL".to_string(),
cache_size: -20000, enable_foreign_keys: true,
journal_mode: "WAL".to_string(),
temp_store: "MEMORY".to_string(),
mmap_size: 268_435_456, enable_query_planner: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PoolHealthStatus {
pub is_healthy: bool,
pub pool_size: u32,
pub active_connections: u32,
pub idle_connections: u32,
pub max_connections: u32,
pub min_connections: u32,
pub connection_timeout: Duration,
pub idle_timeout: Option<Duration>,
pub max_lifetime: Option<Duration>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PoolMetrics {
pub pool_size: u32,
pub active_connections: u32,
pub idle_connections: u32,
pub max_connections: u32,
pub min_connections: u32,
pub utilization_percentage: f64,
pub is_healthy: bool,
pub response_time_ms: u64,
pub connection_timeout: Duration,
pub idle_timeout: Option<Duration>,
pub max_lifetime: Option<Duration>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComprehensiveHealthStatus {
pub overall_healthy: bool,
pub pool_health: PoolHealthStatus,
pub pool_metrics: PoolMetrics,
pub database_stats: DatabaseStats,
pub timestamp: DateTime<Utc>,
}
pub(crate) async fn apply_sqlite_optimizations(
pool: &SqlitePool,
optimizations: &SqliteOptimizations,
) -> ThingsResult<()> {
sqlx::query(&format!(
"PRAGMA journal_mode = {}",
optimizations.journal_mode
))
.execute(pool)
.await
.map_err(|e| ThingsError::unknown(format!("Failed to set journal mode: {e}")))?;
sqlx::query(&format!(
"PRAGMA synchronous = {}",
optimizations.synchronous_mode
))
.execute(pool)
.await
.map_err(|e| ThingsError::unknown(format!("Failed to set synchronous mode: {e}")))?;
sqlx::query(&format!("PRAGMA cache_size = {}", optimizations.cache_size))
.execute(pool)
.await
.map_err(|e| ThingsError::unknown(format!("Failed to set cache size: {e}")))?;
let fk_setting = if optimizations.enable_foreign_keys {
"ON"
} else {
"OFF"
};
sqlx::query(&format!("PRAGMA foreign_keys = {fk_setting}"))
.execute(pool)
.await
.map_err(|e| ThingsError::unknown(format!("Failed to set foreign keys: {e}")))?;
sqlx::query(&format!("PRAGMA temp_store = {}", optimizations.temp_store))
.execute(pool)
.await
.map_err(|e| ThingsError::unknown(format!("Failed to set temp store: {e}")))?;
sqlx::query(&format!("PRAGMA mmap_size = {}", optimizations.mmap_size))
.execute(pool)
.await
.map_err(|e| ThingsError::unknown(format!("Failed to set mmap size: {e}")))?;
if optimizations.enable_query_planner {
sqlx::query("PRAGMA optimize")
.execute(pool)
.await
.map_err(|e| ThingsError::unknown(format!("Failed to optimize database: {e}")))?;
}
debug!(
"Applied SQLite optimizations: WAL={}, sync={}, cache={}KB, fk={}, temp={}, mmap={}MB",
optimizations.enable_wal_mode,
optimizations.synchronous_mode,
optimizations.cache_size.abs() / 1024,
optimizations.enable_foreign_keys,
optimizations.temp_store,
optimizations.mmap_size / 1024 / 1024
);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_database_pool_config_default() {
let config = DatabasePoolConfig::default();
assert_eq!(config.max_connections, 10);
assert_eq!(config.min_connections, 1);
assert_eq!(config.connect_timeout, Duration::from_secs(30));
assert_eq!(config.idle_timeout, Duration::from_secs(600));
assert_eq!(config.max_lifetime, Duration::from_secs(1800));
assert!(config.test_before_acquire);
}
#[test]
fn test_sqlite_optimizations_default() {
let opts = SqliteOptimizations::default();
assert!(opts.enable_wal_mode);
assert_eq!(opts.cache_size, -20000);
assert_eq!(opts.synchronous_mode, "NORMAL".to_string());
assert_eq!(opts.temp_store, "MEMORY".to_string());
assert_eq!(opts.journal_mode, "WAL".to_string());
assert_eq!(opts.mmap_size, 268_435_456);
assert!(opts.enable_foreign_keys);
assert!(opts.enable_query_planner);
}
#[test]
fn test_pool_health_status_creation() {
let status = PoolHealthStatus {
is_healthy: true,
pool_size: 8,
active_connections: 5,
idle_connections: 3,
max_connections: 10,
min_connections: 1,
connection_timeout: Duration::from_secs(30),
idle_timeout: Some(Duration::from_secs(600)),
max_lifetime: Some(Duration::from_secs(1800)),
};
assert!(status.is_healthy);
assert_eq!(status.active_connections, 5);
assert_eq!(status.idle_connections, 3);
assert_eq!(status.pool_size, 8);
}
#[test]
fn test_pool_metrics_creation() {
let metrics = PoolMetrics {
pool_size: 8,
active_connections: 5,
idle_connections: 3,
max_connections: 10,
min_connections: 1,
utilization_percentage: 80.0,
is_healthy: true,
response_time_ms: 50,
connection_timeout: Duration::from_secs(30),
idle_timeout: Some(Duration::from_secs(600)),
max_lifetime: Some(Duration::from_secs(1800)),
};
assert!(metrics.is_healthy);
assert_eq!(metrics.pool_size, 8);
assert_eq!(metrics.active_connections, 5);
assert_eq!(metrics.idle_connections, 3);
assert!((metrics.utilization_percentage - 80.0).abs() < f64::EPSILON);
assert_eq!(metrics.response_time_ms, 50);
}
#[test]
fn test_comprehensive_health_status_creation() {
let pool_health = PoolHealthStatus {
is_healthy: true,
pool_size: 8,
active_connections: 5,
idle_connections: 3,
max_connections: 10,
min_connections: 1,
connection_timeout: Duration::from_secs(30),
idle_timeout: Some(Duration::from_secs(600)),
max_lifetime: Some(Duration::from_secs(1800)),
};
let pool_metrics = PoolMetrics {
pool_size: 8,
active_connections: 5,
idle_connections: 3,
max_connections: 10,
min_connections: 1,
utilization_percentage: 80.0,
is_healthy: true,
response_time_ms: 50,
connection_timeout: Duration::from_secs(30),
idle_timeout: Some(Duration::from_secs(600)),
max_lifetime: Some(Duration::from_secs(1800)),
};
let db_stats = DatabaseStats {
task_count: 50,
project_count: 10,
area_count: 5,
};
let health_status = ComprehensiveHealthStatus {
overall_healthy: true,
pool_health,
pool_metrics,
database_stats: db_stats,
timestamp: Utc::now(),
};
assert!(health_status.overall_healthy);
assert_eq!(health_status.database_stats.total_items(), 65);
}
}