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};
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 hybrid_query(&self, q: HybridQuery) -> Result<Vec<HybridResult>> {
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.query(q, &col)
107 }
108
109 pub fn execute(&self, sql: &str) -> Result<usize> {
111 let conn = self.conn.lock().unwrap();
112 Ok(conn.execute(sql, [])?)
113 }
114
115 pub fn execute_params(&self, sql: &str, params: &[&dyn rusqlite::ToSql]) -> Result<usize> {
117 let conn = self.conn.lock().unwrap();
118 Ok(conn.execute(sql, params)?)
119 }
120
121 pub fn transaction<F, T>(&self, f: F) -> Result<T>
139 where
140 F: FnOnce(&rusqlite::Transaction) -> Result<T>,
141 {
142 let mut conn = self.conn.lock().unwrap();
143 let tx = conn.transaction()?;
144 let result = f(&tx)?;
145 tx.commit()?;
146 Ok(result)
147 }
148
149 pub fn execute_batch(&self, sql: &str) -> Result<()> {
165 let mut conn = self.conn.lock().unwrap();
166 let tx = conn.transaction()?;
167 tx.execute_batch(sql)?;
168 tx.commit()?;
169 Ok(())
170 }
171
172 pub fn query_json(&self, sql: &str) -> Result<Vec<serde_json::Value>> {
174 let conn = self.conn.lock().unwrap();
175 let mut stmt = conn.prepare(sql)?;
176 let col_names: Vec<String> = stmt.column_names().iter().map(|s| s.to_string()).collect();
177 let rows = stmt.query_map([], |row| {
178 let mut map = serde_json::Map::new();
179 for (i, name) in col_names.iter().enumerate() {
180 let val: rusqlite::types::Value = row.get(i)?;
181 map.insert(name.clone(), rusqlite_value_to_json(val));
182 }
183 Ok(serde_json::Value::Object(map))
184 })?;
185 rows.map(|r| r.map_err(crate::error::AgentDbError::Sqlite))
186 .collect()
187 }
188
189 pub fn query_json_params(
203 &self,
204 sql: &str,
205 params: &[&dyn rusqlite::ToSql],
206 ) -> Result<Vec<serde_json::Value>> {
207 let conn = self.conn.lock().unwrap();
208 let mut stmt = conn.prepare(sql)?;
209 let col_names: Vec<String> = stmt.column_names().iter().map(|s| s.to_string()).collect();
210 let rows = stmt.query_map(params, |row| {
211 let mut map = serde_json::Map::new();
212 for (i, name) in col_names.iter().enumerate() {
213 let val: rusqlite::types::Value = row.get(i)?;
214 map.insert(name.clone(), rusqlite_value_to_json(val));
215 }
216 Ok(serde_json::Value::Object(map))
217 })?;
218 rows.map(|r| r.map_err(crate::error::AgentDbError::Sqlite))
219 .collect()
220 }
221
222 pub fn close(self) -> Result<()> {
226 let collections = self.vectors().list_collections()?;
227 for (name, dim, _) in collections {
228 let col = self.vectors().collection(&name, dim)?;
229 let is_dirty: i64 = {
230 let conn = self.conn.lock().unwrap();
231 conn.query_row(
232 "SELECT COALESCE(
233 (SELECT is_dirty FROM _adb_hnsw_index
234 WHERE collection_id =
235 (SELECT id FROM _adb_collections WHERE name = ?1)
236 ), 0)",
237 rusqlite::params![name],
238 |r| r.get(0),
239 )
240 .unwrap_or(0)
241 };
242 if is_dirty == 1 {
243 col.reindex()?;
244 }
245 }
246 self.closed.store(true, Ordering::Release);
247 Ok(())
248 }
249
250 pub fn stats(&self) -> Result<DbStats> {
252 let conn = self.conn.lock().unwrap();
253 conn.query_row(
254 "SELECT
255 (SELECT COUNT(*) FROM _adb_collections) AS collections,
256 (SELECT COALESCE(SUM(count),0) FROM _adb_collections) AS vectors,
257 (SELECT COUNT(*) FROM _adb_nodes) AS nodes,
258 (SELECT COUNT(*) FROM _adb_edges) AS edges,
259 (SELECT COUNT(*) FROM _adb_conversations) AS conversations,
260 (SELECT COUNT(*) FROM _adb_messages) AS messages,
261 (SELECT COUNT(*) FROM _adb_workflows) AS workflows,
262 (SELECT COUNT(*) FROM _adb_workflow_steps) AS workflow_steps,
263 (SELECT COUNT(*) FROM _adb_traces) AS traces,
264 (SELECT COUNT(*) FROM _adb_tools) AS tools,
265 (SELECT COUNT(*) FROM _adb_tool_calls) AS tool_calls,
266 (SELECT COUNT(*) FROM _adb_audit_log) AS audit_entries,
267 (SELECT COUNT(*) FROM _adb_prompt_templates) AS prompt_templates",
268 [],
269 |r| {
270 Ok(DbStats {
271 collections: r.get(0)?,
272 vectors: r.get(1)?,
273 nodes: r.get(2)?,
274 edges: r.get(3)?,
275 conversations: r.get(4)?,
276 messages: r.get(5)?,
277 workflows: r.get(6)?,
278 workflow_steps: r.get(7)?,
279 traces: r.get(8)?,
280 tools: r.get(9)?,
281 tool_calls: r.get(10)?,
282 audit_entries: r.get(11)?,
283 prompt_templates: r.get(12)?,
284 })
285 },
286 )
287 .map_err(crate::error::AgentDbError::Sqlite)
288 }
289}
290
291#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
293pub struct DbStats {
294 pub collections: i64,
296 pub vectors: i64,
298 pub nodes: i64,
300 pub edges: i64,
302 pub conversations: i64,
304 pub messages: i64,
306 pub workflows: i64,
308 pub workflow_steps: i64,
310 pub traces: i64,
312 pub tools: i64,
314 pub tool_calls: i64,
316 pub audit_entries: i64,
318 pub prompt_templates: i64,
320}
321
322impl Drop for AgentDB {
323 fn drop(&mut self) {
324 if self.closed.load(Ordering::Acquire) {
325 return;
326 }
327 if let Ok(collections) = self.vectors().list_collections() {
328 for (name, dim, _) in collections {
329 if let Ok(col) = self.vectors().collection(&name, dim) {
330 let is_dirty: bool = {
331 let conn = self.conn.lock().unwrap();
332 conn.query_row(
333 "SELECT COALESCE(
334 (SELECT is_dirty FROM _adb_hnsw_index
335 WHERE collection_id =
336 (SELECT id FROM _adb_collections WHERE name = ?1)
337 ), 0)",
338 rusqlite::params![name],
339 |r| r.get::<_, i64>(0),
340 )
341 .unwrap_or(0)
342 == 1
343 };
344 if is_dirty {
345 let _ = col.reindex();
346 }
347 }
348 }
349 }
350 }
351}
352
353fn rusqlite_value_to_json(val: rusqlite::types::Value) -> serde_json::Value {
354 match val {
355 rusqlite::types::Value::Null => serde_json::Value::Null,
356 rusqlite::types::Value::Integer(i) => serde_json::Value::Number(i.into()),
357 rusqlite::types::Value::Real(f) => serde_json::Number::from_f64(f)
358 .map(serde_json::Value::Number)
359 .unwrap_or(serde_json::Value::Null),
360 rusqlite::types::Value::Text(s) => serde_json::Value::String(s),
361 rusqlite::types::Value::Blob(b) => {
362 serde_json::Value::String(format!("<blob {} bytes>", b.len()))
363 }
364 }
365}