rexis-rag 0.1.0

Rexis RAG - High-performance Retrieval-Augmented Generation framework with memory-first agents, vector search, and LLM integration
Documentation
//! # Database Storage Implementation with Toasty
//!
//! Persistent storage backend using Toasty ORM for database operations.
//!
//! ## ⚠️ EXPERIMENTAL - NOT PRODUCTION READY
//!
//! **Current Status**: This implementation uses in-memory storage as a fallback.
//!
//! **Why**: Toasty ORM (v0.1.1) is in early incubation stage:
//! - Not production-ready (as stated by Tokio team)
//! - API is unstable and may change
//! - Limited documentation
//! - Schema definition requires procedural macros (`#[toasty::model]`)
//! - Proper integration requires:
//!   1. Adding `toasty` dependency with derive feature
//!   2. Defining models with `#[derive(Model)]` and `#[toasty::model]`
//!   3. Setting up database connections with proper error handling
//!   4. Implementing migrations
//!
//! **Recommendation**: For production use, consider:
//! - Use `InMemoryStorage` for development/testing
//! - Use mature ORMs like `sqlx` or `diesel` for production
//! - Wait for Toasty to reach stable release (v1.0+)
//!
//! **Future**: Once Toasty matures, this implementation will be updated with:
//! - Proper model definitions using `#[toasty::model]`
//! - Full CRUD operations
//! - Database migrations
//! - Multi-database support (PostgreSQL, MySQL, SQLite, DynamoDB)
//!
//! ## Current Implementation
//!
//! Currently uses `InMemoryStorage` as a fallback to provide a working interface.
//! All data is stored in memory and will be lost on restart.

#[cfg(feature = "database")]
use super::memory::{Memory, MemoryQuery, MemoryStats, MemoryValue};
#[cfg(feature = "database")]
use crate::RragResult;
#[cfg(feature = "database")]
use async_trait::async_trait;

#[cfg(feature = "database")]
/// Database storage configuration
#[derive(Debug, Clone)]
pub struct DatabaseConfig {
    /// Database connection string
    pub connection_string: String,

    /// Maximum number of connections in the pool
    pub max_connections: u32,

    /// Connection timeout in seconds
    pub connection_timeout_secs: u64,

    /// Enable query logging
    pub enable_query_logging: bool,
}

#[cfg(feature = "database")]
impl Default for DatabaseConfig {
    fn default() -> Self {
        Self {
            connection_string: "sqlite::memory:".to_string(),
            max_connections: 10,
            connection_timeout_secs: 30,
            enable_query_logging: false,
        }
    }
}

#[cfg(feature = "database")]
/// Database storage implementation using Toasty
///
/// **PLACEHOLDER**: This implementation currently uses in-memory storage as a fallback.
/// Full Toasty integration requires compiling the schema and implementing proper migrations.
pub struct DatabaseStorage {
    /// Configuration
    config: DatabaseConfig,

    /// Fallback in-memory storage until Toasty is fully integrated
    fallback: super::in_memory::InMemoryStorage,
}

#[cfg(feature = "database")]
impl DatabaseStorage {
    /// Create a new database storage with default configuration
    pub async fn new() -> RragResult<Self> {
        Self::with_config(DatabaseConfig::default()).await
    }

    /// Create a new database storage with custom configuration
    ///
    /// **Note**: Currently uses in-memory fallback until Toasty is fully integrated
    pub async fn with_config(config: DatabaseConfig) -> RragResult<Self> {
        // TODO: Initialize Toasty database when schema is compiled
        // For now, use in-memory storage as fallback
        tracing::warn!(
            "DatabaseStorage is using in-memory fallback. Full Toasty integration pending."
        );

        Ok(Self {
            config,
            fallback: super::in_memory::InMemoryStorage::new(),
        })
    }
}

#[cfg(feature = "database")]
#[async_trait]
impl Memory for DatabaseStorage {
    fn backend_name(&self) -> &str {
        "database_fallback"
    }

    async fn set(&self, key: &str, value: MemoryValue) -> RragResult<()> {
        self.fallback.set(key, value).await
    }

    async fn get(&self, key: &str) -> RragResult<Option<MemoryValue>> {
        self.fallback.get(key).await
    }

    async fn delete(&self, key: &str) -> RragResult<bool> {
        self.fallback.delete(key).await
    }

    async fn exists(&self, key: &str) -> RragResult<bool> {
        self.fallback.exists(key).await
    }

    async fn keys(&self, query: &MemoryQuery) -> RragResult<Vec<String>> {
        self.fallback.keys(query).await
    }

    async fn mget(&self, keys: &[String]) -> RragResult<Vec<Option<MemoryValue>>> {
        self.fallback.mget(keys).await
    }

    async fn mset(&self, pairs: &[(String, MemoryValue)]) -> RragResult<()> {
        self.fallback.mset(pairs).await
    }

    async fn mdelete(&self, keys: &[String]) -> RragResult<usize> {
        self.fallback.mdelete(keys).await
    }

    async fn clear(&self, namespace: Option<&str>) -> RragResult<()> {
        self.fallback.clear(namespace).await
    }

    async fn count(&self, namespace: Option<&str>) -> RragResult<usize> {
        self.fallback.count(namespace).await
    }

    async fn health_check(&self) -> RragResult<bool> {
        self.fallback.health_check().await
    }

    async fn stats(&self) -> RragResult<MemoryStats> {
        let mut stats = self.fallback.stats().await?;
        stats.backend_type = format!("database_fallback ({})", self.config.connection_string);
        stats.extra.insert(
            "note".to_string(),
            serde_json::json!("Using in-memory fallback until Toasty is fully integrated"),
        );
        Ok(stats)
    }
}

// Placeholder for when database feature is not enabled
#[cfg(not(feature = "database"))]
pub struct DatabaseStorage;

#[cfg(not(feature = "database"))]
impl DatabaseStorage {
    pub async fn new() -> Result<Self, String> {
        Err("Database feature not enabled. Enable with --features database".to_string())
    }
}