1use std::path::PathBuf;
6
7#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
9pub struct MemoryFact {
10 pub id: i64,
11 pub key: String,
12 pub value: String,
13 pub category: String,
14 pub created_at: String,
15 pub updated_at: String,
16}
17
18pub struct MemoryStore {
20 db_path: PathBuf,
21}
22
23impl MemoryStore {
24 pub fn open(db_path: PathBuf) -> anyhow::Result<Self> {
26 let conn = rusqlite::Connection::open(&db_path)?;
27 conn.execute_batch(
28 "CREATE TABLE IF NOT EXISTS memory (
29 id INTEGER PRIMARY KEY AUTOINCREMENT,
30 key TEXT NOT NULL UNIQUE,
31 value TEXT NOT NULL,
32 category TEXT NOT NULL DEFAULT 'general',
33 created_at TEXT NOT NULL DEFAULT (datetime('now')),
34 updated_at TEXT NOT NULL DEFAULT (datetime('now'))
35 );
36 CREATE INDEX IF NOT EXISTS idx_memory_key ON memory(key);
37 CREATE INDEX IF NOT EXISTS idx_memory_category ON memory(category);"
38 )?;
39 Ok(Self { db_path })
40 }
41
42 pub fn set(&self, key: &str, value: &str, category: &str) -> anyhow::Result<()> {
44 let conn = rusqlite::Connection::open(&self.db_path)?;
45 conn.execute(
46 "INSERT INTO memory (key, value, category) VALUES (?1, ?2, ?3)
47 ON CONFLICT(key) DO UPDATE SET value = ?2, category = ?3, updated_at = datetime('now')",
48 rusqlite::params![key, value, category],
49 )?;
50 Ok(())
51 }
52
53 pub fn get(&self, key: &str) -> anyhow::Result<Option<MemoryFact>> {
55 let conn = rusqlite::Connection::open(&self.db_path)?;
56 let mut stmt = conn.prepare(
57 "SELECT id, key, value, category, created_at, updated_at FROM memory WHERE key = ?1"
58 )?;
59 let result = stmt.query_row(rusqlite::params![key], |row| {
60 Ok(MemoryFact {
61 id: row.get(0)?,
62 key: row.get(1)?,
63 value: row.get(2)?,
64 category: row.get(3)?,
65 created_at: row.get(4)?,
66 updated_at: row.get(5)?,
67 })
68 }).ok();
69 Ok(result)
70 }
71
72 pub fn search(&self, query: &str) -> anyhow::Result<Vec<MemoryFact>> {
74 let conn = rusqlite::Connection::open(&self.db_path)?;
75 let pattern = format!("%{}%", query);
76 let mut stmt = conn.prepare(
77 "SELECT id, key, value, category, created_at, updated_at FROM memory
78 WHERE key LIKE ?1 OR value LIKE ?1 OR category LIKE ?1
79 ORDER BY updated_at DESC LIMIT 50"
80 )?;
81 let facts = stmt.query_map(rusqlite::params![pattern], |row| {
82 Ok(MemoryFact {
83 id: row.get(0)?,
84 key: row.get(1)?,
85 value: row.get(2)?,
86 category: row.get(3)?,
87 created_at: row.get(4)?,
88 updated_at: row.get(5)?,
89 })
90 })?.filter_map(|r| r.ok()).collect();
91 Ok(facts)
92 }
93
94 pub fn list_all(&self) -> anyhow::Result<Vec<MemoryFact>> {
96 let conn = rusqlite::Connection::open(&self.db_path)?;
97 let mut stmt = conn.prepare(
98 "SELECT id, key, value, category, created_at, updated_at FROM memory
99 ORDER BY updated_at DESC"
100 )?;
101 let facts = stmt.query_map([], |row| {
102 Ok(MemoryFact {
103 id: row.get(0)?,
104 key: row.get(1)?,
105 value: row.get(2)?,
106 category: row.get(3)?,
107 created_at: row.get(4)?,
108 updated_at: row.get(5)?,
109 })
110 })?.filter_map(|r| r.ok()).collect();
111 Ok(facts)
112 }
113
114 pub fn list_by_category(&self, category: &str) -> anyhow::Result<Vec<MemoryFact>> {
116 let conn = rusqlite::Connection::open(&self.db_path)?;
117 let mut stmt = conn.prepare(
118 "SELECT id, key, value, category, created_at, updated_at FROM memory
119 WHERE category = ?1 ORDER BY updated_at DESC"
120 )?;
121 let facts = stmt.query_map(rusqlite::params![category], |row| {
122 Ok(MemoryFact {
123 id: row.get(0)?,
124 key: row.get(1)?,
125 value: row.get(2)?,
126 category: row.get(3)?,
127 created_at: row.get(4)?,
128 updated_at: row.get(5)?,
129 })
130 })?.filter_map(|r| r.ok()).collect();
131 Ok(facts)
132 }
133
134 pub fn delete(&self, key: &str) -> anyhow::Result<bool> {
136 let conn = rusqlite::Connection::open(&self.db_path)?;
137 let count = conn.execute("DELETE FROM memory WHERE key = ?1", rusqlite::params![key])?;
138 Ok(count > 0)
139 }
140
141 pub fn count(&self) -> anyhow::Result<usize> {
143 let conn = rusqlite::Connection::open(&self.db_path)?;
144 let count: usize = conn.query_row("SELECT COUNT(*) FROM memory", [], |row| row.get(0))?;
145 Ok(count)
146 }
147
148 pub fn categories(&self) -> anyhow::Result<Vec<(String, usize)>> {
150 let conn = rusqlite::Connection::open(&self.db_path)?;
151 let mut stmt = conn.prepare(
152 "SELECT category, COUNT(*) as cnt FROM memory GROUP BY category ORDER BY cnt DESC"
153 )?;
154 let cats = stmt.query_map([], |row| {
155 Ok((row.get::<_, String>(0)?, row.get::<_, usize>(1)?))
156 })?.filter_map(|r| r.ok()).collect();
157 Ok(cats)
158 }
159}