rexis_rag/storage/
database.rs

1//! # Database Storage Implementation with Toasty
2//!
3//! Persistent storage backend using Toasty ORM for database operations.
4//!
5//! ## ⚠️ EXPERIMENTAL - NOT PRODUCTION READY
6//!
7//! **Current Status**: This implementation uses in-memory storage as a fallback.
8//!
9//! **Why**: Toasty ORM (v0.1.1) is in early incubation stage:
10//! - Not production-ready (as stated by Tokio team)
11//! - API is unstable and may change
12//! - Limited documentation
13//! - Schema definition requires procedural macros (`#[toasty::model]`)
14//! - Proper integration requires:
15//!   1. Adding `toasty` dependency with derive feature
16//!   2. Defining models with `#[derive(Model)]` and `#[toasty::model]`
17//!   3. Setting up database connections with proper error handling
18//!   4. Implementing migrations
19//!
20//! **Recommendation**: For production use, consider:
21//! - Use `InMemoryStorage` for development/testing
22//! - Use mature ORMs like `sqlx` or `diesel` for production
23//! - Wait for Toasty to reach stable release (v1.0+)
24//!
25//! **Future**: Once Toasty matures, this implementation will be updated with:
26//! - Proper model definitions using `#[toasty::model]`
27//! - Full CRUD operations
28//! - Database migrations
29//! - Multi-database support (PostgreSQL, MySQL, SQLite, DynamoDB)
30//!
31//! ## Current Implementation
32//!
33//! Currently uses `InMemoryStorage` as a fallback to provide a working interface.
34//! All data is stored in memory and will be lost on restart.
35
36#[cfg(feature = "database")]
37use super::memory::{Memory, MemoryQuery, MemoryStats, MemoryValue};
38#[cfg(feature = "database")]
39use crate::RragResult;
40#[cfg(feature = "database")]
41use async_trait::async_trait;
42
43#[cfg(feature = "database")]
44/// Database storage configuration
45#[derive(Debug, Clone)]
46pub struct DatabaseConfig {
47    /// Database connection string
48    pub connection_string: String,
49
50    /// Maximum number of connections in the pool
51    pub max_connections: u32,
52
53    /// Connection timeout in seconds
54    pub connection_timeout_secs: u64,
55
56    /// Enable query logging
57    pub enable_query_logging: bool,
58}
59
60#[cfg(feature = "database")]
61impl Default for DatabaseConfig {
62    fn default() -> Self {
63        Self {
64            connection_string: "sqlite::memory:".to_string(),
65            max_connections: 10,
66            connection_timeout_secs: 30,
67            enable_query_logging: false,
68        }
69    }
70}
71
72#[cfg(feature = "database")]
73/// Database storage implementation using Toasty
74///
75/// **PLACEHOLDER**: This implementation currently uses in-memory storage as a fallback.
76/// Full Toasty integration requires compiling the schema and implementing proper migrations.
77pub struct DatabaseStorage {
78    /// Configuration
79    config: DatabaseConfig,
80
81    /// Fallback in-memory storage until Toasty is fully integrated
82    fallback: super::in_memory::InMemoryStorage,
83}
84
85#[cfg(feature = "database")]
86impl DatabaseStorage {
87    /// Create a new database storage with default configuration
88    pub async fn new() -> RragResult<Self> {
89        Self::with_config(DatabaseConfig::default()).await
90    }
91
92    /// Create a new database storage with custom configuration
93    ///
94    /// **Note**: Currently uses in-memory fallback until Toasty is fully integrated
95    pub async fn with_config(config: DatabaseConfig) -> RragResult<Self> {
96        // TODO: Initialize Toasty database when schema is compiled
97        // For now, use in-memory storage as fallback
98        tracing::warn!(
99            "DatabaseStorage is using in-memory fallback. Full Toasty integration pending."
100        );
101
102        Ok(Self {
103            config,
104            fallback: super::in_memory::InMemoryStorage::new(),
105        })
106    }
107}
108
109#[cfg(feature = "database")]
110#[async_trait]
111impl Memory for DatabaseStorage {
112    fn backend_name(&self) -> &str {
113        "database_fallback"
114    }
115
116    async fn set(&self, key: &str, value: MemoryValue) -> RragResult<()> {
117        self.fallback.set(key, value).await
118    }
119
120    async fn get(&self, key: &str) -> RragResult<Option<MemoryValue>> {
121        self.fallback.get(key).await
122    }
123
124    async fn delete(&self, key: &str) -> RragResult<bool> {
125        self.fallback.delete(key).await
126    }
127
128    async fn exists(&self, key: &str) -> RragResult<bool> {
129        self.fallback.exists(key).await
130    }
131
132    async fn keys(&self, query: &MemoryQuery) -> RragResult<Vec<String>> {
133        self.fallback.keys(query).await
134    }
135
136    async fn mget(&self, keys: &[String]) -> RragResult<Vec<Option<MemoryValue>>> {
137        self.fallback.mget(keys).await
138    }
139
140    async fn mset(&self, pairs: &[(String, MemoryValue)]) -> RragResult<()> {
141        self.fallback.mset(pairs).await
142    }
143
144    async fn mdelete(&self, keys: &[String]) -> RragResult<usize> {
145        self.fallback.mdelete(keys).await
146    }
147
148    async fn clear(&self, namespace: Option<&str>) -> RragResult<()> {
149        self.fallback.clear(namespace).await
150    }
151
152    async fn count(&self, namespace: Option<&str>) -> RragResult<usize> {
153        self.fallback.count(namespace).await
154    }
155
156    async fn health_check(&self) -> RragResult<bool> {
157        self.fallback.health_check().await
158    }
159
160    async fn stats(&self) -> RragResult<MemoryStats> {
161        let mut stats = self.fallback.stats().await?;
162        stats.backend_type = format!("database_fallback ({})", self.config.connection_string);
163        stats.extra.insert(
164            "note".to_string(),
165            serde_json::json!("Using in-memory fallback until Toasty is fully integrated"),
166        );
167        Ok(stats)
168    }
169}
170
171// Placeholder for when database feature is not enabled
172#[cfg(not(feature = "database"))]
173pub struct DatabaseStorage;
174
175#[cfg(not(feature = "database"))]
176impl DatabaseStorage {
177    pub async fn new() -> Result<Self, String> {
178        Err("Database feature not enabled. Enable with --features database".to_string())
179    }
180}