Skip to main content

do_memory_storage_redb/
relationships.rs

1//! Redb cache layer for episode relationships.
2
3use crate::{RELATIONSHIPS_TABLE, RedbStorage, Result};
4#[allow(unused_imports)] // False positive - import is used in error mapping
5use do_memory_core::Error;
6use do_memory_core::episode::{Direction, EpisodeRelationship, RelationshipType};
7use redb::{ReadableDatabase, ReadableTable, ReadableTableMetadata};
8use tracing::debug;
9use uuid::Uuid;
10
11impl RedbStorage {
12    // StorageBackend trait implementations
13
14    /// Store a relationship (StorageBackend trait)
15    pub async fn store_relationship(&self, relationship: &EpisodeRelationship) -> Result<()> {
16        self.cache_relationship(relationship)
17    }
18
19    /// Remove a relationship (StorageBackend trait)
20    pub async fn remove_relationship(&self, relationship_id: Uuid) -> Result<()> {
21        self.remove_cached_relationship(relationship_id)
22    }
23
24    /// Get relationships (StorageBackend trait)
25    pub async fn get_relationships(
26        &self,
27        episode_id: Uuid,
28        direction: Direction,
29    ) -> Result<Vec<EpisodeRelationship>> {
30        self.get_cached_relationships(episode_id, direction)
31    }
32
33    /// Check if relationship exists (StorageBackend trait)
34    pub async fn relationship_exists(
35        &self,
36        from_episode_id: Uuid,
37        to_episode_id: Uuid,
38        relationship_type: RelationshipType,
39    ) -> Result<bool> {
40        let relationships = self.get_cached_relationships(from_episode_id, Direction::Outgoing)?;
41        Ok(relationships
42            .iter()
43            .any(|r| r.to_episode_id == to_episode_id && r.relationship_type == relationship_type))
44    }
45
46    // Original redb-specific methods
47
48    /// Cache a relationship
49    pub fn cache_relationship(&self, relationship: &EpisodeRelationship) -> Result<()> {
50        let write_txn = self
51            .db
52            .begin_write()
53            .map_err(|e| do_memory_core::Error::Storage(format!("Begin write failed: {}", e)))?;
54        {
55            let mut table = write_txn
56                .open_table(RELATIONSHIPS_TABLE)
57                .map_err(|e| do_memory_core::Error::Storage(format!("Open table failed: {}", e)))?;
58            let key = relationship.id.to_string();
59            let value = postcard::to_allocvec(relationship).map_err(|e| {
60                do_memory_core::Error::Storage(format!("Serialization error: {}", e))
61            })?;
62            table
63                .insert(key.as_str(), value.as_slice())
64                .map_err(|e| do_memory_core::Error::Storage(format!("Insert failed: {}", e)))?;
65        }
66        write_txn
67            .commit()
68            .map_err(|e| do_memory_core::Error::Storage(format!("Commit failed: {}", e)))?;
69
70        debug!("Cached relationship {} in redb", relationship.id);
71        Ok(())
72    }
73
74    /// Get a cached relationship by ID
75    pub fn get_cached_relationship(
76        &self,
77        relationship_id: Uuid,
78    ) -> Result<Option<EpisodeRelationship>> {
79        let read_txn = self
80            .db
81            .begin_read()
82            .map_err(|e| do_memory_core::Error::Storage(format!("Begin read failed: {}", e)))?;
83        let table = read_txn
84            .open_table(RELATIONSHIPS_TABLE)
85            .map_err(|e| do_memory_core::Error::Storage(format!("Open table failed: {}", e)))?;
86        let key = relationship_id.to_string();
87
88        match table
89            .get(key.as_str())
90            .map_err(|e| do_memory_core::Error::Storage(format!("Get failed: {}", e)))?
91        {
92            Some(value) => {
93                let bytes = value.value();
94                let relationship: EpisodeRelationship =
95                    postcard::from_bytes(bytes).map_err(|e| {
96                        do_memory_core::Error::Storage(format!("Deserialization error: {}", e))
97                    })?;
98                Ok(Some(relationship))
99            }
100            None => Ok(None),
101        }
102    }
103
104    /// Remove a relationship from cache
105    pub fn remove_cached_relationship(&self, relationship_id: Uuid) -> Result<()> {
106        let write_txn = self
107            .db
108            .begin_write()
109            .map_err(|e| do_memory_core::Error::Storage(format!("Begin write failed: {}", e)))?;
110        {
111            let mut table = write_txn
112                .open_table(RELATIONSHIPS_TABLE)
113                .map_err(|e| do_memory_core::Error::Storage(format!("Open table failed: {}", e)))?;
114            let key = relationship_id.to_string();
115            table
116                .remove(key.as_str())
117                .map_err(|e| do_memory_core::Error::Storage(format!("Remove failed: {}", e)))?;
118        }
119        write_txn
120            .commit()
121            .map_err(|e| do_memory_core::Error::Storage(format!("Commit failed: {}", e)))?;
122
123        debug!("Removed relationship {} from cache", relationship_id);
124        Ok(())
125    }
126
127    /// Get all cached relationships for an episode
128    pub fn get_cached_relationships(
129        &self,
130        episode_id: Uuid,
131        direction: Direction,
132    ) -> Result<Vec<EpisodeRelationship>> {
133        let read_txn = self
134            .db
135            .begin_read()
136            .map_err(|e| do_memory_core::Error::Storage(format!("Begin read failed: {}", e)))?;
137        let table = read_txn
138            .open_table(RELATIONSHIPS_TABLE)
139            .map_err(|e| do_memory_core::Error::Storage(format!("Open table failed: {}", e)))?;
140
141        let mut relationships = Vec::new();
142        let iter = table.iter().map_err(|e| {
143            do_memory_core::Error::Storage(format!("Iterator creation failed: {}", e))
144        })?;
145
146        for item in iter {
147            let (_, value) = item.map_err(|e| {
148                do_memory_core::Error::Storage(format!("Iterator next failed: {}", e))
149            })?;
150            let bytes = value.value();
151            let relationship: EpisodeRelationship = postcard::from_bytes(bytes).map_err(|e| {
152                do_memory_core::Error::Storage(format!("Deserialization error: {}", e))
153            })?;
154
155            let matches = match direction {
156                Direction::Outgoing => relationship.from_episode_id == episode_id,
157                Direction::Incoming => relationship.to_episode_id == episode_id,
158                Direction::Both => {
159                    relationship.from_episode_id == episode_id
160                        || relationship.to_episode_id == episode_id
161                }
162            };
163
164            if matches {
165                relationships.push(relationship);
166            }
167        }
168
169        debug!(
170            "Found {} cached relationships for episode {} (direction: {:?})",
171            relationships.len(),
172            episode_id,
173            direction
174        );
175
176        Ok(relationships)
177    }
178
179    /// Clear all cached relationships
180    pub fn clear_relationships_cache(&self) -> Result<()> {
181        let write_txn = self
182            .db
183            .begin_write()
184            .map_err(|e| do_memory_core::Error::Storage(format!("Begin write failed: {}", e)))?;
185        {
186            let mut table = write_txn
187                .open_table(RELATIONSHIPS_TABLE)
188                .map_err(|e| do_memory_core::Error::Storage(format!("Open table failed: {}", e)))?;
189            // Remove all entries
190            let keys: Vec<String> = {
191                let iter = table.iter().map_err(|e| {
192                    do_memory_core::Error::Storage(format!("Iterator creation failed: {}", e))
193                })?;
194                let mut keys = Vec::new();
195                for item in iter {
196                    let (key, _) = item.map_err(|e| {
197                        do_memory_core::Error::Storage(format!("Iterator next failed: {}", e))
198                    })?;
199                    keys.push(key.value().to_string());
200                }
201                keys
202            };
203
204            for key in keys {
205                table
206                    .remove(key.as_str())
207                    .map_err(|e| do_memory_core::Error::Storage(format!("Remove failed: {}", e)))?;
208            }
209        }
210        write_txn
211            .commit()
212            .map_err(|e| do_memory_core::Error::Storage(format!("Commit failed: {}", e)))?;
213
214        debug!("Cleared all cached relationships");
215        Ok(())
216    }
217
218    /// Get count of cached relationships
219    pub fn count_cached_relationships(&self) -> Result<usize> {
220        let read_txn = self
221            .db
222            .begin_read()
223            .map_err(|e| do_memory_core::Error::Storage(format!("Begin read failed: {}", e)))?;
224        let table = read_txn
225            .open_table(RELATIONSHIPS_TABLE)
226            .map_err(|e| do_memory_core::Error::Storage(format!("Open table failed: {}", e)))?;
227        let count = table.len().map_err(|e| {
228            do_memory_core::Error::Storage(format!("Failed to get table length: {}", e))
229        })? as usize;
230        Ok(count)
231    }
232}
233
234#[cfg(test)]
235mod tests {
236    use super::*;
237    use do_memory_core::episode::RelationshipType;
238    use tempfile::TempDir;
239
240    async fn create_test_storage() -> (RedbStorage, TempDir) {
241        let dir = TempDir::new().expect("Failed to create temp dir");
242        let db_path = dir.path().join("test.redb");
243        let storage = RedbStorage::new(&db_path)
244            .await
245            .expect("Failed to create storage");
246        (storage, dir)
247    }
248
249    fn create_test_relationship(from_id: Uuid, to_id: Uuid) -> EpisodeRelationship {
250        EpisodeRelationship::with_reason(
251            from_id,
252            to_id,
253            RelationshipType::ParentChild,
254            "Test relationship".to_string(),
255        )
256    }
257
258    #[tokio::test]
259    async fn test_cache_and_get_relationship() {
260        let (storage, _dir) = create_test_storage().await;
261        let from_id = Uuid::new_v4();
262        let to_id = Uuid::new_v4();
263        let relationship = create_test_relationship(from_id, to_id);
264        let rel_id = relationship.id;
265
266        // Cache the relationship
267        storage
268            .cache_relationship(&relationship)
269            .expect("Failed to cache relationship");
270
271        // Retrieve it
272        let cached = storage
273            .get_cached_relationship(rel_id)
274            .expect("Failed to get relationship");
275        assert!(cached.is_some());
276        let cached_rel = cached.unwrap();
277        assert_eq!(cached_rel.id, rel_id);
278        assert_eq!(cached_rel.from_episode_id, from_id);
279        assert_eq!(cached_rel.to_episode_id, to_id);
280    }
281
282    #[tokio::test]
283    async fn test_remove_cached_relationship() {
284        let (storage, _dir) = create_test_storage().await;
285        let from_id = Uuid::new_v4();
286        let to_id = Uuid::new_v4();
287        let relationship = create_test_relationship(from_id, to_id);
288        let rel_id = relationship.id;
289
290        storage
291            .cache_relationship(&relationship)
292            .expect("Failed to cache relationship");
293
294        // Remove it
295        storage
296            .remove_cached_relationship(rel_id)
297            .expect("Failed to remove relationship");
298
299        // Verify it's gone
300        let cached = storage
301            .get_cached_relationship(rel_id)
302            .expect("Failed to get relationship");
303        assert!(cached.is_none());
304    }
305
306    #[tokio::test]
307    async fn test_get_cached_relationships_outgoing() {
308        let (storage, _dir) = create_test_storage().await;
309        let from_id = Uuid::new_v4();
310        let to_id1 = Uuid::new_v4();
311        let to_id2 = Uuid::new_v4();
312
313        let rel1 = create_test_relationship(from_id, to_id1);
314        let rel2 = create_test_relationship(from_id, to_id2);
315
316        storage.cache_relationship(&rel1).expect("Failed to cache");
317        storage.cache_relationship(&rel2).expect("Failed to cache");
318
319        let relationships = storage
320            .get_cached_relationships(from_id, Direction::Outgoing)
321            .expect("Failed to get relationships");
322
323        assert_eq!(relationships.len(), 2);
324        assert!(relationships.iter().any(|r| r.to_episode_id == to_id1));
325        assert!(relationships.iter().any(|r| r.to_episode_id == to_id2));
326    }
327
328    #[tokio::test]
329    async fn test_get_cached_relationships_incoming() {
330        let (storage, _dir) = create_test_storage().await;
331        let to_id = Uuid::new_v4();
332        let from_id1 = Uuid::new_v4();
333        let from_id2 = Uuid::new_v4();
334
335        let rel1 = create_test_relationship(from_id1, to_id);
336        let rel2 = create_test_relationship(from_id2, to_id);
337
338        storage.cache_relationship(&rel1).expect("Failed to cache");
339        storage.cache_relationship(&rel2).expect("Failed to cache");
340
341        let relationships = storage
342            .get_cached_relationships(to_id, Direction::Incoming)
343            .expect("Failed to get relationships");
344
345        assert_eq!(relationships.len(), 2);
346        assert!(relationships.iter().any(|r| r.from_episode_id == from_id1));
347        assert!(relationships.iter().any(|r| r.from_episode_id == from_id2));
348    }
349
350    #[tokio::test]
351    async fn test_clear_relationships_cache() {
352        let (storage, _dir) = create_test_storage().await;
353        let from_id = Uuid::new_v4();
354        let to_id = Uuid::new_v4();
355
356        for _ in 0..5 {
357            let rel = create_test_relationship(from_id, to_id);
358            storage.cache_relationship(&rel).expect("Failed to cache");
359        }
360
361        let count_before = storage
362            .count_cached_relationships()
363            .expect("Failed to count");
364        assert_eq!(count_before, 5);
365
366        storage
367            .clear_relationships_cache()
368            .expect("Failed to clear cache");
369
370        let count_after = storage
371            .count_cached_relationships()
372            .expect("Failed to count");
373        assert_eq!(count_after, 0);
374    }
375
376    #[tokio::test]
377    async fn test_count_cached_relationships() {
378        let (storage, _dir) = create_test_storage().await;
379
380        // Add at least one relationship to ensure table exists
381        let from_id = Uuid::new_v4();
382        let to_id = Uuid::new_v4();
383        let rel = create_test_relationship(from_id, to_id);
384        storage.cache_relationship(&rel).expect("Failed to cache");
385
386        let count_initial = storage
387            .count_cached_relationships()
388            .expect("Failed to count");
389        assert_eq!(count_initial, 1);
390
391        // Add 2 more relationships (total should be 3)
392        for i in 0..2 {
393            let from_id = Uuid::new_v4();
394            let to_id = Uuid::new_v4();
395            let rel = create_test_relationship(from_id, to_id);
396            storage.cache_relationship(&rel).expect("Failed to cache");
397
398            let count = storage
399                .count_cached_relationships()
400                .expect("Failed to count");
401            assert_eq!(count, i + 2); // +2 because we start at 1
402        }
403    }
404}