Skip to main content

llm_optimizer_processor/state/
postgres_backend_enhanced.rs

1//! Enterprise-grade PostgreSQL backend with advanced features
2//!
3//! This module extends the basic PostgreSQL backend with production-ready features:
4//!
5//! - **Connection Pooling**: Advanced pool management with health checks
6//! - **Replication**: Read replica support with automatic failover
7//! - **Transactions**: Full isolation level control with optimistic locking
8//! - **Advanced Queries**: Prepared statements, batch operations, JSONB queries
9//! - **Distributed Coordination**: Advisory locks, listen/notify, leader election
10//! - **Data Management**: Partitioning, compression, retention policies
11//! - **High Availability**: Streaming replication, failover automation
12//! - **Observability**: Comprehensive metrics and slow query logging
13//! - **Security**: TLS/SSL with certificate validation
14
15use async_trait::async_trait;
16use serde::{Deserialize, Serialize};
17use sqlx::postgres::{PgConnectOptions, PgPoolOptions, PgSslMode};
18use sqlx::{Executor, PgPool, Postgres, Row, Transaction};
19use std::collections::HashMap;
20use std::ops::{Deref, DerefMut};
21use std::path::{Path, PathBuf};
22use std::str::FromStr;
23use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
24use std::sync::Arc;
25use std::time::{Duration, Instant};
26use tokio::sync::{Mutex, RwLock};
27use tracing::{debug, error, info, trace, warn};
28use uuid::Uuid;
29
30use super::backend::StateBackend;
31use crate::error::{StateError, StateResult};
32
33// ============================================================================
34// Configuration Structures
35// ============================================================================
36
37/// PostgreSQL SSL/TLS configuration
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct PostgresSslConfig {
40    /// SSL mode (disable, allow, prefer, require, verify-ca, verify-full)
41    pub mode: String,
42    /// Path to root certificate
43    pub root_cert_path: Option<PathBuf>,
44    /// Path to client certificate
45    pub client_cert_path: Option<PathBuf>,
46    /// Path to client key
47    pub client_key_path: Option<PathBuf>,
48}
49
50impl Default for PostgresSslConfig {
51    fn default() -> Self {
52        Self {
53            mode: "prefer".to_string(),
54            root_cert_path: None,
55            client_cert_path: None,
56            client_key_path: None,
57        }
58    }
59}
60
61/// Connection pool configuration
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct PostgresPoolConfig {
64    /// Minimum number of connections
65    pub min_connections: u32,
66    /// Maximum number of connections
67    pub max_connections: u32,
68    /// Connection acquire timeout
69    pub acquire_timeout: Duration,
70    /// Connection idle timeout
71    pub idle_timeout: Option<Duration>,
72    /// Connection lifetime before recycling
73    pub max_lifetime: Option<Duration>,
74    /// Health check interval
75    pub health_check_interval: Duration,
76    /// Enable test on acquire
77    pub test_on_acquire: bool,
78}
79
80impl Default for PostgresPoolConfig {
81    fn default() -> Self {
82        Self {
83            min_connections: 2,
84            max_connections: 20,
85            acquire_timeout: Duration::from_secs(30),
86            idle_timeout: Some(Duration::from_secs(600)),
87            max_lifetime: Some(Duration::from_secs(1800)),
88            health_check_interval: Duration::from_secs(30),
89            test_on_acquire: true,
90        }
91    }
92}
93
94/// Read replica configuration
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct PostgresReplicaConfig {
97    /// Replica connection URLs
98    pub replica_urls: Vec<String>,
99    /// Enable read/write splitting
100    pub enable_read_splitting: bool,
101    /// Maximum acceptable replication lag (ms)
102    pub max_replication_lag_ms: i64,
103    /// Replica health check interval
104    pub health_check_interval: Duration,
105    /// Enable automatic failover
106    pub enable_auto_failover: bool,
107}
108
109impl Default for PostgresReplicaConfig {
110    fn default() -> Self {
111        Self {
112            replica_urls: Vec::new(),
113            enable_read_splitting: true,
114            max_replication_lag_ms: 1000,
115            health_check_interval: Duration::from_secs(10),
116            enable_auto_failover: true,
117        }
118    }
119}
120
121/// Transaction isolation level
122#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
123pub enum IsolationLevel {
124    ReadCommitted,
125    RepeatableRead,
126    Serializable,
127}
128
129impl IsolationLevel {
130    pub fn as_sql(&self) -> &'static str {
131        match self {
132            IsolationLevel::ReadCommitted => "READ COMMITTED",
133            IsolationLevel::RepeatableRead => "REPEATABLE READ",
134            IsolationLevel::Serializable => "SERIALIZABLE",
135        }
136    }
137}
138
139/// Partitioning strategy
140#[derive(Debug, Clone, Serialize, Deserialize)]
141pub enum PartitionStrategy {
142    /// Partition by hash of key
143    Hash { num_partitions: u32 },
144    /// Partition by time range
145    TimeRange {
146        column: String,
147        interval: String, // e.g., "1 day", "1 week", "1 month"
148    },
149    /// Partition by list of values
150    List { column: String, values: Vec<String> },
151}
152
153/// Data retention policy
154#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct RetentionPolicy {
156    /// Enable automatic cleanup
157    pub enabled: bool,
158    /// Age threshold for deletion
159    pub max_age: Duration,
160    /// Cleanup interval
161    pub cleanup_interval: Duration,
162    /// Batch size for deletion
163    pub batch_size: usize,
164}
165
166impl Default for RetentionPolicy {
167    fn default() -> Self {
168        Self {
169            enabled: false,
170            max_age: Duration::from_secs(86400 * 30), // 30 days
171            cleanup_interval: Duration::from_secs(3600), // 1 hour
172            batch_size: 1000,
173        }
174    }
175}
176
177/// Comprehensive PostgreSQL configuration
178#[derive(Debug, Clone, Serialize, Deserialize)]
179pub struct EnhancedPostgresConfig {
180    /// Primary database URL
181    pub primary_url: String,
182    /// Connection pool configuration
183    pub pool: PostgresPoolConfig,
184    /// SSL/TLS configuration
185    pub ssl: PostgresSslConfig,
186    /// Replica configuration
187    pub replicas: Option<PostgresReplicaConfig>,
188    /// Default transaction isolation level
189    pub default_isolation_level: IsolationLevel,
190    /// Table name for state storage
191    pub table_name: String,
192    /// Schema name
193    pub schema_name: String,
194    /// Enable prepared statement caching
195    pub enable_prepared_statements: bool,
196    /// Prepared statement cache size
197    pub prepared_statement_cache_size: usize,
198    /// Partitioning strategy
199    pub partition_strategy: Option<PartitionStrategy>,
200    /// Data retention policy
201    pub retention_policy: RetentionPolicy,
202    /// Query timeout
203    pub query_timeout: Duration,
204    /// Maximum retry attempts
205    pub max_retries: u32,
206    /// Retry backoff base duration
207    pub retry_backoff: Duration,
208    /// Enable slow query logging
209    pub enable_slow_query_log: bool,
210    /// Slow query threshold
211    pub slow_query_threshold: Duration,
212    /// Enable statement logging
213    pub log_statements: bool,
214    /// Enable metrics collection
215    pub enable_metrics: bool,
216}
217
218impl Default for EnhancedPostgresConfig {
219    fn default() -> Self {
220        Self {
221            primary_url: "postgresql://postgres:postgres@localhost/optimizer".to_string(),
222            pool: PostgresPoolConfig::default(),
223            ssl: PostgresSslConfig::default(),
224            replicas: None,
225            default_isolation_level: IsolationLevel::ReadCommitted,
226            table_name: "state_entries".to_string(),
227            schema_name: "public".to_string(),
228            enable_prepared_statements: true,
229            prepared_statement_cache_size: 100,
230            partition_strategy: None,
231            retention_policy: RetentionPolicy::default(),
232            query_timeout: Duration::from_secs(60),
233            max_retries: 3,
234            retry_backoff: Duration::from_millis(100),
235            enable_slow_query_log: true,
236            slow_query_threshold: Duration::from_millis(1000),
237            log_statements: false,
238            enable_metrics: true,
239        }
240    }
241}
242
243impl EnhancedPostgresConfig {
244    pub fn new<S: Into<String>>(primary_url: S) -> Self {
245        Self {
246            primary_url: primary_url.into(),
247            ..Default::default()
248        }
249    }
250
251    pub fn with_pool_config(mut self, pool: PostgresPoolConfig) -> Self {
252        self.pool = pool;
253        self
254    }
255
256    pub fn with_ssl_config(mut self, ssl: PostgresSslConfig) -> Self {
257        self.ssl = ssl;
258        self
259    }
260
261    pub fn with_replicas(mut self, replicas: PostgresReplicaConfig) -> Self {
262        self.replicas = Some(replicas);
263        self
264    }
265
266    pub fn with_isolation_level(mut self, level: IsolationLevel) -> Self {
267        self.default_isolation_level = level;
268        self
269    }
270
271    pub fn with_partitioning(mut self, strategy: PartitionStrategy) -> Self {
272        self.partition_strategy = Some(strategy);
273        self
274    }
275
276    pub fn with_retention_policy(mut self, policy: RetentionPolicy) -> Self {
277        self.retention_policy = policy;
278        self
279    }
280}
281
282// ============================================================================
283// Metrics and Statistics
284// ============================================================================
285
286/// Enhanced statistics tracking
287#[derive(Debug, Default, Serialize, Deserialize)]
288pub struct EnhancedPostgresStats {
289    // Basic operations
290    pub get_count: AtomicU64,
291    pub put_count: AtomicU64,
292    pub delete_count: AtomicU64,
293    pub query_count: AtomicU64,
294
295    // Transactions
296    pub transaction_count: AtomicU64,
297    pub transaction_rollback_count: AtomicU64,
298    pub transaction_conflict_count: AtomicU64,
299
300    // Connection pool
301    pub pool_acquire_count: AtomicU64,
302    pub pool_acquire_duration_ms: AtomicU64,
303    pub pool_timeout_count: AtomicU64,
304
305    // Replication
306    pub replica_read_count: AtomicU64,
307    pub replica_failover_count: AtomicU64,
308    pub replication_lag_ms: AtomicU64,
309
310    // Performance
311    pub slow_query_count: AtomicU64,
312    pub avg_query_duration_ms: AtomicU64,
313    pub retry_count: AtomicU64,
314
315    // Data
316    pub bytes_written: AtomicU64,
317    pub bytes_read: AtomicU64,
318    pub rows_inserted: AtomicU64,
319    pub rows_updated: AtomicU64,
320    pub rows_deleted: AtomicU64,
321
322    // Batch operations
323    pub batch_insert_count: AtomicU64,
324    pub batch_insert_rows: AtomicU64,
325
326    // Distributed locks
327    pub advisory_lock_count: AtomicU64,
328    pub advisory_lock_wait_ms: AtomicU64,
329}
330
331impl EnhancedPostgresStats {
332    pub fn new() -> Self {
333        Self::default()
334    }
335
336    pub fn snapshot(&self) -> PostgresStatsSnapshot {
337        PostgresStatsSnapshot {
338            get_count: self.get_count.load(Ordering::Relaxed),
339            put_count: self.put_count.load(Ordering::Relaxed),
340            delete_count: self.delete_count.load(Ordering::Relaxed),
341            query_count: self.query_count.load(Ordering::Relaxed),
342            transaction_count: self.transaction_count.load(Ordering::Relaxed),
343            transaction_rollback_count: self.transaction_rollback_count.load(Ordering::Relaxed),
344            transaction_conflict_count: self.transaction_conflict_count.load(Ordering::Relaxed),
345            pool_acquire_count: self.pool_acquire_count.load(Ordering::Relaxed),
346            pool_acquire_duration_ms: self.pool_acquire_duration_ms.load(Ordering::Relaxed),
347            pool_timeout_count: self.pool_timeout_count.load(Ordering::Relaxed),
348            replica_read_count: self.replica_read_count.load(Ordering::Relaxed),
349            replica_failover_count: self.replica_failover_count.load(Ordering::Relaxed),
350            replication_lag_ms: self.replication_lag_ms.load(Ordering::Relaxed),
351            slow_query_count: self.slow_query_count.load(Ordering::Relaxed),
352            avg_query_duration_ms: self.avg_query_duration_ms.load(Ordering::Relaxed),
353            retry_count: self.retry_count.load(Ordering::Relaxed),
354            bytes_written: self.bytes_written.load(Ordering::Relaxed),
355            bytes_read: self.bytes_read.load(Ordering::Relaxed),
356            rows_inserted: self.rows_inserted.load(Ordering::Relaxed),
357            rows_updated: self.rows_updated.load(Ordering::Relaxed),
358            rows_deleted: self.rows_deleted.load(Ordering::Relaxed),
359            batch_insert_count: self.batch_insert_count.load(Ordering::Relaxed),
360            batch_insert_rows: self.batch_insert_rows.load(Ordering::Relaxed),
361            advisory_lock_count: self.advisory_lock_count.load(Ordering::Relaxed),
362            advisory_lock_wait_ms: self.advisory_lock_wait_ms.load(Ordering::Relaxed),
363        }
364    }
365}
366
367#[derive(Debug, Clone, Serialize, Deserialize)]
368pub struct PostgresStatsSnapshot {
369    pub get_count: u64,
370    pub put_count: u64,
371    pub delete_count: u64,
372    pub query_count: u64,
373    pub transaction_count: u64,
374    pub transaction_rollback_count: u64,
375    pub transaction_conflict_count: u64,
376    pub pool_acquire_count: u64,
377    pub pool_acquire_duration_ms: u64,
378    pub pool_timeout_count: u64,
379    pub replica_read_count: u64,
380    pub replica_failover_count: u64,
381    pub replication_lag_ms: u64,
382    pub slow_query_count: u64,
383    pub avg_query_duration_ms: u64,
384    pub retry_count: u64,
385    pub bytes_written: u64,
386    pub bytes_read: u64,
387    pub rows_inserted: u64,
388    pub rows_updated: u64,
389    pub rows_deleted: u64,
390    pub batch_insert_count: u64,
391    pub batch_insert_rows: u64,
392    pub advisory_lock_count: u64,
393    pub advisory_lock_wait_ms: u64,
394}
395
396// ============================================================================
397// Replica Management
398// ============================================================================
399
400struct ReplicaInfo {
401    pool: PgPool,
402    url: String,
403    healthy: AtomicBool,
404    lag_ms: AtomicU64,
405    last_check: Mutex<Instant>,
406}
407
408struct ReplicaManager {
409    replicas: Vec<Arc<ReplicaInfo>>,
410    config: PostgresReplicaConfig,
411    current_index: AtomicU64,
412}
413
414impl ReplicaManager {
415    async fn new(config: PostgresReplicaConfig, ssl_config: &PostgresSslConfig) -> StateResult<Self> {
416        let mut replicas = Vec::new();
417
418        for url in &config.replica_urls {
419            match Self::create_replica_pool(url, ssl_config).await {
420                Ok(pool) => {
421                    replicas.push(Arc::new(ReplicaInfo {
422                        pool,
423                        url: url.clone(),
424                        healthy: AtomicBool::new(true),
425                        lag_ms: AtomicU64::new(0),
426                        last_check: Mutex::new(Instant::now()),
427                    }));
428                }
429                Err(e) => {
430                    warn!("Failed to connect to replica {}: {}", url, e);
431                }
432            }
433        }
434
435        Ok(Self {
436            replicas,
437            config,
438            current_index: AtomicU64::new(0),
439        })
440    }
441
442    async fn create_replica_pool(url: &str, ssl_config: &PostgresSslConfig) -> StateResult<PgPool> {
443        let ssl_mode = Self::parse_ssl_mode(&ssl_config.mode)?;
444        let mut connect_options = PgConnectOptions::from_str(url)
445            .map_err(|e| StateError::StorageError {
446                backend_type: "postgres".to_string(),
447                details: format!("Invalid replica URL: {}", e),
448            })?
449            .ssl_mode(ssl_mode);
450
451        if let Some(root_cert) = &ssl_config.root_cert_path {
452            connect_options = connect_options.ssl_root_cert(root_cert);
453        }
454
455        let pool = PgPoolOptions::new()
456            .min_connections(1)
457            .max_connections(10)
458            .acquire_timeout(Duration::from_secs(10))
459            .connect_with(connect_options)
460            .await
461            .map_err(|e| StateError::StorageError {
462                backend_type: "postgres".to_string(),
463                details: format!("Failed to create replica pool: {}", e),
464            })?;
465
466        Ok(pool)
467    }
468
469    fn parse_ssl_mode(mode: &str) -> StateResult<PgSslMode> {
470        match mode.to_lowercase().as_str() {
471            "disable" => Ok(PgSslMode::Disable),
472            "allow" => Ok(PgSslMode::Allow),
473            "prefer" => Ok(PgSslMode::Prefer),
474            "require" => Ok(PgSslMode::Require),
475            "verify-ca" => Ok(PgSslMode::VerifyCa),
476            "verify-full" => Ok(PgSslMode::VerifyFull),
477            _ => Err(StateError::StorageError {
478                backend_type: "postgres".to_string(),
479                details: format!("Invalid SSL mode: {}", mode),
480            }),
481        }
482    }
483
484    async fn get_read_pool(&self) -> Option<&PgPool> {
485        if self.replicas.is_empty() || !self.config.enable_read_splitting {
486            return None;
487        }
488
489        // Round-robin selection
490        let start_idx = self.current_index.fetch_add(1, Ordering::Relaxed) as usize % self.replicas.len();
491
492        for i in 0..self.replicas.len() {
493            let idx = (start_idx + i) % self.replicas.len();
494            let replica = &self.replicas[idx];
495
496            if replica.healthy.load(Ordering::Relaxed) {
497                let lag = replica.lag_ms.load(Ordering::Relaxed);
498                if lag <= self.config.max_replication_lag_ms as u64 {
499                    return Some(&replica.pool);
500                }
501            }
502        }
503
504        None
505    }
506
507    async fn check_replica_health(&self, stats: &EnhancedPostgresStats) {
508        for replica in &self.replicas {
509            let mut last_check = replica.last_check.lock().await;
510            if last_check.elapsed() < self.config.health_check_interval {
511                continue;
512            }
513            *last_check = Instant::now();
514            drop(last_check);
515
516            // Check if replica is reachable
517            match sqlx::query("SELECT 1").fetch_one(&replica.pool).await {
518                Ok(_) => {
519                    replica.healthy.store(true, Ordering::Relaxed);
520
521                    // Measure replication lag
522                    if let Ok(lag) = self.measure_replication_lag(&replica.pool).await {
523                        replica.lag_ms.store(lag as u64, Ordering::Relaxed);
524                        stats.replication_lag_ms.store(lag as u64, Ordering::Relaxed);
525                    }
526                }
527                Err(e) => {
528                    warn!("Replica {} unhealthy: {}", replica.url, e);
529                    replica.healthy.store(false, Ordering::Relaxed);
530
531                    if self.config.enable_auto_failover {
532                        stats.replica_failover_count.fetch_add(1, Ordering::Relaxed);
533                    }
534                }
535            }
536        }
537    }
538
539    async fn measure_replication_lag(&self, pool: &PgPool) -> StateResult<i64> {
540        let row = sqlx::query(
541            "SELECT EXTRACT(EPOCH FROM (NOW() - pg_last_xact_replay_timestamp())) * 1000 AS lag_ms"
542        )
543        .fetch_one(pool)
544        .await
545        .map_err(|e| StateError::StorageError {
546            backend_type: "postgres".to_string(),
547            details: format!("Failed to measure replication lag: {}", e),
548        })?;
549
550        let lag: Option<f64> = row.try_get("lag_ms").ok();
551        Ok(lag.unwrap_or(0.0) as i64)
552    }
553}
554
555// ============================================================================
556// Transaction Manager
557// ============================================================================
558
559pub struct PostgresTransaction<'a> {
560    pub(crate) tx: Transaction<'a, Postgres>,
561    isolation_level: IsolationLevel,
562}
563
564impl<'a> PostgresTransaction<'a> {
565    async fn new(pool: &'a PgPool, isolation_level: IsolationLevel) -> StateResult<Self> {
566        let mut tx = pool.begin().await.map_err(|e| StateError::TransactionFailed {
567            operation: "begin".to_string(),
568            reason: e.to_string(),
569        })?;
570
571        // Set isolation level
572        sqlx::query(&format!("SET TRANSACTION ISOLATION LEVEL {}", isolation_level.as_sql()))
573            .execute(&mut *tx)
574            .await
575            .map_err(|e| StateError::TransactionFailed {
576                operation: "set_isolation_level".to_string(),
577                reason: e.to_string(),
578            })?;
579
580        Ok(Self { tx, isolation_level })
581    }
582
583    pub async fn commit(self) -> StateResult<()> {
584        self.tx.commit().await.map_err(|e| StateError::TransactionFailed {
585            operation: "commit".to_string(),
586            reason: e.to_string(),
587        })
588    }
589
590    pub async fn rollback(self) -> StateResult<()> {
591        self.tx.rollback().await.map_err(|e| StateError::TransactionFailed {
592            operation: "rollback".to_string(),
593            reason: e.to_string(),
594        })
595    }
596
597    pub async fn savepoint(&mut self, name: &str) -> StateResult<()> {
598        sqlx::query(&format!("SAVEPOINT {}", name))
599            .execute(&mut *self.tx)
600            .await
601            .map_err(|e| StateError::TransactionFailed {
602                operation: "savepoint".to_string(),
603                reason: e.to_string(),
604            })?;
605        Ok(())
606    }
607
608    pub async fn rollback_to_savepoint(&mut self, name: &str) -> StateResult<()> {
609        sqlx::query(&format!("ROLLBACK TO SAVEPOINT {}", name))
610            .execute(&mut *self.tx)
611            .await
612            .map_err(|e| StateError::TransactionFailed {
613                operation: "rollback_to_savepoint".to_string(),
614                reason: e.to_string(),
615            })?;
616        Ok(())
617    }
618
619    pub fn executor(&mut self) -> &mut Transaction<'a, Postgres> {
620        &mut self.tx
621    }
622}
623
624// ============================================================================
625// Main Backend Implementation
626// ============================================================================
627
628pub struct EnhancedPostgresBackend {
629    primary_pool: PgPool,
630    replica_manager: Option<Arc<ReplicaManager>>,
631    config: EnhancedPostgresConfig,
632    stats: Arc<EnhancedPostgresStats>,
633    prepared_statements: Arc<RwLock<HashMap<String, String>>>,
634    health_check_running: Arc<AtomicBool>,
635}
636
637impl EnhancedPostgresBackend {
638    pub async fn new(config: EnhancedPostgresConfig) -> StateResult<Self> {
639        info!("Initializing Enhanced PostgreSQL backend");
640        info!("Primary: {}", Self::sanitize_url(&config.primary_url));
641        info!("Pool: {:?}", config.pool);
642
643        // Create primary pool
644        let primary_pool = Self::create_primary_pool(&config).await?;
645
646        // Create replica manager if configured
647        let replica_manager = if let Some(replica_config) = &config.replicas {
648            info!("Initializing {} read replicas", replica_config.replica_urls.len());
649            Some(Arc::new(ReplicaManager::new(replica_config.clone(), &config.ssl).await?))
650        } else {
651            None
652        };
653
654        let backend = Self {
655            primary_pool,
656            replica_manager,
657            config,
658            stats: Arc::new(EnhancedPostgresStats::new()),
659            prepared_statements: Arc::new(RwLock::new(HashMap::new())),
660            health_check_running: Arc::new(AtomicBool::new(false)),
661        };
662
663        // Run migrations
664        backend.run_migrations().await?;
665
666        // Initialize partitioning if configured
667        if backend.config.partition_strategy.is_some() {
668            backend.setup_partitioning().await?;
669        }
670
671        // Start health check background task
672        if backend.config.enable_metrics {
673            backend.start_health_check_task();
674        }
675
676        info!("Enhanced PostgreSQL backend initialized successfully");
677        Ok(backend)
678    }
679
680    async fn create_primary_pool(config: &EnhancedPostgresConfig) -> StateResult<PgPool> {
681        let ssl_mode = ReplicaManager::parse_ssl_mode(&config.ssl.mode)?;
682
683        let mut connect_options = PgConnectOptions::from_str(&config.primary_url)
684            .map_err(|e| StateError::StorageError {
685                backend_type: "postgres".to_string(),
686                details: format!("Invalid database URL: {}", e),
687            })?
688            .ssl_mode(ssl_mode);
689
690        if let Some(root_cert) = &config.ssl.root_cert_path {
691            connect_options = connect_options.ssl_root_cert(root_cert);
692        }
693
694        if let Some(client_cert) = &config.ssl.client_cert_path {
695            if let Some(client_key) = &config.ssl.client_key_path {
696                connect_options = connect_options
697                    .ssl_client_cert(client_cert)
698                    .ssl_client_key(client_key);
699            }
700        }
701
702        let mut pool_options = PgPoolOptions::new()
703            .min_connections(config.pool.min_connections)
704            .max_connections(config.pool.max_connections)
705            .acquire_timeout(config.pool.acquire_timeout);
706
707        if let Some(idle_timeout) = config.pool.idle_timeout {
708            pool_options = pool_options.idle_timeout(idle_timeout);
709        }
710
711        if let Some(max_lifetime) = config.pool.max_lifetime {
712            pool_options = pool_options.max_lifetime(max_lifetime);
713        }
714
715        if config.pool.test_on_acquire {
716            pool_options = pool_options.test_before_acquire(true);
717        }
718
719        let pool = pool_options
720            .connect_with(connect_options)
721            .await
722            .map_err(|e| StateError::StorageError {
723                backend_type: "postgres".to_string(),
724                details: format!("Failed to create connection pool: {}", e),
725            })?;
726
727        info!("Primary connection pool created successfully");
728        Ok(pool)
729    }
730
731    fn start_health_check_task(&self) {
732        if self.health_check_running.swap(true, Ordering::Relaxed) {
733            return; // Already running
734        }
735
736        let replica_manager = self.replica_manager.clone();
737        let stats = self.stats.clone();
738        let interval = self.config.pool.health_check_interval;
739        let running = self.health_check_running.clone();
740
741        tokio::spawn(async move {
742            while running.load(Ordering::Relaxed) {
743                if let Some(rm) = &replica_manager {
744                    rm.check_replica_health(&stats).await;
745                }
746                tokio::time::sleep(interval).await;
747            }
748        });
749    }
750
751    fn stop_health_check_task(&self) {
752        self.health_check_running.store(false, Ordering::Relaxed);
753    }
754
755    async fn run_migrations(&self) -> StateResult<()> {
756        info!("Running database migrations");
757        let migration_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("migrations");
758
759        if !migration_dir.exists() {
760            warn!("Migrations directory not found: {:?}", migration_dir);
761            return Ok(());
762        }
763
764        let mut migrations = Vec::new();
765        let mut entries = tokio::fs::read_dir(&migration_dir).await.map_err(|e| {
766            StateError::StorageError {
767                backend_type: "postgres".to_string(),
768                details: format!("Failed to read migrations: {}", e),
769            }
770        })?;
771
772        while let Some(entry) = entries.next_entry().await.map_err(|e| StateError::StorageError {
773            backend_type: "postgres".to_string(),
774            details: format!("Failed to read migration entry: {}", e),
775        })? {
776            let path = entry.path();
777            if path.extension().and_then(|s| s.to_str()) == Some("sql") {
778                migrations.push(path);
779            }
780        }
781
782        migrations.sort();
783
784        for migration_path in migrations {
785            let filename = migration_path
786                .file_name()
787                .and_then(|s| s.to_str())
788                .unwrap_or("unknown");
789
790            debug!("Applying migration: {}", filename);
791
792            let sql = tokio::fs::read_to_string(&migration_path)
793                .await
794                .map_err(|e| StateError::StorageError {
795                    backend_type: "postgres".to_string(),
796                    details: format!("Failed to read migration {}: {}", filename, e),
797                })?;
798
799            sqlx::raw_sql(&sql)
800                .execute(&self.primary_pool)
801                .await
802                .map_err(|e| StateError::StorageError {
803                    backend_type: "postgres".to_string(),
804                    details: format!("Failed to execute migration {}: {}", filename, e),
805                })?;
806        }
807
808        info!("All migrations applied successfully");
809        Ok(())
810    }
811
812    async fn setup_partitioning(&self) -> StateResult<()> {
813        if let Some(strategy) = &self.config.partition_strategy {
814            info!("Setting up table partitioning: {:?}", strategy);
815
816            match strategy {
817                PartitionStrategy::Hash { num_partitions } => {
818                    self.setup_hash_partitioning(*num_partitions).await?;
819                }
820                PartitionStrategy::TimeRange { column, interval } => {
821                    self.setup_time_range_partitioning(column, interval).await?;
822                }
823                PartitionStrategy::List { column, values } => {
824                    self.setup_list_partitioning(column, values).await?;
825                }
826            }
827        }
828        Ok(())
829    }
830
831    async fn setup_hash_partitioning(&self, num_partitions: u32) -> StateResult<()> {
832        // This would require recreating the table as partitioned
833        // For now, log a warning that this needs manual setup
834        warn!("Hash partitioning requires manual table recreation. Please run:");
835        warn!("  CREATE TABLE {} PARTITION BY HASH (key);", self.config.table_name);
836        for i in 0..num_partitions {
837            warn!("  CREATE TABLE {}_p{} PARTITION OF {} FOR VALUES WITH (MODULUS {}, REMAINDER {});",
838                  self.config.table_name, i, self.config.table_name, num_partitions, i);
839        }
840        Ok(())
841    }
842
843    async fn setup_time_range_partitioning(&self, column: &str, interval: &str) -> StateResult<()> {
844        warn!("Time-range partitioning requires manual table recreation. Please run:");
845        warn!("  CREATE TABLE {} PARTITION BY RANGE ({});", self.config.table_name, column);
846        warn!("  -- Then create partitions for each time range");
847        Ok(())
848    }
849
850    async fn setup_list_partitioning(&self, column: &str, values: &[String]) -> StateResult<()> {
851        warn!("List partitioning requires manual table recreation. Please run:");
852        warn!("  CREATE TABLE {} PARTITION BY LIST ({});", self.config.table_name, column);
853        Ok(())
854    }
855
856    fn sanitize_url(url: &str) -> String {
857        if let Some(at_pos) = url.rfind('@') {
858            if let Some(scheme_end) = url.find("://") {
859                let scheme = &url[..scheme_end + 3];
860                let host = &url[at_pos + 1..];
861                return format!("{}***@{}", scheme, host);
862            }
863        }
864        url.to_string()
865    }
866
867    // ========================================================================
868    // Advanced Query Operations
869    // ========================================================================
870
871    /// Execute query with metrics tracking
872    async fn execute_tracked<F, T, Fut>(&self, operation: &str, f: F) -> StateResult<T>
873    where
874        F: FnOnce() -> Fut,
875        Fut: std::future::Future<Output = StateResult<T>>,
876    {
877        let start = Instant::now();
878        let result = f().await;
879        let duration = start.elapsed();
880
881        if self.config.enable_metrics {
882            self.stats.query_count.fetch_add(1, Ordering::Relaxed);
883
884            let duration_ms = duration.as_millis() as u64;
885            let current_avg = self.stats.avg_query_duration_ms.load(Ordering::Relaxed);
886            let new_avg = if current_avg == 0 {
887                duration_ms
888            } else {
889                (current_avg + duration_ms) / 2
890            };
891            self.stats.avg_query_duration_ms.store(new_avg, Ordering::Relaxed);
892
893            if duration > self.config.slow_query_threshold {
894                self.stats.slow_query_count.fetch_add(1, Ordering::Relaxed);
895                warn!(
896                    "Slow query detected: {} took {:?} (threshold: {:?})",
897                    operation, duration, self.config.slow_query_threshold
898                );
899            }
900        }
901
902        result
903    }
904
905    /// Get connection pool (primary or replica based on operation type)
906    async fn get_pool_for_read(&self) -> &PgPool {
907        if let Some(replica_manager) = &self.replica_manager {
908            if let Some(pool) = replica_manager.get_read_pool().await {
909                self.stats.replica_read_count.fetch_add(1, Ordering::Relaxed);
910                return pool;
911            }
912        }
913        &self.primary_pool
914    }
915
916    /// Helper function for prefix range calculation
917    fn next_prefix(prefix: &[u8]) -> Vec<u8> {
918        let mut next = prefix.to_vec();
919        for i in (0..next.len()).rev() {
920            if next[i] < 255 {
921                next[i] += 1;
922                return next;
923            }
924        }
925        next.push(0);
926        next
927    }
928
929    fn clone_handle(&self) -> Self {
930        Self {
931            primary_pool: self.primary_pool.clone(),
932            replica_manager: self.replica_manager.clone(),
933            config: self.config.clone(),
934            stats: self.stats.clone(),
935            prepared_statements: self.prepared_statements.clone(),
936            health_check_running: self.health_check_running.clone(),
937        }
938    }
939
940    // ========================================================================
941    // Extended Public API
942    // ========================================================================
943
944    /// Begin a new transaction with specified isolation level
945    pub async fn begin_transaction(&self, isolation_level: IsolationLevel) -> StateResult<PostgresTransaction<'_>> {
946        trace!("BEGIN TRANSACTION: isolation={:?}", isolation_level);
947
948        let tx = PostgresTransaction::new(&self.primary_pool, isolation_level).await?;
949        self.stats.transaction_count.fetch_add(1, Ordering::Relaxed);
950
951        Ok(tx)
952    }
953
954    /// Batch insert using COPY command for maximum performance
955    pub async fn batch_copy(&self, entries: &[(&[u8], &[u8])]) -> StateResult<()> {
956        if entries.is_empty() {
957            return Ok(());
958        }
959
960        info!("Batch COPY: {} entries", entries.len());
961
962        let mut tx = self.primary_pool.begin().await.map_err(|e| {
963            StateError::TransactionFailed {
964                operation: "begin".to_string(),
965                reason: e.to_string(),
966            }
967        })?;
968
969        // Use multi-row INSERT as efficient alternative to COPY
970        let values_per_batch = 1000;
971        for chunk in entries.chunks(values_per_batch) {
972            let mut values = Vec::new();
973            for (i, _) in chunk.iter().enumerate() {
974                values.push(format!("(${}::bytea, ${}::bytea, NOW())", i * 2 + 1, i * 2 + 2));
975            }
976
977            let sql = format!(
978                "INSERT INTO {} (key, value, updated_at) VALUES {}
979                 ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW()",
980                self.config.table_name,
981                values.join(", ")
982            );
983
984            let mut query = sqlx::query(&sql);
985            for (key, value) in chunk {
986                query = query.bind(*key).bind(*value);
987            }
988
989            query.execute(&mut *tx).await.map_err(|e| StateError::StorageError {
990                backend_type: "postgres".to_string(),
991                details: format!("Bulk insert failed: {}", e),
992            })?;
993        }
994
995        tx.commit().await.map_err(|e| StateError::TransactionFailed {
996            operation: "commit".to_string(),
997            reason: e.to_string(),
998        })?;
999
1000        self.stats.batch_insert_count.fetch_add(1, Ordering::Relaxed);
1001        self.stats.batch_insert_rows.fetch_add(entries.len() as u64, Ordering::Relaxed);
1002
1003        Ok(())
1004    }
1005
1006    /// Batch put operation (standard transaction version)
1007    pub async fn batch_put(&self, entries: &[(&[u8], &[u8])]) -> StateResult<()> {
1008        if entries.is_empty() {
1009            return Ok(());
1010        }
1011
1012        debug!("BATCH_PUT: {} entries", entries.len());
1013
1014        let mut tx = self.begin_transaction(self.config.default_isolation_level).await?;
1015
1016        for (key, value) in entries {
1017            sqlx::query(
1018                "INSERT INTO state_entries (key, value, updated_at)
1019                 VALUES ($1, $2, NOW())
1020                 ON CONFLICT (key) DO UPDATE
1021                 SET value = EXCLUDED.value, updated_at = NOW()"
1022            )
1023            .bind(key)
1024            .bind(value)
1025            .execute(&mut **tx.executor())
1026            .await
1027            .map_err(|e| StateError::StorageError {
1028                backend_type: "postgres".to_string(),
1029                details: format!("Batch insert failed: {}", e),
1030            })?;
1031        }
1032
1033        tx.commit().await?;
1034        self.stats.put_count.fetch_add(entries.len() as u64, Ordering::Relaxed);
1035
1036        Ok(())
1037    }
1038
1039    /// Query with JSONB path expressions
1040    pub async fn query_by_metadata(
1041        &self,
1042        json_path: &str,
1043        value: &serde_json::Value,
1044    ) -> StateResult<Vec<(Vec<u8>, Vec<u8>)>> {
1045        debug!("Querying by metadata: path={}", json_path);
1046
1047        let pool = self.get_pool_for_read().await;
1048
1049        let rows = sqlx::query(&format!(
1050            "SELECT key, value FROM {}
1051             WHERE metadata @> $1::jsonb
1052             AND (expires_at IS NULL OR expires_at > NOW())",
1053            self.config.table_name
1054        ))
1055        .bind(value)
1056        .fetch_all(pool)
1057        .await
1058        .map_err(|e| StateError::StorageError {
1059            backend_type: "postgres".to_string(),
1060            details: format!("Metadata query failed: {}", e),
1061        })?;
1062
1063        let mut results = Vec::new();
1064        for row in rows {
1065            let key: Vec<u8> = row.try_get("key").map_err(|e| StateError::StorageError {
1066                backend_type: "postgres".to_string(),
1067                details: format!("Failed to extract key: {}", e),
1068            })?;
1069
1070            let value: Vec<u8> = row.try_get("value").map_err(|e| StateError::StorageError {
1071                backend_type: "postgres".to_string(),
1072                details: format!("Failed to extract value: {}", e),
1073            })?;
1074
1075            results.push((key, value));
1076        }
1077
1078        Ok(results)
1079    }
1080
1081    /// Put with optimistic locking using version column
1082    pub async fn put_with_version(
1083        &self,
1084        key: &[u8],
1085        value: &[u8],
1086        expected_version: Option<i64>,
1087    ) -> StateResult<i64> {
1088        trace!("Put with version check: key_len={}", key.len());
1089
1090        let result = if let Some(expected) = expected_version {
1091            // Update only if version matches
1092            sqlx::query(
1093                "UPDATE state_entries
1094                 SET value = $1, version = version + 1, updated_at = NOW()
1095                 WHERE key = $2 AND version = $3
1096                 RETURNING version",
1097            )
1098            .bind(value)
1099            .bind(key)
1100            .bind(expected)
1101            .fetch_optional(&self.primary_pool)
1102            .await
1103        } else {
1104            // Insert or update
1105            sqlx::query(
1106                "INSERT INTO state_entries (key, value, version, updated_at)
1107                 VALUES ($1, $2, 1, NOW())
1108                 ON CONFLICT (key) DO UPDATE
1109                 SET value = EXCLUDED.value,
1110                     version = state_entries.version + 1,
1111                     updated_at = NOW()
1112                 RETURNING version",
1113            )
1114            .bind(key)
1115            .bind(value)
1116            .fetch_optional(&self.primary_pool)
1117            .await
1118        }
1119        .map_err(|e| StateError::StorageError {
1120            backend_type: "postgres".to_string(),
1121            details: format!("Versioned put failed: {}", e),
1122        })?;
1123
1124        if let Some(row) = result {
1125            let version: i64 = row.try_get("version").map_err(|e| StateError::StorageError {
1126                backend_type: "postgres".to_string(),
1127                details: format!("Failed to get version: {}", e),
1128            })?;
1129
1130            self.stats.put_count.fetch_add(1, Ordering::Relaxed);
1131            self.stats.rows_updated.fetch_add(1, Ordering::Relaxed);
1132
1133            Ok(version)
1134        } else {
1135            Err(StateError::ConcurrentModification {
1136                key: String::from_utf8_lossy(key).to_string(),
1137            })
1138        }
1139    }
1140
1141    /// Put with metadata
1142    pub async fn put_with_metadata(
1143        &self,
1144        key: &[u8],
1145        value: &[u8],
1146        metadata: serde_json::Value,
1147    ) -> StateResult<()> {
1148        trace!("PUT_WITH_METADATA: key_len={}", key.len());
1149
1150        sqlx::query(
1151            "INSERT INTO state_entries (key, value, metadata, updated_at)
1152             VALUES ($1, $2, $3, NOW())
1153             ON CONFLICT (key) DO UPDATE
1154             SET value = EXCLUDED.value,
1155                 metadata = EXCLUDED.metadata,
1156                 updated_at = NOW()"
1157        )
1158        .bind(key)
1159        .bind(value)
1160        .bind(metadata)
1161        .execute(&self.primary_pool)
1162        .await
1163        .map_err(|e| StateError::StorageError {
1164            backend_type: "postgres".to_string(),
1165            details: format!("Put with metadata failed: {}", e),
1166        })?;
1167
1168        self.stats.put_count.fetch_add(1, Ordering::Relaxed);
1169        Ok(())
1170    }
1171
1172    /// Put with TTL
1173    pub async fn put_with_ttl(&self, key: &[u8], value: &[u8], ttl: Duration) -> StateResult<()> {
1174        trace!("PUT_WITH_TTL: key_len={}, ttl={:?}", key.len(), ttl);
1175
1176        let expires_at = chrono::Utc::now() + chrono::Duration::from_std(ttl).unwrap();
1177
1178        sqlx::query(
1179            "INSERT INTO state_entries (key, value, expires_at, updated_at)
1180             VALUES ($1, $2, $3, NOW())
1181             ON CONFLICT (key) DO UPDATE
1182             SET value = EXCLUDED.value,
1183                 expires_at = EXCLUDED.expires_at,
1184                 updated_at = NOW()"
1185        )
1186        .bind(key)
1187        .bind(value)
1188        .bind(expires_at)
1189        .execute(&self.primary_pool)
1190        .await
1191        .map_err(|e| StateError::StorageError {
1192            backend_type: "postgres".to_string(),
1193            details: format!("Put with TTL failed: {}", e),
1194        })?;
1195
1196        self.stats.put_count.fetch_add(1, Ordering::Relaxed);
1197        Ok(())
1198    }
1199
1200    /// Clean up expired entries
1201    pub async fn cleanup_expired(&self) -> StateResult<usize> {
1202        debug!("CLEANUP_EXPIRED");
1203
1204        let result = sqlx::query("SELECT cleanup_expired_state_entries()")
1205            .fetch_one(&self.primary_pool)
1206            .await
1207            .map_err(|e| StateError::StorageError {
1208                backend_type: "postgres".to_string(),
1209                details: format!("Cleanup failed: {}", e),
1210            })?;
1211
1212        let count: i64 = result.try_get(0).map_err(|e| StateError::StorageError {
1213            backend_type: "postgres".to_string(),
1214            details: format!("Failed to get cleanup count: {}", e),
1215        })?;
1216
1217        self.stats.rows_deleted.fetch_add(count as u64, Ordering::Relaxed);
1218        Ok(count as usize)
1219    }
1220
1221    // ========================================================================
1222    // Distributed Coordination
1223    // ========================================================================
1224
1225    /// Acquire PostgreSQL advisory lock
1226    pub async fn acquire_advisory_lock(&self, lock_id: i64) -> StateResult<bool> {
1227        trace!("Acquiring advisory lock: {}", lock_id);
1228
1229        let start = Instant::now();
1230
1231        let row = sqlx::query("SELECT pg_try_advisory_lock($1) AS acquired")
1232            .bind(lock_id)
1233            .fetch_one(&self.primary_pool)
1234            .await
1235            .map_err(|e| StateError::StorageError {
1236                backend_type: "postgres".to_string(),
1237                details: format!("Advisory lock failed: {}", e),
1238            })?;
1239
1240        let acquired: bool = row.try_get("acquired").unwrap_or(false);
1241
1242        if acquired {
1243            self.stats.advisory_lock_count.fetch_add(1, Ordering::Relaxed);
1244            self.stats.advisory_lock_wait_ms.fetch_add(
1245                start.elapsed().as_millis() as u64,
1246                Ordering::Relaxed,
1247            );
1248        }
1249
1250        Ok(acquired)
1251    }
1252
1253    /// Release PostgreSQL advisory lock
1254    pub async fn release_advisory_lock(&self, lock_id: i64) -> StateResult<()> {
1255        trace!("Releasing advisory lock: {}", lock_id);
1256
1257        sqlx::query("SELECT pg_advisory_unlock($1)")
1258            .bind(lock_id)
1259            .execute(&self.primary_pool)
1260            .await
1261            .map_err(|e| StateError::StorageError {
1262                backend_type: "postgres".to_string(),
1263                details: format!("Advisory unlock failed: {}", e),
1264            })?;
1265
1266        Ok(())
1267    }
1268
1269    /// Notify listeners on a channel (LISTEN/NOTIFY)
1270    pub async fn notify(&self, channel: &str, payload: &str) -> StateResult<()> {
1271        debug!("Sending notification: channel={}", channel);
1272
1273        sqlx::query("SELECT pg_notify($1, $2)")
1274            .bind(channel)
1275            .bind(payload)
1276            .execute(&self.primary_pool)
1277            .await
1278            .map_err(|e| StateError::StorageError {
1279                backend_type: "postgres".to_string(),
1280                details: format!("Notify failed: {}", e),
1281            })?;
1282
1283        Ok(())
1284    }
1285
1286    /// Leader election using advisory locks with timeout
1287    pub async fn try_become_leader(&self, election_key: &str, ttl: Duration) -> StateResult<bool> {
1288        info!("Attempting leader election: key={}", election_key);
1289
1290        // Use a hash of the election key as the lock ID
1291        let lock_id = {
1292            use sha2::{Digest, Sha256};
1293            let mut hasher = Sha256::new();
1294            hasher.update(election_key.as_bytes());
1295            let result = hasher.finalize();
1296            i64::from_le_bytes(result[0..8].try_into().unwrap())
1297        };
1298
1299        // Try to acquire lock
1300        let acquired = self.acquire_advisory_lock(lock_id).await?;
1301
1302        if acquired {
1303            info!("Leader election won: key={}", election_key);
1304
1305            // Schedule lock refresh
1306            let backend = self.clone_handle();
1307            tokio::spawn(async move {
1308                tokio::time::sleep(ttl).await;
1309                if let Err(e) = backend.release_advisory_lock(lock_id).await {
1310                    warn!("Failed to release leader lock: {}", e);
1311                }
1312            });
1313        }
1314
1315        Ok(acquired)
1316    }
1317
1318    // ========================================================================
1319    // Data Management
1320    // ========================================================================
1321
1322    /// Execute VACUUM with options
1323    pub async fn vacuum_analyze(&self, full: bool) -> StateResult<()> {
1324        info!("Running VACUUM{} ANALYZE", if full { " FULL" } else { "" });
1325
1326        let sql = if full {
1327            format!("VACUUM FULL ANALYZE {}", self.config.table_name)
1328        } else {
1329            format!("VACUUM ANALYZE {}", self.config.table_name)
1330        };
1331
1332        sqlx::query(&sql)
1333            .execute(&self.primary_pool)
1334            .await
1335            .map_err(|e| StateError::StorageError {
1336                backend_type: "postgres".to_string(),
1337                details: format!("VACUUM failed: {}", e),
1338            })?;
1339
1340        info!("VACUUM completed successfully");
1341        Ok(())
1342    }
1343
1344    /// Apply retention policy - delete old entries
1345    pub async fn apply_retention_policy(&self) -> StateResult<usize> {
1346        if !self.config.retention_policy.enabled {
1347            return Ok(0);
1348        }
1349
1350        info!("Applying retention policy");
1351
1352        let cutoff_time = chrono::Utc::now()
1353            - chrono::Duration::from_std(self.config.retention_policy.max_age).unwrap();
1354
1355        let result = sqlx::query(&format!(
1356            "WITH deleted AS (
1357                DELETE FROM {}
1358                WHERE created_at < $1
1359                RETURNING 1
1360             )
1361             SELECT count(*) as count FROM deleted",
1362            self.config.table_name
1363        ))
1364        .bind(cutoff_time)
1365        .fetch_one(&self.primary_pool)
1366        .await
1367        .map_err(|e| StateError::StorageError {
1368            backend_type: "postgres".to_string(),
1369            details: format!("Retention policy failed: {}", e),
1370        })?;
1371
1372        let deleted: i64 = result.try_get("count").unwrap_or(0);
1373        info!("Retention policy: deleted {} entries", deleted);
1374
1375        self.stats.rows_deleted.fetch_add(deleted as u64, Ordering::Relaxed);
1376
1377        Ok(deleted as usize)
1378    }
1379
1380    /// Archive old data to a separate table
1381    pub async fn archive_old_data(&self, archive_table: &str, age_threshold: Duration) -> StateResult<usize> {
1382        info!("Archiving data older than {:?}", age_threshold);
1383
1384        let cutoff_time = chrono::Utc::now() - chrono::Duration::from_std(age_threshold).unwrap();
1385
1386        let mut tx = self.primary_pool.begin().await.map_err(|e| {
1387            StateError::TransactionFailed {
1388                operation: "begin".to_string(),
1389                reason: e.to_string(),
1390            }
1391        })?;
1392
1393        // Copy to archive table
1394        let result = sqlx::query(&format!(
1395            "INSERT INTO {} SELECT *, NOW() as archived_at FROM {} WHERE created_at < $1",
1396            archive_table, self.config.table_name
1397        ))
1398        .bind(cutoff_time)
1399        .execute(&mut *tx)
1400        .await
1401        .map_err(|e| StateError::StorageError {
1402            backend_type: "postgres".to_string(),
1403            details: format!("Archive insert failed: {}", e),
1404        })?;
1405
1406        let archived = result.rows_affected() as usize;
1407
1408        // Delete from main table
1409        sqlx::query(&format!(
1410            "DELETE FROM {} WHERE created_at < $1",
1411            self.config.table_name
1412        ))
1413        .bind(cutoff_time)
1414        .execute(&mut *tx)
1415        .await
1416        .map_err(|e| StateError::StorageError {
1417            backend_type: "postgres".to_string(),
1418            details: format!("Archive delete failed: {}", e),
1419        })?;
1420
1421        tx.commit().await.map_err(|e| StateError::TransactionFailed {
1422            operation: "commit".to_string(),
1423            reason: e.to_string(),
1424        })?;
1425
1426        info!("Archived {} entries", archived);
1427        Ok(archived)
1428    }
1429
1430    /// Get comprehensive table statistics
1431    pub async fn get_table_statistics(&self) -> StateResult<TableStatistics> {
1432        debug!("Fetching table statistics");
1433
1434        let pool = self.get_pool_for_read().await;
1435
1436        let row = sqlx::query("SELECT * FROM get_state_table_stats()")
1437            .fetch_one(pool)
1438            .await
1439            .map_err(|e| StateError::StorageError {
1440                backend_type: "postgres".to_string(),
1441                details: format!("Failed to get table stats: {}", e),
1442            })?;
1443
1444        Ok(TableStatistics {
1445            total_rows: row.try_get("total_rows").unwrap_or(0),
1446            active_rows: row.try_get("active_rows").unwrap_or(0),
1447            expired_rows: row.try_get("expired_rows").unwrap_or(0),
1448            total_size: row.try_get("total_size_bytes").unwrap_or(0),
1449            table_size: row.try_get("table_size_bytes").unwrap_or(0),
1450            indexes_size: row.try_get("indexes_size_bytes").unwrap_or(0),
1451            toast_size: row.try_get("toast_size_bytes").unwrap_or(0),
1452        })
1453    }
1454
1455    // ========================================================================
1456    // High Availability
1457    // ========================================================================
1458
1459    /// Check replication status
1460    pub async fn check_replication_status(&self) -> StateResult<ReplicationStatus> {
1461        debug!("Checking replication status");
1462
1463        let row = sqlx::query("SELECT * FROM replication_lag")
1464            .fetch_one(&self.primary_pool)
1465            .await
1466            .map_err(|e| StateError::StorageError {
1467                backend_type: "postgres".to_string(),
1468                details: format!("Replication status check failed: {}", e),
1469            })?;
1470
1471        Ok(ReplicationStatus {
1472            is_replica: row.try_get("is_replica").unwrap_or(false),
1473            lag_ms: row.try_get::<Option<f64>, _>("lag_ms").unwrap_or(None).unwrap_or(0.0) as i64,
1474            replicas_connected: self.replica_manager.as_ref().map(|rm| rm.replicas.len()).unwrap_or(0),
1475        })
1476    }
1477
1478    /// Trigger a manual checkpoint
1479    pub async fn checkpoint(&self) -> StateResult<()> {
1480        info!("Triggering manual checkpoint");
1481
1482        sqlx::query("CHECKPOINT")
1483            .execute(&self.primary_pool)
1484            .await
1485            .map_err(|e| StateError::StorageError {
1486                backend_type: "postgres".to_string(),
1487                details: format!("Checkpoint failed: {}", e),
1488            })?;
1489
1490        Ok(())
1491    }
1492
1493    // ========================================================================
1494    // Observability
1495    // ========================================================================
1496
1497    /// Get current statistics snapshot
1498    pub fn get_stats(&self) -> PostgresStatsSnapshot {
1499        self.stats.snapshot()
1500    }
1501
1502    /// Get pool statistics
1503    pub fn get_pool_stats(&self) -> PoolStatistics {
1504        let size = self.primary_pool.size() as u64;
1505        let idle = self.primary_pool.num_idle() as u64;
1506        PoolStatistics {
1507            size,
1508            idle,
1509            active: size.saturating_sub(idle),
1510            max_size: self.config.pool.max_connections as u64,
1511        }
1512    }
1513
1514    /// Health check
1515    pub async fn health_check(&self) -> StateResult<bool> {
1516        sqlx::query("SELECT 1")
1517            .fetch_one(&self.primary_pool)
1518            .await
1519            .map(|_| true)
1520            .map_err(|e| StateError::StorageError {
1521                backend_type: "postgres".to_string(),
1522                details: format!("Health check failed: {}", e),
1523            })
1524    }
1525
1526    /// Close all connections gracefully
1527    pub async fn close(&self) {
1528        info!("Closing PostgreSQL connections");
1529        self.stop_health_check_task();
1530        self.primary_pool.close().await;
1531
1532        if let Some(replica_manager) = &self.replica_manager {
1533            for replica in &replica_manager.replicas {
1534                replica.pool.close().await;
1535            }
1536        }
1537    }
1538}
1539
1540// ============================================================================
1541// StateBackend Trait Implementation
1542// ============================================================================
1543
1544#[async_trait]
1545impl StateBackend for EnhancedPostgresBackend {
1546    async fn get(&self, key: &[u8]) -> StateResult<Option<Vec<u8>>> {
1547        trace!("GET: key_len={}", key.len());
1548
1549        let pool = self.get_pool_for_read().await;
1550
1551        let result = sqlx::query(
1552            "SELECT value FROM state_entries
1553             WHERE key = $1
1554             AND (expires_at IS NULL OR expires_at > NOW())"
1555        )
1556        .bind(key)
1557        .fetch_optional(pool)
1558        .await
1559        .map_err(|e| StateError::StorageError {
1560            backend_type: "postgres".to_string(),
1561            details: format!("Get failed: {}", e),
1562        })?;
1563
1564        self.stats.get_count.fetch_add(1, Ordering::Relaxed);
1565
1566        if let Some(row) = result {
1567            let value: Vec<u8> = row.try_get("value").map_err(|e| StateError::StorageError {
1568                backend_type: "postgres".to_string(),
1569                details: format!("Failed to extract value: {}", e),
1570            })?;
1571
1572            self.stats.bytes_read.fetch_add(value.len() as u64, Ordering::Relaxed);
1573            Ok(Some(value))
1574        } else {
1575            Ok(None)
1576        }
1577    }
1578
1579    async fn put(&self, key: &[u8], value: &[u8]) -> StateResult<()> {
1580        trace!("PUT: key_len={}, value_len={}", key.len(), value.len());
1581
1582        let result = sqlx::query(
1583            "INSERT INTO state_entries (key, value, updated_at)
1584             VALUES ($1, $2, NOW())
1585             ON CONFLICT (key) DO UPDATE
1586             SET value = EXCLUDED.value, updated_at = NOW()"
1587        )
1588        .bind(key)
1589        .bind(value)
1590        .execute(&self.primary_pool)
1591        .await
1592        .map_err(|e| StateError::StorageError {
1593            backend_type: "postgres".to_string(),
1594            details: format!("Put failed: {}", e),
1595        })?;
1596
1597        self.stats.put_count.fetch_add(1, Ordering::Relaxed);
1598        self.stats.bytes_written.fetch_add(
1599            (key.len() + value.len()) as u64,
1600            Ordering::Relaxed,
1601        );
1602
1603        if result.rows_affected() > 0 {
1604            self.stats.rows_updated.fetch_add(1, Ordering::Relaxed);
1605        } else {
1606            self.stats.rows_inserted.fetch_add(1, Ordering::Relaxed);
1607        }
1608
1609        Ok(())
1610    }
1611
1612    async fn delete(&self, key: &[u8]) -> StateResult<()> {
1613        trace!("DELETE: key_len={}", key.len());
1614
1615        let result = sqlx::query("DELETE FROM state_entries WHERE key = $1")
1616            .bind(key)
1617            .execute(&self.primary_pool)
1618            .await
1619            .map_err(|e| StateError::StorageError {
1620                backend_type: "postgres".to_string(),
1621                details: format!("Delete failed: {}", e),
1622            })?;
1623
1624        self.stats.delete_count.fetch_add(1, Ordering::Relaxed);
1625        self.stats.rows_deleted.fetch_add(result.rows_affected(), Ordering::Relaxed);
1626
1627        Ok(())
1628    }
1629
1630    async fn list_keys(&self, prefix: &[u8]) -> StateResult<Vec<Vec<u8>>> {
1631        trace!("LIST_KEYS: prefix_len={}", prefix.len());
1632
1633        let pool = self.get_pool_for_read().await;
1634
1635        let rows = if prefix.is_empty() {
1636            sqlx::query("SELECT key FROM state_entries WHERE expires_at IS NULL OR expires_at > NOW()")
1637                .fetch_all(pool)
1638                .await
1639        } else {
1640            sqlx::query(
1641                "SELECT key FROM state_entries
1642                 WHERE key >= $1 AND key < $2
1643                 AND (expires_at IS NULL OR expires_at > NOW())"
1644            )
1645            .bind(prefix)
1646            .bind(Self::next_prefix(prefix))
1647            .fetch_all(pool)
1648            .await
1649        }
1650        .map_err(|e| StateError::StorageError {
1651            backend_type: "postgres".to_string(),
1652            details: format!("List keys failed: {}", e),
1653        })?;
1654
1655        let keys = rows
1656            .into_iter()
1657            .filter_map(|row| row.try_get::<Vec<u8>, _>("key").ok())
1658            .collect();
1659
1660        Ok(keys)
1661    }
1662
1663    async fn clear(&self) -> StateResult<()> {
1664        debug!("CLEAR: truncating table");
1665
1666        sqlx::query("TRUNCATE TABLE state_entries")
1667            .execute(&self.primary_pool)
1668            .await
1669            .map_err(|e| StateError::StorageError {
1670                backend_type: "postgres".to_string(),
1671                details: format!("Clear failed: {}", e),
1672            })?;
1673
1674        Ok(())
1675    }
1676
1677    async fn count(&self) -> StateResult<usize> {
1678        let pool = self.get_pool_for_read().await;
1679
1680        let row = sqlx::query(
1681            "SELECT COUNT(*) as count FROM state_entries
1682             WHERE expires_at IS NULL OR expires_at > NOW()"
1683        )
1684        .fetch_one(pool)
1685        .await
1686        .map_err(|e| StateError::StorageError {
1687            backend_type: "postgres".to_string(),
1688            details: format!("Count failed: {}", e),
1689        })?;
1690
1691        let count: i64 = row.try_get("count").map_err(|e| StateError::StorageError {
1692            backend_type: "postgres".to_string(),
1693            details: format!("Failed to extract count: {}", e),
1694        })?;
1695
1696        Ok(count as usize)
1697    }
1698
1699    async fn contains(&self, key: &[u8]) -> StateResult<bool> {
1700        let pool = self.get_pool_for_read().await;
1701
1702        let row = sqlx::query(
1703            "SELECT EXISTS(
1704                SELECT 1 FROM state_entries
1705                WHERE key = $1
1706                AND (expires_at IS NULL OR expires_at > NOW())
1707            ) as exists"
1708        )
1709        .bind(key)
1710        .fetch_one(pool)
1711        .await
1712        .map_err(|e| StateError::StorageError {
1713            backend_type: "postgres".to_string(),
1714            details: format!("Contains check failed: {}", e),
1715        })?;
1716
1717        let exists: bool = row.try_get("exists").unwrap_or(false);
1718        Ok(exists)
1719    }
1720}
1721
1722// ============================================================================
1723// Supporting Data Structures
1724// ============================================================================
1725
1726#[derive(Debug, Clone, Serialize, Deserialize)]
1727pub struct TableStatistics {
1728    pub total_rows: i64,
1729    pub active_rows: i64,
1730    pub expired_rows: i64,
1731    pub total_size: i64,
1732    pub table_size: i64,
1733    pub indexes_size: i64,
1734    pub toast_size: i64,
1735}
1736
1737#[derive(Debug, Clone, Serialize, Deserialize)]
1738pub struct ReplicationStatus {
1739    pub is_replica: bool,
1740    pub lag_ms: i64,
1741    pub replicas_connected: usize,
1742}
1743
1744#[derive(Debug, Clone, Serialize, Deserialize)]
1745pub struct PoolStatistics {
1746    pub size: u64,
1747    pub idle: u64,
1748    pub active: u64,
1749    pub max_size: u64,
1750}
1751
1752// ============================================================================
1753// Distributed Lock Guard
1754// ============================================================================
1755
1756/// PostgreSQL advisory lock guard
1757pub struct PostgresLockGuard {
1758    lock_id: i64,
1759    backend: EnhancedPostgresBackend,
1760    released: Arc<AtomicBool>,
1761}
1762
1763impl PostgresLockGuard {
1764    pub async fn new(
1765        backend: EnhancedPostgresBackend,
1766        lock_key: &str,
1767        timeout: Duration,
1768    ) -> StateResult<Option<Self>> {
1769        // Convert key to lock ID
1770        use sha2::{Digest, Sha256};
1771        let mut hasher = Sha256::new();
1772        hasher.update(lock_key.as_bytes());
1773        let result = hasher.finalize();
1774        let lock_id = i64::from_le_bytes(result[0..8].try_into().unwrap());
1775
1776        // Try to acquire lock with timeout
1777        let deadline = Instant::now() + timeout;
1778        while Instant::now() < deadline {
1779            if backend.acquire_advisory_lock(lock_id).await? {
1780                return Ok(Some(Self {
1781                    lock_id,
1782                    backend,
1783                    released: Arc::new(AtomicBool::new(false)),
1784                }));
1785            }
1786            tokio::time::sleep(Duration::from_millis(100)).await;
1787        }
1788
1789        Ok(None)
1790    }
1791
1792    pub async fn release(self) -> StateResult<()> {
1793        if !self.released.swap(true, Ordering::Relaxed) {
1794            self.backend.release_advisory_lock(self.lock_id).await?;
1795        }
1796        Ok(())
1797    }
1798}
1799
1800impl Drop for PostgresLockGuard {
1801    fn drop(&mut self) {
1802        if !self.released.swap(true, Ordering::Relaxed) {
1803            let lock_id = self.lock_id;
1804            let backend = self.backend.clone_handle();
1805
1806            // Best effort release in background
1807            tokio::spawn(async move {
1808                if let Err(e) = backend.release_advisory_lock(lock_id).await {
1809                    warn!("Failed to release lock in drop: {}", e);
1810                }
1811            });
1812        }
1813    }
1814}
1815
1816// ============================================================================
1817// Listen/Notify Support
1818// ============================================================================
1819
1820pub struct PostgresListener {
1821    listener: sqlx::postgres::PgListener,
1822}
1823
1824impl PostgresListener {
1825    pub async fn new(database_url: &str, channel: &str) -> StateResult<Self> {
1826        let mut listener = sqlx::postgres::PgListener::connect(database_url)
1827            .await
1828            .map_err(|e| StateError::StorageError {
1829                backend_type: "postgres".to_string(),
1830                details: format!("Failed to create listener: {}", e),
1831            })?;
1832
1833        listener.listen(channel).await.map_err(|e| StateError::StorageError {
1834            backend_type: "postgres".to_string(),
1835            details: format!("Failed to listen on channel: {}", e),
1836        })?;
1837
1838        Ok(Self { listener })
1839    }
1840
1841    pub async fn recv(&mut self) -> StateResult<String> {
1842        let notification = self.listener.recv().await.map_err(|e| StateError::StorageError {
1843            backend_type: "postgres".to_string(),
1844            details: format!("Failed to receive notification: {}", e),
1845        })?;
1846
1847        Ok(notification.payload().to_string())
1848    }
1849
1850    pub async fn try_recv(&mut self) -> StateResult<Option<String>> {
1851        match self.listener.try_recv().await {
1852            Ok(Some(notification)) => Ok(Some(notification.payload().to_string())),
1853            Ok(None) => Ok(None),
1854            Err(e) => Err(StateError::StorageError {
1855                backend_type: "postgres".to_string(),
1856                details: format!("Failed to try_recv notification: {}", e),
1857            }),
1858        }
1859    }
1860}