1#![allow(deprecated)]
3
4use crate::database::stats::DatabaseStats;
5use crate::error::{Result as ThingsResult, ThingsError};
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8use sqlx::SqlitePool;
9use std::time::Duration;
10use tracing::debug;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct DatabasePoolConfig {
15 pub max_connections: u32,
17 pub min_connections: u32,
19 pub connect_timeout: Duration,
21 pub idle_timeout: Duration,
23 pub max_lifetime: Duration,
25 pub test_before_acquire: bool,
27 pub sqlite_optimizations: SqliteOptimizations,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct SqliteOptimizations {
34 pub enable_wal_mode: bool,
36 pub synchronous_mode: String,
38 pub cache_size: i32,
40 pub enable_foreign_keys: bool,
42 pub journal_mode: String,
44 pub temp_store: String,
46 pub mmap_size: i64,
48 pub enable_query_planner: bool,
50}
51
52impl Default for DatabasePoolConfig {
53 fn default() -> Self {
54 Self {
55 max_connections: 10,
56 min_connections: 1,
57 connect_timeout: Duration::from_secs(30),
58 idle_timeout: Duration::from_secs(600), max_lifetime: Duration::from_secs(1800), test_before_acquire: true,
61 sqlite_optimizations: SqliteOptimizations::default(),
62 }
63 }
64}
65
66impl Default for SqliteOptimizations {
67 fn default() -> Self {
68 Self {
69 enable_wal_mode: true,
70 synchronous_mode: "NORMAL".to_string(),
71 cache_size: -20000, enable_foreign_keys: true,
73 journal_mode: "WAL".to_string(),
74 temp_store: "MEMORY".to_string(),
75 mmap_size: 268_435_456, enable_query_planner: true,
77 }
78 }
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct PoolHealthStatus {
84 pub is_healthy: bool,
85 pub pool_size: u32,
86 pub active_connections: u32,
87 pub idle_connections: u32,
88 pub max_connections: u32,
89 pub min_connections: u32,
90 pub connection_timeout: Duration,
91 pub idle_timeout: Option<Duration>,
92 pub max_lifetime: Option<Duration>,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct PoolMetrics {
98 pub pool_size: u32,
99 pub active_connections: u32,
100 pub idle_connections: u32,
101 pub max_connections: u32,
102 pub min_connections: u32,
103 pub utilization_percentage: f64,
104 pub is_healthy: bool,
105 pub response_time_ms: u64,
106 pub connection_timeout: Duration,
107 pub idle_timeout: Option<Duration>,
108 pub max_lifetime: Option<Duration>,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct ComprehensiveHealthStatus {
114 pub overall_healthy: bool,
115 pub pool_health: PoolHealthStatus,
116 pub pool_metrics: PoolMetrics,
117 pub database_stats: DatabaseStats,
118 pub timestamp: DateTime<Utc>,
119}
120
121pub(crate) async fn apply_sqlite_optimizations(
123 pool: &SqlitePool,
124 optimizations: &SqliteOptimizations,
125) -> ThingsResult<()> {
126 sqlx::query(&format!(
127 "PRAGMA journal_mode = {}",
128 optimizations.journal_mode
129 ))
130 .execute(pool)
131 .await
132 .map_err(|e| ThingsError::unknown(format!("Failed to set journal mode: {e}")))?;
133
134 sqlx::query(&format!(
135 "PRAGMA synchronous = {}",
136 optimizations.synchronous_mode
137 ))
138 .execute(pool)
139 .await
140 .map_err(|e| ThingsError::unknown(format!("Failed to set synchronous mode: {e}")))?;
141
142 sqlx::query(&format!("PRAGMA cache_size = {}", optimizations.cache_size))
143 .execute(pool)
144 .await
145 .map_err(|e| ThingsError::unknown(format!("Failed to set cache size: {e}")))?;
146
147 let fk_setting = if optimizations.enable_foreign_keys {
148 "ON"
149 } else {
150 "OFF"
151 };
152 sqlx::query(&format!("PRAGMA foreign_keys = {fk_setting}"))
153 .execute(pool)
154 .await
155 .map_err(|e| ThingsError::unknown(format!("Failed to set foreign keys: {e}")))?;
156
157 sqlx::query(&format!("PRAGMA temp_store = {}", optimizations.temp_store))
158 .execute(pool)
159 .await
160 .map_err(|e| ThingsError::unknown(format!("Failed to set temp store: {e}")))?;
161
162 sqlx::query(&format!("PRAGMA mmap_size = {}", optimizations.mmap_size))
163 .execute(pool)
164 .await
165 .map_err(|e| ThingsError::unknown(format!("Failed to set mmap size: {e}")))?;
166
167 if optimizations.enable_query_planner {
168 sqlx::query("PRAGMA optimize")
169 .execute(pool)
170 .await
171 .map_err(|e| ThingsError::unknown(format!("Failed to optimize database: {e}")))?;
172 }
173
174 debug!(
175 "Applied SQLite optimizations: WAL={}, sync={}, cache={}KB, fk={}, temp={}, mmap={}MB",
176 optimizations.enable_wal_mode,
177 optimizations.synchronous_mode,
178 optimizations.cache_size.abs() / 1024,
179 optimizations.enable_foreign_keys,
180 optimizations.temp_store,
181 optimizations.mmap_size / 1024 / 1024
182 );
183
184 Ok(())
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
192 fn test_database_pool_config_default() {
193 let config = DatabasePoolConfig::default();
194 assert_eq!(config.max_connections, 10);
195 assert_eq!(config.min_connections, 1);
196 assert_eq!(config.connect_timeout, Duration::from_secs(30));
197 assert_eq!(config.idle_timeout, Duration::from_secs(600));
198 assert_eq!(config.max_lifetime, Duration::from_secs(1800));
199 assert!(config.test_before_acquire);
200 }
201
202 #[test]
203 fn test_sqlite_optimizations_default() {
204 let opts = SqliteOptimizations::default();
205 assert!(opts.enable_wal_mode);
206 assert_eq!(opts.cache_size, -20000);
207 assert_eq!(opts.synchronous_mode, "NORMAL".to_string());
208 assert_eq!(opts.temp_store, "MEMORY".to_string());
209 assert_eq!(opts.journal_mode, "WAL".to_string());
210 assert_eq!(opts.mmap_size, 268_435_456);
211 assert!(opts.enable_foreign_keys);
212 assert!(opts.enable_query_planner);
213 }
214
215 #[test]
216 fn test_pool_health_status_creation() {
217 let status = PoolHealthStatus {
218 is_healthy: true,
219 pool_size: 8,
220 active_connections: 5,
221 idle_connections: 3,
222 max_connections: 10,
223 min_connections: 1,
224 connection_timeout: Duration::from_secs(30),
225 idle_timeout: Some(Duration::from_secs(600)),
226 max_lifetime: Some(Duration::from_secs(1800)),
227 };
228 assert!(status.is_healthy);
229 assert_eq!(status.active_connections, 5);
230 assert_eq!(status.idle_connections, 3);
231 assert_eq!(status.pool_size, 8);
232 }
233
234 #[test]
235 fn test_pool_metrics_creation() {
236 let metrics = PoolMetrics {
237 pool_size: 8,
238 active_connections: 5,
239 idle_connections: 3,
240 max_connections: 10,
241 min_connections: 1,
242 utilization_percentage: 80.0,
243 is_healthy: true,
244 response_time_ms: 50,
245 connection_timeout: Duration::from_secs(30),
246 idle_timeout: Some(Duration::from_secs(600)),
247 max_lifetime: Some(Duration::from_secs(1800)),
248 };
249 assert!(metrics.is_healthy);
250 assert_eq!(metrics.pool_size, 8);
251 assert_eq!(metrics.active_connections, 5);
252 assert_eq!(metrics.idle_connections, 3);
253 assert!((metrics.utilization_percentage - 80.0).abs() < f64::EPSILON);
254 assert_eq!(metrics.response_time_ms, 50);
255 }
256
257 #[test]
258 fn test_comprehensive_health_status_creation() {
259 let pool_health = PoolHealthStatus {
260 is_healthy: true,
261 pool_size: 8,
262 active_connections: 5,
263 idle_connections: 3,
264 max_connections: 10,
265 min_connections: 1,
266 connection_timeout: Duration::from_secs(30),
267 idle_timeout: Some(Duration::from_secs(600)),
268 max_lifetime: Some(Duration::from_secs(1800)),
269 };
270
271 let pool_metrics = PoolMetrics {
272 pool_size: 8,
273 active_connections: 5,
274 idle_connections: 3,
275 max_connections: 10,
276 min_connections: 1,
277 utilization_percentage: 80.0,
278 is_healthy: true,
279 response_time_ms: 50,
280 connection_timeout: Duration::from_secs(30),
281 idle_timeout: Some(Duration::from_secs(600)),
282 max_lifetime: Some(Duration::from_secs(1800)),
283 };
284
285 let db_stats = DatabaseStats {
286 task_count: 50,
287 project_count: 10,
288 area_count: 5,
289 };
290
291 let health_status = ComprehensiveHealthStatus {
292 overall_healthy: true,
293 pool_health,
294 pool_metrics,
295 database_stats: db_stats,
296 timestamp: Utc::now(),
297 };
298
299 assert!(health_status.overall_healthy);
300 assert_eq!(health_status.database_stats.total_items(), 65);
301 }
302}