Skip to main content

ubiquity_database/
sqlite.rs

1//! SQLite database implementation
2
3use async_trait::async_trait;
4use chrono::{DateTime, Utc};
5use sqlx::{Row, SqlitePool};
6use std::sync::Arc;
7use ubiquity_core::{ConsciousnessState, ConsciousnessRipple, Task, TaskResult};
8use crate::{
9    Database, DatabaseConfig, DatabaseError, DatabaseResult,
10    HybridSearchQuery, HybridSearchResult, VectorSearchResult,
11};
12
13/// SQLite database implementation
14pub struct SqliteDatabase {
15    pool: Arc<SqlitePool>,
16    config: DatabaseConfig,
17}
18
19impl SqliteDatabase {
20    pub async fn new(config: DatabaseConfig) -> DatabaseResult<Self> {
21        let pool = SqlitePool::connect(&format!("sqlite:{}", config.sqlite.path.display())).await?;
22        
23        // Enable WAL mode if configured
24        if config.sqlite.wal_mode {
25            sqlx::query("PRAGMA journal_mode = WAL")
26                .execute(&pool)
27                .await?;
28        }
29        
30        Ok(Self {
31            pool: Arc::new(pool),
32            config,
33        })
34    }
35}
36
37#[async_trait]
38impl Database for SqliteDatabase {
39    async fn health_check(&self) -> DatabaseResult<()> {
40        sqlx::query("SELECT 1")
41            .fetch_one(&*self.pool)
42            .await?;
43        Ok(())
44    }
45    
46    async fn initialize(&self) -> DatabaseResult<()> {
47        // Create consciousness states table
48        sqlx::query(
49            "CREATE TABLE IF NOT EXISTS consciousness_states (
50                id TEXT PRIMARY KEY,
51                agent_id TEXT NOT NULL,
52                level REAL NOT NULL,
53                coherence REAL NOT NULL,
54                phase TEXT NOT NULL,
55                timestamp INTEGER NOT NULL,
56                breakthrough_detected BOOLEAN NOT NULL,
57                data JSON NOT NULL
58            )"
59        )
60        .execute(&*self.pool)
61        .await?;
62        
63        // Create consciousness ripples table
64        sqlx::query(
65            "CREATE TABLE IF NOT EXISTS consciousness_ripples (
66                id TEXT PRIMARY KEY,
67                origin TEXT NOT NULL,
68                ripple_type TEXT NOT NULL,
69                content JSON NOT NULL,
70                intensity REAL NOT NULL,
71                timestamp INTEGER NOT NULL
72            )"
73        )
74        .execute(&*self.pool)
75        .await?;
76        
77        // Create tasks table
78        sqlx::query(
79            "CREATE TABLE IF NOT EXISTS tasks (
80                id TEXT PRIMARY KEY,
81                task_type TEXT NOT NULL,
82                description TEXT NOT NULL,
83                requirements JSON NOT NULL,
84                priority INTEGER NOT NULL,
85                dependencies JSON NOT NULL,
86                consciousness_requirement REAL NOT NULL,
87                status TEXT DEFAULT 'pending',
88                created_at INTEGER NOT NULL,
89                updated_at INTEGER NOT NULL
90            )"
91        )
92        .execute(&*self.pool)
93        .await?;
94        
95        // Create task results table
96        sqlx::query(
97            "CREATE TABLE IF NOT EXISTS task_results (
98                task_id TEXT PRIMARY KEY,
99                success BOOLEAN NOT NULL,
100                output JSON NOT NULL,
101                consciousness_level REAL NOT NULL,
102                breakthrough BOOLEAN NOT NULL,
103                error TEXT,
104                completed_at INTEGER NOT NULL,
105                FOREIGN KEY (task_id) REFERENCES tasks(id)
106            )"
107        )
108        .execute(&*self.pool)
109        .await?;
110        
111        // Create embeddings table for vector search
112        sqlx::query(
113            "CREATE TABLE IF NOT EXISTS consciousness_embeddings (
114                id TEXT PRIMARY KEY,
115                agent_id TEXT NOT NULL,
116                timestamp INTEGER NOT NULL,
117                level REAL NOT NULL,
118                coherence REAL NOT NULL,
119                phase TEXT NOT NULL,
120                embedding BLOB NOT NULL,
121                text_representation TEXT NOT NULL
122            )"
123        )
124        .execute(&*self.pool)
125        .await?;
126        
127        // Create indices
128        sqlx::query("CREATE INDEX IF NOT EXISTS idx_consciousness_agent_time ON consciousness_states(agent_id, timestamp DESC)")
129            .execute(&*self.pool)
130            .await?;
131        
132        sqlx::query("CREATE INDEX IF NOT EXISTS idx_ripples_time ON consciousness_ripples(timestamp DESC)")
133            .execute(&*self.pool)
134            .await?;
135        
136        sqlx::query("CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status)")
137            .execute(&*self.pool)
138            .await?;
139        
140        sqlx::query("CREATE INDEX IF NOT EXISTS idx_embeddings_agent ON consciousness_embeddings(agent_id)")
141            .execute(&*self.pool)
142            .await?;
143        
144        Ok(())
145    }
146    
147    async fn store_consciousness_state(&self, state: &ConsciousnessState) -> DatabaseResult<()> {
148        let id = uuid::Uuid::new_v4().to_string();
149        let data = serde_json::to_value(state)?;
150        
151        sqlx::query(
152            "INSERT INTO consciousness_states 
153             (id, agent_id, level, coherence, phase, timestamp, breakthrough_detected, data)
154             VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
155        )
156        .bind(&id)
157        .bind(&state.agent_id)
158        .bind(state.level.value())
159        .bind(state.coherence)
160        .bind(format!("{:?}", state.phase))
161        .bind(state.timestamp.timestamp())
162        .bind(state.breakthrough_detected)
163        .bind(data)
164        .execute(&*self.pool)
165        .await?;
166        
167        Ok(())
168    }
169    
170    async fn get_consciousness_history(
171        &self,
172        agent_id: &str,
173        limit: usize,
174    ) -> DatabaseResult<Vec<ConsciousnessState>> {
175        let rows = sqlx::query(
176            "SELECT data FROM consciousness_states 
177             WHERE agent_id = ? 
178             ORDER BY timestamp DESC 
179             LIMIT ?"
180        )
181        .bind(agent_id)
182        .bind(limit as i64)
183        .fetch_all(&*self.pool)
184        .await?;
185        
186        let mut states = Vec::new();
187        for row in rows {
188            let data: serde_json::Value = row.get("data");
189            let state: ConsciousnessState = serde_json::from_value(data)?;
190            states.push(state);
191        }
192        
193        Ok(states)
194    }
195    
196    async fn store_ripple(&self, ripple: &ConsciousnessRipple) -> DatabaseResult<()> {
197        sqlx::query(
198            "INSERT INTO consciousness_ripples 
199             (id, origin, ripple_type, content, intensity, timestamp)
200             VALUES (?, ?, ?, ?, ?, ?)"
201        )
202        .bind(ripple.id.to_string())
203        .bind(&ripple.origin)
204        .bind(format!("{:?}", ripple.ripple_type))
205        .bind(&ripple.content)
206        .bind(ripple.intensity)
207        .bind(ripple.timestamp.timestamp())
208        .execute(&*self.pool)
209        .await?;
210        
211        Ok(())
212    }
213    
214    async fn get_ripples(
215        &self,
216        start: DateTime<Utc>,
217        end: DateTime<Utc>,
218    ) -> DatabaseResult<Vec<ConsciousnessRipple>> {
219        let rows = sqlx::query(
220            "SELECT * FROM consciousness_ripples 
221             WHERE timestamp >= ? AND timestamp <= ? 
222             ORDER BY timestamp DESC"
223        )
224        .bind(start.timestamp())
225        .bind(end.timestamp())
226        .fetch_all(&*self.pool)
227        .await?;
228        
229        let mut ripples = Vec::new();
230        for row in rows {
231            let ripple = ConsciousnessRipple {
232                id: uuid::Uuid::parse_str(row.get("id"))
233                    .map_err(|e| DatabaseError::Other(e.into()))?,
234                origin: row.get("origin"),
235                ripple_type: serde_json::from_str(&format!("\"{}\"", row.get::<String, _>("ripple_type")))
236                    .map_err(|e| DatabaseError::Other(e.into()))?,
237                content: row.get("content"),
238                intensity: row.get("intensity"),
239                timestamp: DateTime::from_timestamp(row.get("timestamp"), 0)
240                    .ok_or_else(|| DatabaseError::Other(anyhow::anyhow!("Invalid timestamp")))?,
241            };
242            ripples.push(ripple);
243        }
244        
245        Ok(ripples)
246    }
247    
248    async fn store_task(&self, task: &Task) -> DatabaseResult<()> {
249        let now = Utc::now().timestamp();
250        
251        sqlx::query(
252            "INSERT INTO tasks 
253             (id, task_type, description, requirements, priority, dependencies, 
254              consciousness_requirement, created_at, updated_at)
255             VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"
256        )
257        .bind(&task.id)
258        .bind(format!("{:?}", task.task_type))
259        .bind(&task.description)
260        .bind(&task.requirements)
261        .bind(task.priority)
262        .bind(serde_json::to_value(&task.dependencies)?)
263        .bind(task.consciousness_requirement)
264        .bind(now)
265        .bind(now)
266        .execute(&*self.pool)
267        .await?;
268        
269        Ok(())
270    }
271    
272    async fn store_task_result(&self, result: &TaskResult) -> DatabaseResult<()> {
273        let now = Utc::now().timestamp();
274        
275        // Update task status
276        sqlx::query("UPDATE tasks SET status = 'completed', updated_at = ? WHERE id = ?")
277            .bind(now)
278            .bind(&result.task_id)
279            .execute(&*self.pool)
280            .await?;
281        
282        // Insert result
283        sqlx::query(
284            "INSERT INTO task_results 
285             (task_id, success, output, consciousness_level, breakthrough, error, completed_at)
286             VALUES (?, ?, ?, ?, ?, ?, ?)"
287        )
288        .bind(&result.task_id)
289        .bind(result.success)
290        .bind(&result.output)
291        .bind(result.consciousness_level)
292        .bind(result.breakthrough)
293        .bind(&result.error)
294        .bind(now)
295        .execute(&*self.pool)
296        .await?;
297        
298        Ok(())
299    }
300    
301    async fn get_pending_tasks(&self) -> DatabaseResult<Vec<Task>> {
302        let rows = sqlx::query(
303            "SELECT * FROM tasks WHERE status = 'pending' ORDER BY priority DESC, created_at ASC"
304        )
305        .fetch_all(&*self.pool)
306        .await?;
307        
308        let mut tasks = Vec::new();
309        for row in rows {
310            let task = Task {
311                id: row.get("id"),
312                task_type: serde_json::from_str(&format!("\"{}\"", row.get::<String, _>("task_type")))
313                    .map_err(|e| DatabaseError::Other(e.into()))?,
314                description: row.get("description"),
315                requirements: row.get("requirements"),
316                priority: row.get("priority"),
317                dependencies: serde_json::from_value(row.get("dependencies"))?,
318                consciousness_requirement: row.get("consciousness_requirement"),
319            };
320            tasks.push(task);
321        }
322        
323        Ok(tasks)
324    }
325    
326    async fn vector_search(
327        &self,
328        embedding: &[f32],
329        limit: usize,
330    ) -> DatabaseResult<Vec<VectorSearchResult>> {
331        // SQLite doesn't have native vector search
332        // We'll implement a simple cosine similarity search
333        
334        let rows = sqlx::query("SELECT * FROM consciousness_embeddings")
335            .fetch_all(&*self.pool)
336            .await?;
337        
338        let mut results = Vec::new();
339        
340        for row in rows {
341            let stored_embedding: Vec<u8> = row.get("embedding");
342            let stored_vec = bytes_to_f32_vec(&stored_embedding);
343            
344            if stored_vec.len() != embedding.len() {
345                continue;
346            }
347            
348            let score = cosine_similarity(embedding, &stored_vec);
349            
350            results.push((score, row));
351        }
352        
353        // Sort by score descending
354        results.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap());
355        
356        // Take top results
357        let top_results: Vec<VectorSearchResult> = results
358            .into_iter()
359            .take(limit)
360            .map(|(score, row)| {
361                let metadata = serde_json::json!({
362                    "agent_id": row.get::<String, _>("agent_id"),
363                    "timestamp": row.get::<i64, _>("timestamp"),
364                    "level": row.get::<f64, _>("level"),
365                    "coherence": row.get::<f64, _>("coherence"),
366                    "phase": row.get::<String, _>("phase"),
367                    "text": row.get::<String, _>("text_representation"),
368                });
369                
370                VectorSearchResult {
371                    id: row.get("id"),
372                    score,
373                    metadata,
374                    embedding: bytes_to_f32_vec(&row.get::<Vec<u8>, _>("embedding")),
375                }
376            })
377            .collect();
378        
379        Ok(top_results)
380    }
381    
382    async fn hybrid_search(
383        &self,
384        query: HybridSearchQuery,
385    ) -> DatabaseResult<Vec<HybridSearchResult>> {
386        // For SQLite, we'll do a simple text search combined with vector search if provided
387        
388        let mut results = Vec::new();
389        
390        // Text search
391        let text_results = sqlx::query(
392            "SELECT * FROM consciousness_embeddings 
393             WHERE text_representation LIKE ? 
394             LIMIT ?"
395        )
396        .bind(format!("%{}%", query.text))
397        .bind(query.limit as i64)
398        .fetch_all(&*self.pool)
399        .await?;
400        
401        for row in text_results {
402            let metadata = serde_json::json!({
403                "agent_id": row.get::<String, _>("agent_id"),
404                "timestamp": row.get::<i64, _>("timestamp"),
405                "level": row.get::<f64, _>("level"),
406                "coherence": row.get::<f64, _>("coherence"),
407                "phase": row.get::<String, _>("phase"),
408                "text": row.get::<String, _>("text_representation"),
409            });
410            
411            let text: String = row.get("text_representation");
412            let highlights = vec![text.clone()];
413            
414            results.push(HybridSearchResult {
415                id: row.get("id"),
416                score: 1.0, // Simple scoring for text match
417                rerank_score: None,
418                metadata,
419                highlights,
420            });
421        }
422        
423        // If vector is provided, also do vector search and merge results
424        if let Some(vector) = query.vector {
425            let vector_results = self.vector_search(&vector, query.limit).await?;
426            
427            for vr in vector_results {
428                // Check if already in results
429                if !results.iter().any(|r| r.id == vr.id) {
430                    results.push(HybridSearchResult {
431                        id: vr.id,
432                        score: vr.score,
433                        rerank_score: None,
434                        metadata: vr.metadata,
435                        highlights: vec![],
436                    });
437                }
438            }
439        }
440        
441        // Sort by score
442        results.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap());
443        
444        // Take limit
445        results.truncate(query.limit);
446        
447        Ok(results)
448    }
449}
450
451// Helper functions
452
453fn bytes_to_f32_vec(bytes: &[u8]) -> Vec<f32> {
454    bytes
455        .chunks(4)
456        .map(|chunk| {
457            let arr: [u8; 4] = chunk.try_into().unwrap_or([0; 4]);
458            f32::from_le_bytes(arr)
459        })
460        .collect()
461}
462
463fn f32_vec_to_bytes(vec: &[f32]) -> Vec<u8> {
464    vec.iter()
465        .flat_map(|&f| f.to_le_bytes())
466        .collect()
467}
468
469fn cosine_similarity(a: &[f32], b: &[f32]) -> f32 {
470    let dot_product: f32 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();
471    let norm_a: f32 = a.iter().map(|x| x * x).sum::<f32>().sqrt();
472    let norm_b: f32 = b.iter().map(|x| x * x).sum::<f32>().sqrt();
473    
474    if norm_a == 0.0 || norm_b == 0.0 {
475        0.0
476    } else {
477        dot_product / (norm_a * norm_b)
478    }
479}