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
60 .query_row(rusqlite::params![key], |row| {
61 Ok(MemoryFact {
62 id: row.get(0)?,
63 key: row.get(1)?,
64 value: row.get(2)?,
65 category: row.get(3)?,
66 created_at: row.get(4)?,
67 updated_at: row.get(5)?,
68 })
69 })
70 .ok();
71 Ok(result)
72 }
73
74 pub fn search(&self, query: &str) -> anyhow::Result<Vec<MemoryFact>> {
76 let conn = rusqlite::Connection::open(&self.db_path)?;
77 let pattern = format!("%{}%", query);
78 let mut stmt = conn.prepare(
79 "SELECT id, key, value, category, created_at, updated_at FROM memory
80 WHERE key LIKE ?1 OR value LIKE ?1 OR category LIKE ?1
81 ORDER BY updated_at DESC LIMIT 50",
82 )?;
83 let facts = stmt
84 .query_map(rusqlite::params![pattern], |row| {
85 Ok(MemoryFact {
86 id: row.get(0)?,
87 key: row.get(1)?,
88 value: row.get(2)?,
89 category: row.get(3)?,
90 created_at: row.get(4)?,
91 updated_at: row.get(5)?,
92 })
93 })?
94 .filter_map(|r| r.ok())
95 .collect();
96 Ok(facts)
97 }
98
99 pub fn list_all(&self) -> anyhow::Result<Vec<MemoryFact>> {
101 let conn = rusqlite::Connection::open(&self.db_path)?;
102 let mut stmt = conn.prepare(
103 "SELECT id, key, value, category, created_at, updated_at FROM memory
104 ORDER BY updated_at DESC",
105 )?;
106 let facts = stmt
107 .query_map([], |row| {
108 Ok(MemoryFact {
109 id: row.get(0)?,
110 key: row.get(1)?,
111 value: row.get(2)?,
112 category: row.get(3)?,
113 created_at: row.get(4)?,
114 updated_at: row.get(5)?,
115 })
116 })?
117 .filter_map(|r| r.ok())
118 .collect();
119 Ok(facts)
120 }
121
122 pub fn list_by_category(&self, category: &str) -> anyhow::Result<Vec<MemoryFact>> {
124 let conn = rusqlite::Connection::open(&self.db_path)?;
125 let mut stmt = conn.prepare(
126 "SELECT id, key, value, category, created_at, updated_at FROM memory
127 WHERE category = ?1 ORDER BY updated_at DESC",
128 )?;
129 let facts = stmt
130 .query_map(rusqlite::params![category], |row| {
131 Ok(MemoryFact {
132 id: row.get(0)?,
133 key: row.get(1)?,
134 value: row.get(2)?,
135 category: row.get(3)?,
136 created_at: row.get(4)?,
137 updated_at: row.get(5)?,
138 })
139 })?
140 .filter_map(|r| r.ok())
141 .collect();
142 Ok(facts)
143 }
144
145 pub fn delete(&self, key: &str) -> anyhow::Result<bool> {
147 let conn = rusqlite::Connection::open(&self.db_path)?;
148 let count = conn.execute("DELETE FROM memory WHERE key = ?1", rusqlite::params![key])?;
149 Ok(count > 0)
150 }
151
152 pub fn count(&self) -> anyhow::Result<usize> {
154 let conn = rusqlite::Connection::open(&self.db_path)?;
155 let count: usize = conn.query_row("SELECT COUNT(*) FROM memory", [], |row| row.get(0))?;
156 Ok(count)
157 }
158
159 pub fn categories(&self) -> anyhow::Result<Vec<(String, usize)>> {
161 let conn = rusqlite::Connection::open(&self.db_path)?;
162 let mut stmt = conn.prepare(
163 "SELECT category, COUNT(*) as cnt FROM memory GROUP BY category ORDER BY cnt DESC",
164 )?;
165 let cats = stmt
166 .query_map([], |row| {
167 Ok((row.get::<_, String>(0)?, row.get::<_, usize>(1)?))
168 })?
169 .filter_map(|r| r.ok())
170 .collect();
171 Ok(cats)
172 }
173}