1#[cfg(feature = "persistence")]
11use std::collections::HashMap;
12#[cfg(feature = "persistence")]
13use std::path::Path;
14#[cfg(feature = "persistence")]
15use std::sync::Arc;
16
17#[cfg(feature = "persistence")]
18use serde_json::json;
19use serde_json::Value;
20#[cfg(feature = "persistence")]
21use velesdb_core::agent::AgentMemory;
22#[cfg(feature = "persistence")]
23use velesdb_core::{Database, SearchResult};
24
25use crate::error::MemoryError;
26use crate::model::{ColumnFilter, MemoryEdge, Recollection};
27use crate::service::Metadata;
28
29pub trait MemoryStore {
35 fn store(&self, id: u64, content: &str, embedding: &[f32]) -> Result<(), MemoryError>;
40
41 fn store_with_metadata(
46 &self,
47 id: u64,
48 content: &str,
49 embedding: &[f32],
50 metadata: &Metadata,
51 ) -> Result<(), MemoryError>;
52
53 fn store_with_ttl(
58 &self,
59 id: u64,
60 content: &str,
61 embedding: &[f32],
62 ttl_seconds: u64,
63 ) -> Result<(), MemoryError>;
64
65 fn update_metadata(&self, id: u64, metadata: &Metadata) -> Result<(), MemoryError>;
73
74 fn get(&self, id: u64) -> Result<Option<(String, Vec<f32>)>, MemoryError>;
79
80 fn get_metadata(&self, id: u64) -> Result<Option<Metadata>, MemoryError>;
88
89 fn get_metadata_batch(&self, ids: &[u64]) -> Result<Vec<Option<Metadata>>, MemoryError>;
96
97 fn delete(&self, id: u64) -> Result<(), MemoryError>;
102
103 fn query_filtered(
109 &self,
110 embedding: &[f32],
111 k: usize,
112 filter: &Metadata,
113 offset: usize,
114 ) -> Result<Vec<(u64, f32, String)>, MemoryError>;
115
116 fn query_excluding(
122 &self,
123 embedding: &[f32],
124 k: usize,
125 exclude: &Metadata,
126 ) -> Result<Vec<(u64, f32, String)>, MemoryError>;
127
128 fn query_columnar(
137 &self,
138 embedding: &[f32],
139 k: usize,
140 filters: &[ColumnFilter],
141 ) -> Result<Vec<Recollection>, MemoryError>;
142
143 fn relate(&self, from: u64, to: u64, relation: &str) -> Result<u64, MemoryError>;
148
149 fn relations(&self, id: u64) -> Result<Vec<MemoryEdge>, MemoryError>;
154
155 fn count(&self) -> usize;
158}
159
160#[cfg(feature = "persistence")]
165pub struct NativeStore {
166 memory: AgentMemory,
167}
168
169#[cfg(feature = "persistence")]
170impl NativeStore {
171 pub fn open<P: AsRef<Path>>(path: P, dimension: usize) -> Result<Self, MemoryError> {
176 let db = Arc::new(Database::open(path)?);
177 let memory = AgentMemory::with_dimension(db, dimension)?;
178 Ok(Self { memory })
179 }
180}
181
182#[cfg(feature = "persistence")]
183impl MemoryStore for NativeStore {
184 fn store(&self, id: u64, content: &str, embedding: &[f32]) -> Result<(), MemoryError> {
185 self.memory
186 .semantic()
187 .store(id, content, embedding)
188 .map_err(MemoryError::from)
189 }
190
191 fn store_with_metadata(
192 &self,
193 id: u64,
194 content: &str,
195 embedding: &[f32],
196 metadata: &Metadata,
197 ) -> Result<(), MemoryError> {
198 self.memory
199 .semantic()
200 .store_with_metadata(id, content, embedding, metadata)
201 .map_err(MemoryError::from)
202 }
203
204 fn store_with_ttl(
205 &self,
206 id: u64,
207 content: &str,
208 embedding: &[f32],
209 ttl_seconds: u64,
210 ) -> Result<(), MemoryError> {
211 self.memory
212 .semantic()
213 .store_with_ttl(id, content, embedding, ttl_seconds)
214 .map_err(MemoryError::from)
215 }
216
217 fn update_metadata(&self, id: u64, metadata: &Metadata) -> Result<(), MemoryError> {
218 self.memory
219 .semantic()
220 .update_metadata(id, metadata)
221 .map_err(MemoryError::from)
222 }
223
224 fn get(&self, id: u64) -> Result<Option<(String, Vec<f32>)>, MemoryError> {
225 self.memory.semantic().get(id).map_err(MemoryError::from)
226 }
227
228 fn get_metadata(&self, id: u64) -> Result<Option<Metadata>, MemoryError> {
229 self.memory
230 .semantic()
231 .get_metadata(id)
232 .map_err(MemoryError::from)
233 }
234
235 fn get_metadata_batch(&self, ids: &[u64]) -> Result<Vec<Option<Metadata>>, MemoryError> {
236 self.memory
237 .semantic()
238 .get_metadata_batch(ids)
239 .map_err(MemoryError::from)
240 }
241
242 fn delete(&self, id: u64) -> Result<(), MemoryError> {
243 self.memory.semantic().delete(id).map_err(MemoryError::from)
244 }
245
246 fn query_filtered(
247 &self,
248 embedding: &[f32],
249 k: usize,
250 filter: &Metadata,
251 offset: usize,
252 ) -> Result<Vec<(u64, f32, String)>, MemoryError> {
253 self.memory
254 .semantic()
255 .query_filtered(embedding, k, filter, offset)
256 .map_err(MemoryError::from)
257 }
258
259 fn query_excluding(
260 &self,
261 embedding: &[f32],
262 k: usize,
263 exclude: &Metadata,
264 ) -> Result<Vec<(u64, f32, String)>, MemoryError> {
265 self.memory
266 .semantic()
267 .query_excluding(embedding, k, exclude)
268 .map_err(MemoryError::from)
269 }
270
271 fn query_columnar(
272 &self,
273 embedding: &[f32],
274 k: usize,
275 filters: &[ColumnFilter],
276 ) -> Result<Vec<Recollection>, MemoryError> {
277 let (sql, params) = self.build_fused_query(embedding, k, filters)?;
278 for filter in filters {
282 self.memory
283 .semantic()
284 .ensure_index(&filter.field)
285 .map_err(MemoryError::from)?;
286 }
287 let results = self
288 .memory
289 .query_semantic(&sql, ¶ms)
290 .map_err(MemoryError::from)?;
291 Ok(results.iter().map(to_recollection).collect())
292 }
293
294 fn relate(&self, from: u64, to: u64, relation: &str) -> Result<u64, MemoryError> {
295 self.memory
296 .semantic()
297 .relate(from, to, relation, None)
298 .map_err(MemoryError::from)
299 }
300
301 fn relations(&self, id: u64) -> Result<Vec<MemoryEdge>, MemoryError> {
302 Ok(self
303 .memory
304 .semantic()
305 .relations(id)?
306 .into_iter()
307 .map(|edge| MemoryEdge {
308 from: edge.source(),
309 to: edge.target(),
310 relation: edge.label().to_owned(),
311 })
312 .collect())
313 }
314
315 fn count(&self) -> usize {
316 self.memory.semantic().count()
317 }
318}
319
320#[cfg(feature = "persistence")]
321impl NativeStore {
322 fn build_fused_query(
327 &self,
328 embedding: &[f32],
329 k: usize,
330 filters: &[ColumnFilter],
331 ) -> Result<(String, HashMap<String, Value>), MemoryError> {
332 use std::fmt::Write as _;
333 let mut params: HashMap<String, Value> = HashMap::new();
334 params.insert("q".to_string(), json!(embedding));
335 let mut predicate = String::from("vector NEAR $q");
336 for (index, filter) in filters.iter().enumerate() {
337 validate_column_filter(filter)?;
338 let key = format!("p{index}");
339 let _ = write!(
340 predicate,
341 " AND {} {} ${key}",
342 filter.field,
343 filter.op.as_sql()
344 );
345 params.insert(key, filter.value.clone());
346 }
347 let sql = format!(
348 "SELECT * FROM {} WHERE {predicate} LIMIT {k}",
349 self.memory.semantic().collection_name()
350 );
351 Ok((sql, params))
352 }
353}
354
355pub(crate) fn is_reserved_key(key: &str) -> bool {
360 key == "content" || key.starts_with("_veles_")
361}
362
363#[must_use]
370pub fn strip_reserved_keys(payload: Option<Metadata>) -> Option<Metadata> {
371 payload.and_then(|payload| {
372 let metadata: Metadata = payload
373 .into_iter()
374 .filter(|(key, _)| !is_reserved_key(key))
375 .collect();
376 (!metadata.is_empty()).then_some(metadata)
377 })
378}
379
380#[must_use]
385pub fn strip_reserved_keys_ref(payload: Option<&Metadata>) -> Option<Metadata> {
386 payload.and_then(|payload| {
387 let metadata: Metadata = payload
388 .iter()
389 .filter(|(key, _)| !is_reserved_key(key))
390 .map(|(key, value)| (key.clone(), value.clone()))
391 .collect();
392 (!metadata.is_empty()).then_some(metadata)
393 })
394}
395
396#[cfg(feature = "persistence")]
400fn to_recollection(result: &SearchResult) -> Recollection {
401 let payload = result.point.payload.as_ref().and_then(Value::as_object);
402 let content = payload
403 .and_then(|payload| payload.get("content"))
404 .and_then(Value::as_str)
405 .unwrap_or_default()
406 .to_owned();
407 Recollection {
408 id: result.point.id,
409 score: result.score,
410 content,
411 metadata: strip_reserved_keys_ref(payload),
412 }
413}
414
415pub fn validate_column_filter(filter: &ColumnFilter) -> Result<(), MemoryError> {
427 let field = &filter.field;
428 let plain = !field.is_empty() && field.chars().all(|c| c.is_ascii_alphanumeric() || c == '_');
429 if !plain || is_reserved_key(field) {
430 return Err(MemoryError::InvalidFilter(field.clone()));
431 }
432 match &filter.value {
433 Value::String(_) | Value::Number(_) | Value::Bool(_) => Ok(()),
434 value => Err(MemoryError::InvalidFilter(format!(
435 "value must be a string, number, or boolean, got {value}"
436 ))),
437 }
438}
439
440#[cfg(all(test, feature = "persistence"))]
441#[path = "storage_tests.rs"]
442mod tests;