1use crate::audit::AuditStore;
2use crate::context::ContextStore;
3use crate::conversations::ConversationStore;
4use crate::error::Result;
5use crate::fts::FullTextStore;
6use crate::hybrid::{HybridQuery, HybridResult, HybridStore, TriModalQuery, TriModalResult};
7use crate::labels::LabelStore;
8use crate::memory::MemoryGraph;
9use crate::prompts::PromptStore;
10use crate::schema;
11use crate::tools::ToolStore;
12use crate::traces::TraceStore;
13use crate::vectors::VectorStore;
14use crate::workflows::WorkflowStore;
15use rusqlite::Connection;
16use std::sync::atomic::{AtomicBool, Ordering};
17use std::sync::{Arc, Mutex};
18
19#[derive(Clone)]
21pub struct AgentDB {
22 conn: Arc<Mutex<Connection>>,
23 closed: Arc<AtomicBool>,
24}
25
26impl AgentDB {
27 pub fn open(path: &str) -> Result<Self> {
29 let conn = Connection::open(path)?;
30 schema::bootstrap(&conn)?;
31 schema::check_version(&conn)?;
32 Ok(Self {
33 conn: Arc::new(Mutex::new(conn)),
34 closed: Arc::new(AtomicBool::new(false)),
35 })
36 }
37
38 pub fn vectors(&self) -> VectorStore {
40 VectorStore::new(Arc::clone(&self.conn))
41 }
42
43 pub fn memory(&self) -> MemoryGraph {
45 MemoryGraph::new(Arc::clone(&self.conn))
46 }
47
48 pub fn fts(&self) -> FullTextStore {
50 FullTextStore::new(Arc::clone(&self.conn))
51 }
52
53 pub fn conversations(&self) -> ConversationStore {
55 ConversationStore::new(Arc::clone(&self.conn))
56 }
57
58 pub fn workflows(&self) -> WorkflowStore {
60 WorkflowStore::new(Arc::clone(&self.conn))
61 }
62
63 pub fn traces(&self) -> TraceStore {
65 TraceStore::new(Arc::clone(&self.conn))
66 }
67
68 pub fn tools(&self) -> ToolStore {
70 ToolStore::new(Arc::clone(&self.conn))
71 }
72
73 pub fn audit(&self) -> AuditStore {
75 AuditStore::new(Arc::clone(&self.conn))
76 }
77
78 pub fn context(&self) -> ContextStore {
80 ContextStore::new(Arc::clone(&self.conn))
81 }
82
83 pub fn prompts(&self) -> PromptStore {
85 PromptStore::new(Arc::clone(&self.conn))
86 }
87
88 pub fn labels(&self) -> LabelStore {
90 LabelStore::new(Arc::clone(&self.conn))
91 }
92
93 pub fn tri_modal_query(&self, q: &TriModalQuery) -> Result<Vec<TriModalResult>> {
95 let dim: usize = {
96 let conn = self.conn.lock().unwrap();
97 conn.query_row(
98 "SELECT dim FROM _adb_collections WHERE name = ?1",
99 rusqlite::params![q.collection],
100 |r| r.get::<_, i64>(0).map(|v| v as usize),
101 )
102 .unwrap_or(q.embedding.len())
103 };
104 let col = self.vectors().collection(&q.collection, dim)?;
105 let store = HybridStore::new(Arc::clone(&self.conn));
106 store.tri_modal_query(q, &col)
107 }
108
109 pub fn hybrid_query(&self, q: HybridQuery) -> Result<Vec<HybridResult>> {
111 let dim: usize = {
112 let conn = self.conn.lock().unwrap();
113 conn.query_row(
114 "SELECT dim FROM _adb_collections WHERE name = ?1",
115 rusqlite::params![q.collection],
116 |r| r.get::<_, i64>(0).map(|v| v as usize),
117 )
118 .unwrap_or(q.embedding.len())
119 };
120 let col = self.vectors().collection(q.collection, dim)?;
121 let store = HybridStore::new(Arc::clone(&self.conn));
122 store.query(q, &col)
123 }
124
125 pub fn execute(&self, sql: &str) -> Result<usize> {
127 let conn = self.conn.lock().unwrap();
128 Ok(conn.execute(sql, [])?)
129 }
130
131 pub fn execute_params(&self, sql: &str, params: &[&dyn rusqlite::ToSql]) -> Result<usize> {
133 let conn = self.conn.lock().unwrap();
134 Ok(conn.execute(sql, params)?)
135 }
136
137 pub fn transaction<F, T>(&self, f: F) -> Result<T>
155 where
156 F: FnOnce(&rusqlite::Transaction) -> Result<T>,
157 {
158 let mut conn = self.conn.lock().unwrap();
159 let tx = conn.transaction()?;
160 let result = f(&tx)?;
161 tx.commit()?;
162 Ok(result)
163 }
164
165 pub fn execute_batch(&self, sql: &str) -> Result<()> {
181 let mut conn = self.conn.lock().unwrap();
182 let tx = conn.transaction()?;
183 tx.execute_batch(sql)?;
184 tx.commit()?;
185 Ok(())
186 }
187
188 pub fn query_json(&self, sql: &str) -> Result<Vec<serde_json::Value>> {
190 let conn = self.conn.lock().unwrap();
191 let mut stmt = conn.prepare(sql)?;
192 let col_names: Vec<String> = stmt.column_names().iter().map(|s| s.to_string()).collect();
193 let rows = stmt.query_map([], |row| {
194 let mut map = serde_json::Map::new();
195 for (i, name) in col_names.iter().enumerate() {
196 let val: rusqlite::types::Value = row.get(i)?;
197 map.insert(name.clone(), rusqlite_value_to_json(val));
198 }
199 Ok(serde_json::Value::Object(map))
200 })?;
201 rows.map(|r| r.map_err(crate::error::AgentDbError::Sqlite))
202 .collect()
203 }
204
205 pub fn query_json_params(
219 &self,
220 sql: &str,
221 params: &[&dyn rusqlite::ToSql],
222 ) -> Result<Vec<serde_json::Value>> {
223 let conn = self.conn.lock().unwrap();
224 let mut stmt = conn.prepare(sql)?;
225 let col_names: Vec<String> = stmt.column_names().iter().map(|s| s.to_string()).collect();
226 let rows = stmt.query_map(params, |row| {
227 let mut map = serde_json::Map::new();
228 for (i, name) in col_names.iter().enumerate() {
229 let val: rusqlite::types::Value = row.get(i)?;
230 map.insert(name.clone(), rusqlite_value_to_json(val));
231 }
232 Ok(serde_json::Value::Object(map))
233 })?;
234 rows.map(|r| r.map_err(crate::error::AgentDbError::Sqlite))
235 .collect()
236 }
237
238 pub fn close(self) -> Result<()> {
242 let collections = self.vectors().list_collections()?;
243 for (name, dim, _) in collections {
244 let col = self.vectors().collection(&name, dim)?;
245 let is_dirty: i64 = {
246 let conn = self.conn.lock().unwrap();
247 conn.query_row(
248 "SELECT COALESCE(
249 (SELECT is_dirty FROM _adb_hnsw_index
250 WHERE collection_id =
251 (SELECT id FROM _adb_collections WHERE name = ?1)
252 ), 0)",
253 rusqlite::params![name],
254 |r| r.get(0),
255 )
256 .unwrap_or(0)
257 };
258 if is_dirty == 1 {
259 col.reindex()?;
260 }
261 }
262 {
264 let conn = self.conn.lock().unwrap();
265 let _ = conn.pragma_update(None, "wal_checkpoint", "TRUNCATE");
266 }
267 self.closed.store(true, Ordering::Release);
268 Ok(())
269 }
270
271 pub fn stats(&self) -> Result<DbStats> {
273 let conn = self.conn.lock().unwrap();
274 conn.query_row(
275 "SELECT
276 (SELECT COUNT(*) FROM _adb_collections) AS collections,
277 (SELECT COALESCE(SUM(count),0) FROM _adb_collections) AS vectors,
278 (SELECT COUNT(*) FROM _adb_nodes) AS nodes,
279 (SELECT COUNT(*) FROM _adb_edges) AS edges,
280 (SELECT COUNT(*) FROM _adb_conversations) AS conversations,
281 (SELECT COUNT(*) FROM _adb_messages) AS messages,
282 (SELECT COUNT(*) FROM _adb_workflows) AS workflows,
283 (SELECT COUNT(*) FROM _adb_workflow_steps) AS workflow_steps,
284 (SELECT COUNT(*) FROM _adb_traces) AS traces,
285 (SELECT COUNT(*) FROM _adb_tools) AS tools,
286 (SELECT COUNT(*) FROM _adb_tool_calls) AS tool_calls,
287 (SELECT COUNT(*) FROM _adb_audit_log) AS audit_entries,
288 (SELECT COUNT(*) FROM _adb_prompt_templates) AS prompt_templates",
289 [],
290 |r| {
291 Ok(DbStats {
292 collections: r.get(0)?,
293 vectors: r.get(1)?,
294 nodes: r.get(2)?,
295 edges: r.get(3)?,
296 conversations: r.get(4)?,
297 messages: r.get(5)?,
298 workflows: r.get(6)?,
299 workflow_steps: r.get(7)?,
300 traces: r.get(8)?,
301 tools: r.get(9)?,
302 tool_calls: r.get(10)?,
303 audit_entries: r.get(11)?,
304 prompt_templates: r.get(12)?,
305 })
306 },
307 )
308 .map_err(crate::error::AgentDbError::Sqlite)
309 }
310}
311
312#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
314pub struct DbStats {
315 pub collections: i64,
317 pub vectors: i64,
319 pub nodes: i64,
321 pub edges: i64,
323 pub conversations: i64,
325 pub messages: i64,
327 pub workflows: i64,
329 pub workflow_steps: i64,
331 pub traces: i64,
333 pub tools: i64,
335 pub tool_calls: i64,
337 pub audit_entries: i64,
339 pub prompt_templates: i64,
341}
342
343impl Drop for AgentDB {
344 fn drop(&mut self) {
345 if self.closed.load(Ordering::Acquire) {
346 return;
347 }
348 if let Ok(collections) = self.vectors().list_collections() {
349 for (name, dim, _) in collections {
350 if let Ok(col) = self.vectors().collection(&name, dim) {
351 let is_dirty: bool = {
352 let conn = self.conn.lock().unwrap();
353 conn.query_row(
354 "SELECT COALESCE(
355 (SELECT is_dirty FROM _adb_hnsw_index
356 WHERE collection_id =
357 (SELECT id FROM _adb_collections WHERE name = ?1)
358 ), 0)",
359 rusqlite::params![name],
360 |r| r.get::<_, i64>(0),
361 )
362 .unwrap_or(0)
363 == 1
364 };
365 if is_dirty {
366 let _ = col.reindex();
367 }
368 }
369 }
370 }
371 }
372}
373
374impl AgentDB {
377 pub(crate) fn conn_arc(&self) -> Arc<Mutex<Connection>> {
383 Arc::clone(&self.conn)
384 }
385}
386
387#[cfg(feature = "wasm")]
395impl AgentDB {
396 pub(crate) fn with_conn<F, T>(&self, f: F) -> crate::error::Result<T>
403 where
404 F: FnOnce(&rusqlite::Connection) -> crate::error::Result<T>,
405 {
406 let conn = self.conn.lock().unwrap();
407 f(&conn)
408 }
409}
410
411fn rusqlite_value_to_json(val: rusqlite::types::Value) -> serde_json::Value {
412 match val {
413 rusqlite::types::Value::Null => serde_json::Value::Null,
414 rusqlite::types::Value::Integer(i) => serde_json::Value::Number(i.into()),
415 rusqlite::types::Value::Real(f) => serde_json::Number::from_f64(f)
416 .map(serde_json::Value::Number)
417 .unwrap_or(serde_json::Value::Null),
418 rusqlite::types::Value::Text(s) => serde_json::Value::String(s),
419 rusqlite::types::Value::Blob(b) => {
420 serde_json::Value::String(format!("<blob {} bytes>", b.len()))
421 }
422 }
423}