Skip to main content

do_memory_storage_turso/
lib.rs

1#![allow(clippy::expect_used)]
2// Intentional allows for memory-storage-turso
3#![allow(unsafe_code)] // Intentional unsafe for performance in connection pooling
4#![allow(clippy::unwrap_used)] // Intentional .unwrap() on mutex locks
5#![allow(invalid_value)] // Intentional zero-initialization in connection pool
6#![allow(dead_code)] // Public API methods not used internally
7// Additional allows for complex code patterns
8#![allow(clippy::excessive_nesting)] // Complex control flow in cache logic
9#![allow(unused_mut)] // Variables used conditionally
10#![allow(unused_assignments)] // Variables assigned in loops
11#![allow(clippy::derivable_impls)] // Prefer explicit impls for clarity
12#![allow(clippy::should_implement_trait)] // Custom default methods
13#![allow(clippy::unnecessary_map_or)] // Explicit is better than implicit
14#![allow(clippy::useless_asref)] // Clarity in type conversions
15// Cast-related: necessary for SQL storage metrics and statistics
16#![allow(clippy::cast_precision_loss)]
17#![allow(clippy::cast_possible_truncation)]
18#![allow(clippy::cast_sign_loss)]
19#![allow(clippy::cast_possible_wrap)]
20#![allow(clippy::cast_lossless)]
21// Documentation/pedantic: would require extensive rework
22#![allow(clippy::cognitive_complexity)]
23#![allow(clippy::missing_errors_doc)]
24#![allow(clippy::doc_markdown)]
25#![allow(clippy::must_use_candidate)]
26#![allow(clippy::return_self_not_must_use)]
27#![allow(clippy::map_unwrap_or)]
28#![allow(clippy::redundant_closure)]
29#![allow(clippy::redundant_closure_for_method_calls)]
30#![allow(clippy::match_same_arms)]
31#![allow(clippy::uninlined_format_args)]
32// Format args: inlining not required for error message clarity
33#![allow(clippy::needless_pass_by_value)]
34#![allow(clippy::unused_self)]
35#![allow(clippy::unused_async)]
36#![allow(clippy::format_in_format_args)]
37#![allow(clippy::to_string_in_format_args)]
38#![allow(clippy::unreadable_literal)]
39#![allow(clippy::struct_excessive_bools)]
40#![allow(clippy::panic)]
41#![allow(clippy::float_cmp)]
42#![allow(clippy::items_after_statements)]
43#![allow(clippy::wildcard_imports)]
44#![allow(clippy::used_underscore_binding)]
45#![allow(clippy::used_underscore_items)]
46#![allow(clippy::ptr_as_ptr)]
47#![allow(clippy::ptr_cast_constness)]
48#![allow(clippy::format_push_string)]
49#![allow(clippy::implicit_clone)]
50#![allow(clippy::missing_fields_in_debug)]
51#![allow(clippy::needless_raw_string_hashes)]
52#![allow(clippy::default_trait_access)]
53#![allow(clippy::missing_panics_doc)]
54#![allow(clippy::no_effect_underscore_binding)]
55#![allow(rust_2024_compatibility)]
56#![allow(tail_expr_drop_order)]
57#![allow(clippy::unnecessary_wraps)]
58#![allow(clippy::unchecked_time_subtraction)]
59#![allow(clippy::semicolon_if_nothing_returned)]
60#![allow(missing_docs)]
61#![allow(unknown_lints)]
62#![allow(clippy::unknown_lints)]
63
64//! # Memory Storage - Turso
65//!
66//! Turso/libSQL storage backend for durable persistence of episodes and patterns.
67//!
68//! This crate provides:
69//! - Connection management for Turso databases
70//! - SQL schema creation and migration
71//! - CRUD operations for episodes, patterns, and heuristics
72//! - Query capabilities for analytical retrieval
73//! - Retry logic and circuit breaker pattern for resilience
74//!
75//! ## Example
76//!
77//! ```no_run
78//! use do_memory_storage_turso::TursoStorage;
79//!
80//! # async fn example() -> anyhow::Result<()> {
81//! let storage = TursoStorage::new("libsql://localhost:8080", "token").await?;
82//! storage.initialize_schema().await?;
83//! # Ok(())
84//! # }
85//! ```
86
87use do_memory_core::{Error, Result};
88
89// Cache module for performance optimization
90pub mod cache;
91pub mod pool;
92mod relationships;
93mod resilient;
94mod schema;
95#[cfg(test)]
96mod tests;
97
98#[cfg(feature = "hybrid_search")]
99mod fts5_schema;
100
101// Storage module - split into submodules for file size compliance
102pub mod storage;
103
104// Trait implementations - moved to separate module for file size compliance
105pub mod trait_impls;
106
107// Schema initialization - moved to separate module for file size compliance
108pub mod turso_config;
109
110// Prepared statement caching for query optimization
111pub mod prepared;
112
113// Performance metrics and export module
114pub mod metrics;
115
116// Compression module for network bandwidth reduction (40% target)
117#[cfg(feature = "compression")]
118pub mod compression;
119
120// Transport layer with compression support
121#[cfg(feature = "compression")]
122pub mod transport;
123
124// Lib implementation submodules - split for file size compliance
125mod lib_impls;
126
127// Re-export public types from lib_impls
128pub use lib_impls::TursoStorage;
129pub use lib_impls::{StorageMode, TursoConfig};
130
131/// Extension trait for `SelfLearningMemory` to provide convenience constructors
132/// using the Turso storage backend.
133#[async_trait::async_trait]
134pub trait SelfLearningMemoryExt {
135    /// Create a memory system with local SQLite storage.
136    async fn with_local_storage(
137        path: impl AsRef<std::path::Path> + Send,
138    ) -> Result<do_memory_core::memory::SelfLearningMemory>;
139    /// Create a memory system with in-memory SQLite storage.
140    async fn with_in_memory_storage() -> Result<do_memory_core::memory::SelfLearningMemory>;
141}
142
143#[async_trait::async_trait]
144impl SelfLearningMemoryExt for do_memory_core::memory::SelfLearningMemory {
145    async fn with_local_storage(
146        path: impl AsRef<std::path::Path> + Send,
147    ) -> Result<do_memory_core::memory::SelfLearningMemory> {
148        let storage = TursoStorage::new_local(path).await?;
149        storage.initialize_schema().await?;
150
151        let cache = std::sync::Arc::new(
152            do_memory_storage_redb::RedbStorage::new(std::path::Path::new(":memory:"))
153                .await
154                .map_err(|e| do_memory_core::Error::Storage(e.to_string()))?,
155        );
156        Ok(do_memory_core::memory::SelfLearningMemory::with_storage(
157            do_memory_core::MemoryConfig::default(),
158            std::sync::Arc::new(storage),
159            cache,
160        ))
161    }
162
163    async fn with_in_memory_storage() -> Result<do_memory_core::memory::SelfLearningMemory> {
164        let storage = TursoStorage::new_in_memory().await?;
165        storage.initialize_schema().await?;
166
167        let cache = std::sync::Arc::new(
168            do_memory_storage_redb::RedbStorage::new(std::path::Path::new(":memory:"))
169                .await
170                .map_err(|e| do_memory_core::Error::Storage(e.to_string()))?,
171        );
172        Ok(do_memory_core::memory::SelfLearningMemory::with_storage(
173            do_memory_core::MemoryConfig::default(),
174            std::sync::Arc::new(storage),
175            cache,
176        ))
177    }
178}
179
180// Cache exports
181pub use cache::query_cache::{AdvancedCacheStats, AdvancedQueryCache, QueryKey};
182pub use cache::{
183    AdaptiveTTLCache, CacheConfig, CacheEntry, CacheStats, CacheStatsSnapshot, CachedTursoStorage,
184    TTLConfig, TTLConfigError,
185};
186
187// Performance metrics exports
188pub use pool::{
189    AdaptiveConnectionPool, AdaptivePoolConfig, AdaptivePoolMetrics, AdaptivePooledConnection,
190};
191pub use pool::{
192    ConnectionPool, PoolConfig, PoolMetrics, PoolMetricsSnapshot, PoolStatistics, PooledConnection,
193};
194#[cfg(feature = "keepalive-pool")]
195pub use pool::{KeepAliveConfig, KeepAlivePool, KeepAliveStatistics};
196pub use prepared::{PreparedCacheConfig, PreparedCacheStats, PreparedStatementCache};
197pub use resilient::ResilientStorage;
198
199// Metrics export re-exports
200pub use metrics::{
201    ExportConfig, ExportFormat, ExportStats, ExportTarget, ExportedMetric, MetricType, MetricValue,
202    MetricsCollector, MetricsHttpServer, PrometheusExporter, TursoMetrics,
203};
204pub use storage::batch::episode_batch::BatchConfig;
205pub use storage::capacity::CapacityStatistics;
206pub use storage::episodes::EpisodeQuery;
207pub use storage::patterns::{PatternMetadata, PatternQuery};
208pub use trait_impls::StorageStatistics;
209
210// Compression exports (when compression feature is enabled)
211#[cfg(feature = "compression")]
212pub use compression::{
213    CompressedPayload, CompressionAlgorithm, CompressionStatistics, compress, compress_embedding,
214    compress_json, decompress, decompress_embedding,
215};
216
217// Transport exports (when compression feature is enabled)
218#[cfg(feature = "compression")]
219pub use transport::{
220    CompressedTransport, Transport, TransportCompressionConfig, TransportCompressionError,
221    TransportCompressionStats, TransportMetadata, TransportResponse,
222};
223
224// Include constructor implementations from lib_impls modules
225// These are automatically included via `mod lib_impls` declaration
226// The impl blocks are in:
227// - lib_impls::constructors_basic (new, from_database, with_config)
228// - lib_impls::constructors_pool (new_with_pool_config, new_with_keepalive)
229// - lib_impls::constructors_adaptive (new_with_adaptive_pool)
230//
231// Helper methods are in:
232// - lib_impls::helpers (get_connection, get_count, etc.)
233
234#[cfg(test)]
235mod ext_tests {
236    use super::*;
237    use do_memory_core::memory::SelfLearningMemory;
238
239    #[tokio::test]
240    async fn test_with_in_memory_storage() {
241        let memory = SelfLearningMemory::with_in_memory_storage().await.unwrap();
242        let (primary, cache) = memory.storage_backends();
243        assert!(primary.is_some());
244        assert!(cache.is_some());
245    }
246
247    #[tokio::test]
248    #[ignore = "flaky in full suite: libsql concurrent connection race (ADR-027); passes in isolation"]
249    async fn test_with_local_storage() {
250        let dir = tempfile::tempdir().unwrap();
251        let path = dir.path().join("test.db");
252        let memory = SelfLearningMemory::with_local_storage(&path).await.unwrap();
253        let (primary, cache) = memory.storage_backends();
254        assert!(primary.is_some());
255        assert!(cache.is_some());
256    }
257}