1use 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
13pub 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 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 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 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 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 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 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 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 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 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 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 results.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap());
355
356 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 let mut results = Vec::new();
389
390 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, rerank_score: None,
418 metadata,
419 highlights,
420 });
421 }
422
423 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 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 results.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap());
443
444 results.truncate(query.limit);
446
447 Ok(results)
448 }
449}
450
451fn 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}