1use crate::db;
6use crate::db::{bytes_to_embedding, parse_optional_json, with_transaction};
7#[cfg(feature = "hnsw")]
8use crate::db::{enqueue_pending_index_op, PendingIndexOpKind};
9#[cfg(feature = "hnsw")]
10use crate::episodes;
11use crate::error::MemoryError;
12use crate::quantize::{self, Quantizer};
13use crate::types::{Fact, NamespaceDeleteReport};
14use crate::{merge_trace_ctx, MemoryStore};
15use rusqlite::{params, Connection};
16use stack_ids::TraceCtx;
17
18#[allow(dead_code)]
20pub fn insert_fact_with_fts(
21 conn: &Connection,
22 fact_id: &str,
23 namespace: &str,
24 content: &str,
25 embedding_bytes: &[u8],
26 source: Option<&str>,
27 metadata: Option<&serde_json::Value>,
28) -> Result<(), MemoryError> {
29 insert_fact_with_fts_q8(
30 conn,
31 fact_id,
32 namespace,
33 content,
34 embedding_bytes,
35 None,
36 source,
37 metadata,
38 )
39}
40
41#[allow(clippy::too_many_arguments)]
43pub fn insert_fact_with_fts_q8(
44 conn: &Connection,
45 fact_id: &str,
46 namespace: &str,
47 content: &str,
48 embedding_bytes: &[u8],
49 q8_bytes: Option<&[u8]>,
50 source: Option<&str>,
51 metadata: Option<&serde_json::Value>,
52) -> Result<(), MemoryError> {
53 let metadata_str = metadata.map(|m| m.to_string());
54 with_transaction(conn, |tx| {
55 tx.execute(
56 "INSERT INTO facts (id, namespace, content, source, embedding, embedding_q8, metadata)
57 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
58 params![
59 fact_id,
60 namespace,
61 content,
62 source,
63 embedding_bytes,
64 q8_bytes,
65 metadata_str
66 ],
67 )?;
68
69 tx.execute(
70 "INSERT INTO facts_rowid_map (fact_id) VALUES (?1)",
71 params![fact_id],
72 )?;
73 let fts_rowid = tx.last_insert_rowid();
74
75 tx.execute(
76 "INSERT INTO facts_fts(rowid, content) VALUES (?1, ?2)",
77 params![fts_rowid, content],
78 )?;
79
80 #[cfg(feature = "hnsw")]
81 enqueue_pending_index_op(
82 tx,
83 &format!("fact:{}", fact_id),
84 "fact",
85 PendingIndexOpKind::Upsert,
86 )?;
87 db::invalidate_derived_vector_artifact(tx, &format!("fact:{fact_id}"))?;
88
89 Ok(())
90 })
91}
92
93#[allow(clippy::too_many_arguments)]
97pub fn insert_fact_in_tx(
98 tx: &rusqlite::Transaction<'_>,
99 fact_id: &str,
100 namespace: &str,
101 content: &str,
102 embedding_bytes: &[u8],
103 q8_bytes: Option<&[u8]>,
104 source: Option<&str>,
105 metadata: Option<&serde_json::Value>,
106) -> Result<(), MemoryError> {
107 let metadata_str = metadata.map(|m| m.to_string());
108 tx.execute(
109 "INSERT INTO facts (id, namespace, content, source, embedding, embedding_q8, metadata)
110 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
111 params![
112 fact_id,
113 namespace,
114 content,
115 source,
116 embedding_bytes,
117 q8_bytes,
118 metadata_str
119 ],
120 )?;
121
122 tx.execute(
123 "INSERT INTO facts_rowid_map (fact_id) VALUES (?1)",
124 params![fact_id],
125 )?;
126 let fts_rowid = tx.last_insert_rowid();
127
128 tx.execute(
129 "INSERT INTO facts_fts(rowid, content) VALUES (?1, ?2)",
130 params![fts_rowid, content],
131 )?;
132
133 #[cfg(feature = "hnsw")]
134 enqueue_pending_index_op(
135 tx,
136 &format!("fact:{}", fact_id),
137 "fact",
138 PendingIndexOpKind::Upsert,
139 )?;
140 db::invalidate_derived_vector_artifact(tx, &format!("fact:{fact_id}"))?;
141
142 Ok(())
143}
144
145#[allow(dead_code)] pub fn delete_fact_with_fts(conn: &Connection, fact_id: &str) -> Result<(), MemoryError> {
148 with_transaction(conn, |tx| {
149 let fts_rowid: i64 = tx
150 .query_row(
151 "SELECT rowid FROM facts_rowid_map WHERE fact_id = ?1",
152 params![fact_id],
153 |row| row.get(0),
154 )
155 .map_err(|e| MemoryError::FactNotFound(format!("{}: {e}", fact_id)))?;
156
157 let content: String = tx
158 .query_row(
159 "SELECT content FROM facts WHERE id = ?1",
160 params![fact_id],
161 |row| row.get(0),
162 )
163 .map_err(|e| MemoryError::FactNotFound(format!("{}: {e}", fact_id)))?;
164
165 tx.execute(
166 "INSERT INTO facts_fts(facts_fts, rowid, content) VALUES('delete', ?1, ?2)",
167 params![fts_rowid, content],
168 )?;
169 tx.execute(
170 "DELETE FROM facts_rowid_map WHERE fact_id = ?1",
171 params![fact_id],
172 )?;
173 tx.execute(
174 "DELETE FROM episode_causes WHERE cause_node_id IN (?1, ?2)",
175 params![fact_id, format!("fact:{fact_id}")],
176 )?;
177 tx.execute(
178 "DELETE FROM derivation_edges
179 WHERE (source_kind = 'fact' AND source_id = ?1)
180 OR (target_kind = 'fact' AND target_id = ?1)",
181 params![fact_id],
182 )?;
183 tx.execute("DELETE FROM facts WHERE id = ?1", params![fact_id])?;
184
185 #[cfg(feature = "hnsw")]
186 enqueue_pending_index_op(
187 tx,
188 &format!("fact:{}", fact_id),
189 "fact",
190 PendingIndexOpKind::Delete,
191 )?;
192 db::invalidate_derived_vector_artifact(tx, &format!("fact:{fact_id}"))?;
193
194 Ok(())
195 })
196}
197
198#[allow(dead_code)] pub fn update_fact_with_fts(
201 conn: &Connection,
202 fact_id: &str,
203 new_content: &str,
204 new_embedding_bytes: &[u8],
205 new_q8_bytes: Option<&[u8]>,
206) -> Result<(), MemoryError> {
207 with_transaction(conn, |tx| {
208 let (fts_rowid, old_content): (i64, String) = tx
209 .query_row(
210 "SELECT fm.rowid, f.content
211 FROM facts f
212 JOIN facts_rowid_map fm ON fm.fact_id = f.id
213 WHERE f.id = ?1",
214 params![fact_id],
215 |row| Ok((row.get(0)?, row.get(1)?)),
216 )
217 .map_err(|e| MemoryError::FactNotFound(format!("{}: {e}", fact_id)))?;
218
219 tx.execute(
220 "INSERT INTO facts_fts(facts_fts, rowid, content) VALUES('delete', ?1, ?2)",
221 params![fts_rowid, old_content],
222 )?;
223
224 tx.execute(
225 "UPDATE facts
226 SET content = ?1,
227 embedding = ?2,
228 embedding_q8 = ?3,
229 updated_at = datetime('now')
230 WHERE id = ?4",
231 params![new_content, new_embedding_bytes, new_q8_bytes, fact_id],
232 )?;
233
234 tx.execute(
235 "INSERT INTO facts_fts(rowid, content) VALUES (?1, ?2)",
236 params![fts_rowid, new_content],
237 )?;
238 tx.execute(
239 "DELETE FROM derivation_edges
240 WHERE (source_kind = 'fact' AND source_id = ?1)
241 OR (target_kind = 'fact' AND target_id = ?1)",
242 params![fact_id],
243 )?;
244
245 #[cfg(feature = "hnsw")]
246 enqueue_pending_index_op(
247 tx,
248 &format!("fact:{}", fact_id),
249 "fact",
250 PendingIndexOpKind::Upsert,
251 )?;
252 db::invalidate_derived_vector_artifact(tx, &format!("fact:{fact_id}"))?;
253
254 Ok(())
255 })
256}
257
258pub fn delete_namespace(
260 conn: &Connection,
261 namespace: &str,
262) -> Result<NamespaceDeleteReport, MemoryError> {
263 with_transaction(conn, |tx| {
264 let mut report = NamespaceDeleteReport::default();
265 let delete_session = |session_id: &str| -> Result<(usize, usize), MemoryError> {
266 let message_data: Vec<(i64, String, i64, bool)> = {
267 let mut stmt = tx.prepare(
268 "SELECT m.id, m.content, mm.rowid, m.embedding IS NOT NULL
269 FROM messages m
270 JOIN messages_rowid_map mm ON mm.message_id = m.id
271 WHERE m.session_id = ?1",
272 )?;
273 let rows = stmt.query_map(params![session_id], |row| {
274 Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?))
275 })?;
276 rows.collect::<Result<Vec<_>, _>>()?
277 };
278
279 for (message_id, content, fts_rowid, has_embedding) in &message_data {
280 #[cfg(not(feature = "hnsw"))]
281 let _ = (message_id, has_embedding);
282 tx.execute(
283 "INSERT INTO messages_fts(messages_fts, rowid, content) VALUES('delete', ?1, ?2)",
284 params![fts_rowid, content],
285 )?;
286 #[cfg(feature = "hnsw")]
287 if *has_embedding {
288 enqueue_pending_index_op(
289 tx,
290 &format!("msg:{}", message_id),
291 "message",
292 PendingIndexOpKind::Delete,
293 )?;
294 }
295 }
296
297 let affected = tx.execute("DELETE FROM sessions WHERE id = ?1", params![session_id])?;
298 if affected == 0 {
299 return Err(MemoryError::SessionNotFound(session_id.to_string()));
300 }
301 let hnsw_ops = message_data
302 .iter()
303 .filter(|(_, _, _, has_embedding)| *has_embedding)
304 .count();
305 Ok((message_data.len(), hnsw_ops))
306 };
307
308 let document_ids: Vec<String> = {
309 let mut stmt = tx.prepare("SELECT id FROM documents WHERE namespace = ?1")?;
310 let ids = stmt
311 .query_map(params![namespace], |row| row.get(0))?
312 .collect::<Result<Vec<_>, _>>()?;
313 ids
314 };
315
316 let session_ids: Vec<String> = {
317 let mut stmt = tx.prepare("SELECT id, metadata FROM sessions")?;
318 let rows = stmt.query_map([], |row| {
319 Ok((row.get::<_, String>(0)?, row.get::<_, Option<String>>(1)?))
320 })?;
321 let mut ids = Vec::new();
322 for row in rows {
323 let (session_id, metadata_raw) = row?;
324 let metadata = parse_optional_json(
325 "sessions",
326 &session_id,
327 "metadata",
328 metadata_raw.as_deref(),
329 )?;
330 let namespace_matches = metadata
331 .as_ref()
332 .and_then(|value| {
333 value
334 .get("namespace")
335 .or_else(|| value.get("scope_namespace"))
336 })
337 .and_then(|value| value.as_str())
338 == Some(namespace);
339 if namespace_matches {
340 ids.push(session_id);
341 }
342 }
343 ids
344 };
345
346 for session_id in &session_ids {
347 let (messages, hnsw_ops) = delete_session(session_id)?;
348 report.messages += messages;
349 report.hnsw_ops += hnsw_ops;
350 }
351 report.sessions = session_ids.len();
352
353 let delete_derivation_edges_for_id = |kind: &str, id: &str| -> Result<(), MemoryError> {
354 tx.execute(
355 "DELETE FROM derivation_edges
356 WHERE (source_kind = ?1 AND source_id = ?2)
357 OR (target_kind = ?1 AND target_id = ?2)",
358 params![kind, id],
359 )?;
360 Ok(())
361 };
362
363 let delete_derivation_edges_for_ids =
364 |kind: &str, ids: &[String]| -> Result<(), MemoryError> {
365 for id in ids {
366 delete_derivation_edges_for_id(kind, id)?;
367 }
368 Ok(())
369 };
370
371 let facts: Vec<(String, i64, String)> = {
372 let mut stmt = tx.prepare(
373 "SELECT f.id, fm.rowid, f.content
374 FROM facts f
375 JOIN facts_rowid_map fm ON fm.fact_id = f.id
376 WHERE f.namespace = ?1",
377 )?;
378 let facts = stmt
379 .query_map(params![namespace], |row| {
380 Ok((row.get(0)?, row.get(1)?, row.get(2)?))
381 })?
382 .collect::<Result<Vec<_>, _>>()?;
383 facts
384 };
385
386 for (fact_id, fts_rowid, content) in &facts {
387 tx.execute(
388 "INSERT INTO facts_fts(facts_fts, rowid, content) VALUES('delete', ?1, ?2)",
389 params![fts_rowid, content],
390 )?;
391 tx.execute(
392 "DELETE FROM facts_rowid_map WHERE fact_id = ?1",
393 params![fact_id],
394 )?;
395
396 #[cfg(feature = "hnsw")]
397 enqueue_pending_index_op(
398 tx,
399 &format!("fact:{}", fact_id),
400 "fact",
401 PendingIndexOpKind::Delete,
402 )?;
403 #[cfg(feature = "hnsw")]
404 {
405 report.hnsw_ops += 1;
406 }
407 }
408 tx.execute("DELETE FROM facts WHERE namespace = ?1", params![namespace])?;
409 report.facts = facts.len();
410
411 for doc_id in &document_ids {
412 let mut stmt = tx.prepare(
413 "SELECT c.id, c.content, cm.rowid
414 FROM chunks c
415 JOIN chunks_rowid_map cm ON cm.chunk_id = c.id
416 WHERE c.document_id = ?1",
417 )?;
418 let chunk_rows: Vec<(String, String, i64)> = stmt
419 .query_map(params![doc_id], |row| {
420 Ok((row.get(0)?, row.get(1)?, row.get(2)?))
421 })?
422 .collect::<Result<Vec<_>, _>>()?;
423 report.chunks += chunk_rows.len();
424
425 for (chunk_id, content, fts_rowid) in &chunk_rows {
426 tx.execute(
427 "INSERT INTO chunks_fts(chunks_fts, rowid, content) VALUES ('delete', ?1, ?2)",
428 params![fts_rowid, content],
429 )?;
430 tx.execute(
431 "DELETE FROM chunks_rowid_map WHERE chunk_id = ?1",
432 params![chunk_id],
433 )?;
434 #[cfg(feature = "hnsw")]
435 enqueue_pending_index_op(
436 tx,
437 &format!("chunk:{}", chunk_id),
438 "chunk",
439 PendingIndexOpKind::Delete,
440 )?;
441 #[cfg(feature = "hnsw")]
442 {
443 report.hnsw_ops += 1;
444 }
445 }
446
447 tx.execute("DELETE FROM chunks WHERE document_id = ?1", params![doc_id])?;
448 }
449
450 for doc_id in &document_ids {
451 let mut stmt = tx.prepare(
452 "SELECT e.episode_id, e.search_text, erm.rowid
453 FROM episodes e
454 JOIN episodes_rowid_map erm ON erm.episode_id = e.episode_id
455 WHERE e.document_id = ?1",
456 )?;
457 let episode_rows: Vec<(String, String, i64)> = stmt
458 .query_map(params![doc_id], |row| {
459 Ok((row.get(0)?, row.get(1)?, row.get(2)?))
460 })?
461 .collect::<Result<Vec<_>, _>>()?;
462 report.episodes += episode_rows.len();
463
464 for (episode_id, search_text, fts_rowid) in &episode_rows {
465 tx.execute(
466 "INSERT INTO episodes_fts(episodes_fts, rowid, content) VALUES ('delete', ?1, ?2)",
467 params![fts_rowid, search_text],
468 )?;
469 tx.execute(
470 "DELETE FROM episodes_rowid_map WHERE episode_id = ?1",
471 params![episode_id],
472 )?;
473 tx.execute(
474 "DELETE FROM episode_causes WHERE episode_id = ?1",
475 params![episode_id],
476 )?;
477 #[cfg(feature = "hnsw")]
478 enqueue_pending_index_op(
479 tx,
480 &episodes::episode_item_key(episode_id),
481 "episode",
482 PendingIndexOpKind::Delete,
483 )?;
484 #[cfg(feature = "hnsw")]
485 {
486 report.hnsw_ops += 1;
487 }
488 }
489
490 tx.execute(
491 "DELETE FROM episodes WHERE document_id = ?1",
492 params![doc_id],
493 )?;
494 tx.execute("DELETE FROM documents WHERE id = ?1", params![doc_id])?;
495 }
496 report.documents = document_ids.len();
497
498 let claim_ids: Vec<String> = {
499 let mut stmt =
500 tx.prepare("SELECT claim_id FROM claim_versions WHERE scope_namespace = ?1")?;
501 let ids = stmt
502 .query_map(params![namespace], |row| row.get(0))?
503 .collect::<Result<Vec<_>, _>>()?;
504 ids
505 };
506
507 let claim_version_ids: Vec<String> = {
508 let mut stmt = tx.prepare(
509 "SELECT claim_version_id FROM claim_versions WHERE scope_namespace = ?1",
510 )?;
511 let ids = stmt
512 .query_map(params![namespace], |row| row.get(0))?
513 .collect::<Result<Vec<_>, _>>()?;
514 ids
515 };
516
517 let relation_version_ids: Vec<String> = {
518 let mut stmt = tx.prepare(
519 "SELECT relation_version_id FROM relation_versions WHERE scope_namespace = ?1",
520 )?;
521 let ids = stmt
522 .query_map(params![namespace], |row| row.get(0))?
523 .collect::<Result<Vec<_>, _>>()?;
524 ids
525 };
526
527 let alias_entity_ids: Vec<String> = {
528 let mut stmt = tx.prepare(
529 "SELECT canonical_entity_id FROM entity_aliases WHERE scope_namespace = ?1",
530 )?;
531 let ids = stmt
532 .query_map(params![namespace], |row| row.get(0))?
533 .collect::<Result<Vec<_>, _>>()?;
534 ids
535 };
536
537 let evidence_handles: Vec<String> = {
538 let mut stmt = tx.prepare(
539 "SELECT er.fetch_handle FROM evidence_refs er
540 JOIN projection_import_log pil ON er.source_envelope_id = pil.source_envelope_id
541 WHERE pil.scope_namespace = ?1",
542 )?;
543 let handles = stmt
544 .query_map(params![namespace], |row| row.get(0))?
545 .collect::<Result<Vec<_>, _>>()?;
546 handles
547 };
548
549 let episode_ids: Vec<String> = {
550 let mut stmt = tx.prepare(
551 "SELECT episode_id FROM episode_links
552 WHERE source_envelope_id IN (SELECT source_envelope_id FROM projection_import_log WHERE scope_namespace = ?1)",
553 )?;
554 let ids = stmt
555 .query_map(params![namespace], |row| row.get(0))?
556 .collect::<Result<Vec<_>, _>>()?;
557 ids
558 };
559
560 delete_derivation_edges_for_ids("claim", &claim_ids)?;
561 delete_derivation_edges_for_ids("claim_version", &claim_version_ids)?;
562 delete_derivation_edges_for_ids("relation_version", &relation_version_ids)?;
563 delete_derivation_edges_for_ids("entity", &alias_entity_ids)?;
564 delete_derivation_edges_for_ids("evidence_ref", &evidence_handles)?;
565 delete_derivation_edges_for_ids("episode", &episode_ids)?;
566
567 report.projection_rows += tx.execute(
568 "DELETE FROM claim_versions WHERE scope_namespace = ?1",
569 params![namespace],
570 )?;
571 report.projection_rows += tx.execute(
572 "DELETE FROM relation_versions WHERE scope_namespace = ?1",
573 params![namespace],
574 )?;
575 report.projection_rows += tx.execute(
576 "DELETE FROM entity_aliases WHERE scope_namespace = ?1",
577 params![namespace],
578 )?;
579 report.projection_rows += tx.execute(
580 "DELETE FROM evidence_refs
581 WHERE source_envelope_id IN (SELECT source_envelope_id FROM projection_import_log WHERE scope_namespace = ?1)",
582 params![namespace],
583 )?;
584 report.projection_rows += tx.execute(
585 "DELETE FROM episode_links
586 WHERE source_envelope_id IN (SELECT source_envelope_id FROM projection_import_log WHERE scope_namespace = ?1)",
587 params![namespace],
588 )?;
589 report.projection_rows += tx.execute(
590 "DELETE FROM projection_import_failures WHERE scope_namespace = ?1",
591 params![namespace],
592 )?;
593 report.projection_rows += tx.execute(
594 "DELETE FROM projection_import_log WHERE scope_namespace = ?1",
595 params![namespace],
596 )?;
597
598 Ok(report)
599 })
600}
601
602pub fn get_fact(conn: &Connection, fact_id: &str) -> Result<Option<Fact>, MemoryError> {
604 let result = conn.query_row(
605 "SELECT id, namespace, content, source, created_at, updated_at, metadata
606 FROM facts WHERE id = ?1",
607 params![fact_id],
608 |row| {
609 Ok((
610 row.get::<_, String>(0)?,
611 row.get::<_, String>(1)?,
612 row.get::<_, String>(2)?,
613 row.get::<_, Option<String>>(3)?,
614 row.get::<_, String>(4)?,
615 row.get::<_, String>(5)?,
616 row.get::<_, Option<String>>(6)?,
617 ))
618 },
619 );
620
621 match result {
622 Ok((id, namespace, content, source, created_at, updated_at, metadata_raw)) => {
623 Ok(Some(Fact {
624 metadata: parse_optional_json("facts", &id, "metadata", metadata_raw.as_deref())?,
625 id,
626 namespace,
627 content,
628 source,
629 created_at,
630 updated_at,
631 }))
632 }
633 Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
634 Err(err) => Err(MemoryError::Database(err)),
635 }
636}
637
638pub fn get_fact_embedding(
640 conn: &Connection,
641 fact_id: &str,
642) -> Result<Option<Vec<f32>>, MemoryError> {
643 let result: Result<Option<Vec<u8>>, _> = conn.query_row(
644 "SELECT embedding FROM facts WHERE id = ?1",
645 params![fact_id],
646 |row| row.get(0),
647 );
648
649 match result {
650 Ok(Some(bytes)) => Ok(Some(bytes_to_embedding(&bytes)?)),
651 Ok(None) => Ok(None),
652 Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
653 Err(err) => Err(MemoryError::Database(err)),
654 }
655}
656
657pub fn list_fact_namespaces(conn: &Connection) -> Result<Vec<String>, MemoryError> {
659 let mut stmt = conn.prepare("SELECT DISTINCT namespace FROM facts ORDER BY namespace")?;
660 let rows = stmt
661 .query_map([], |row| row.get::<_, String>(0))?
662 .collect::<Result<Vec<_>, _>>()?;
663 Ok(rows)
664}
665
666pub fn list_facts(
668 conn: &Connection,
669 namespace: &str,
670 limit: usize,
671 offset: usize,
672) -> Result<Vec<Fact>, MemoryError> {
673 let mut stmt = conn.prepare(
674 "SELECT id, namespace, content, source, created_at, updated_at, metadata
675 FROM facts
676 WHERE namespace = ?1
677 ORDER BY updated_at DESC
678 LIMIT ?2 OFFSET ?3",
679 )?;
680
681 let facts = stmt
682 .query_map(params![namespace, limit as i64, offset as i64], |row| {
683 Ok((
684 row.get::<_, String>(0)?,
685 row.get::<_, String>(1)?,
686 row.get::<_, String>(2)?,
687 row.get::<_, Option<String>>(3)?,
688 row.get::<_, String>(4)?,
689 row.get::<_, String>(5)?,
690 row.get::<_, Option<String>>(6)?,
691 ))
692 })?
693 .collect::<Result<Vec<_>, _>>()?
694 .into_iter()
695 .map(
696 |(id, namespace, content, source, created_at, updated_at, metadata_raw)| {
697 Ok(Fact {
698 metadata: parse_optional_json(
699 "facts",
700 &id,
701 "metadata",
702 metadata_raw.as_deref(),
703 )?,
704 id,
705 namespace,
706 content,
707 source,
708 created_at,
709 updated_at,
710 })
711 },
712 )
713 .collect::<Result<Vec<_>, MemoryError>>()?;
714
715 Ok(facts)
716}
717
718impl MemoryStore {
719 pub async fn add_fact(
721 &self,
722 namespace: &str,
723 content: &str,
724 source: Option<&str>,
725 metadata: Option<serde_json::Value>,
726 ) -> Result<String, MemoryError> {
727 self.add_fact_with_trace(namespace, content, source, metadata, None)
728 .await
729 }
730
731 pub async fn add_fact_with_trace(
733 &self,
734 namespace: &str,
735 content: &str,
736 source: Option<&str>,
737 metadata: Option<serde_json::Value>,
738 trace_ctx: Option<&TraceCtx>,
739 ) -> Result<String, MemoryError> {
740 self.validate_content("fact.content", content)?;
741
742 let ns_check = namespace.to_string();
745 let ct_check = content.to_string();
746 let existing_id = self
747 .with_read_conn(move |conn| {
748 let result: Option<String> = conn
749 .query_row(
750 "SELECT id FROM facts WHERE content = ?1 AND namespace = ?2 LIMIT 1",
751 rusqlite::params![&ct_check, &ns_check],
752 |row| row.get::<_, String>(0),
753 )
754 .ok();
755 Ok(result)
756 })
757 .await?;
758
759 if let Some(id) = existing_id {
760 return Ok(id);
761 }
762
763 let embedding = self.embed_text_internal(content).await?;
764 self.validate_embedding_dimensions(&embedding)?;
765 let embedding_bytes = db::embedding_to_bytes(&embedding);
766 let fact_id = uuid::Uuid::new_v4().to_string();
767 let max_facts_per_namespace = self.inner.config.limits.max_facts_per_namespace;
768
769 let quantizer = Quantizer::new(self.inner.config.embedding.dimensions);
770 let q8_bytes = quantizer
772 .quantize(&embedding)
773 .map(|qv| quantize::pack_quantized(&qv))
774 .ok();
775
776 let ns = namespace.to_string();
777 let ct = content.to_string();
778 let fid = fact_id.clone();
779 let src = source.map(|s| s.to_string());
780 let meta = merge_trace_ctx(metadata, trace_ctx);
781 self.with_write_conn(move |conn| {
782 let current_count: usize = conn.query_row(
783 "SELECT COUNT(*) FROM facts WHERE namespace = ?1",
784 rusqlite::params![&ns],
785 |row| row.get(0),
786 )?;
787 if current_count >= max_facts_per_namespace {
788 return Err(MemoryError::NamespaceFull {
789 namespace: ns.clone(),
790 count: current_count,
791 limit: max_facts_per_namespace,
792 });
793 }
794 insert_fact_with_fts_q8(
795 conn,
796 &fid,
797 &ns,
798 &ct,
799 &embedding_bytes,
800 q8_bytes.as_deref(),
801 src.as_deref(),
802 meta.as_ref(),
803 )
804 })
805 .await?;
806
807 #[cfg(feature = "hnsw")]
808 self.sync_pending_hnsw_ops_best_effort("add_fact").await;
809
810 Ok(fact_id)
811 }
812
813 pub async fn add_fact_with_embedding(
815 &self,
816 namespace: &str,
817 content: &str,
818 embedding: &[f32],
819 source: Option<&str>,
820 metadata: Option<serde_json::Value>,
821 ) -> Result<String, MemoryError> {
822 self.add_fact_with_embedding_and_trace(
823 namespace, content, embedding, source, metadata, None,
824 )
825 .await
826 }
827
828 pub async fn add_fact_with_embedding_and_trace(
830 &self,
831 namespace: &str,
832 content: &str,
833 embedding: &[f32],
834 source: Option<&str>,
835 metadata: Option<serde_json::Value>,
836 trace_ctx: Option<&TraceCtx>,
837 ) -> Result<String, MemoryError> {
838 self.validate_content("fact.content", content)?;
839 self.validate_embedding_dimensions(embedding)?;
840 let embedding_bytes = db::embedding_to_bytes(embedding);
841 let fact_id = uuid::Uuid::new_v4().to_string();
842 let max_facts_per_namespace = self.inner.config.limits.max_facts_per_namespace;
843
844 let quantizer = Quantizer::new(self.inner.config.embedding.dimensions);
845 let q8_bytes = quantizer
847 .quantize(embedding)
848 .map(|qv| quantize::pack_quantized(&qv))
849 .ok();
850
851 let ns = namespace.to_string();
852 let ct = content.to_string();
853 let fid = fact_id.clone();
854 let src = source.map(|s| s.to_string());
855 let meta = merge_trace_ctx(metadata, trace_ctx);
856 self.with_write_conn(move |conn| {
857 let current_count: usize = conn.query_row(
858 "SELECT COUNT(*) FROM facts WHERE namespace = ?1",
859 rusqlite::params![&ns],
860 |row| row.get(0),
861 )?;
862 if current_count >= max_facts_per_namespace {
863 return Err(MemoryError::NamespaceFull {
864 namespace: ns.clone(),
865 count: current_count,
866 limit: max_facts_per_namespace,
867 });
868 }
869 insert_fact_with_fts_q8(
870 conn,
871 &fid,
872 &ns,
873 &ct,
874 &embedding_bytes,
875 q8_bytes.as_deref(),
876 src.as_deref(),
877 meta.as_ref(),
878 )
879 })
880 .await?;
881
882 #[cfg(feature = "hnsw")]
883 self.sync_pending_hnsw_ops_best_effort("add_fact_with_embedding")
884 .await;
885
886 Ok(fact_id)
887 }
888
889 #[cfg(feature = "admin-ops")]
894 pub async fn update_fact(&self, fact_id: &str, content: &str) -> Result<(), MemoryError> {
895 self.validate_content("fact.content", content)?;
896 let embedding = self.embed_text_internal(content).await?;
897 self.validate_embedding_dimensions(&embedding)?;
898 let embedding_bytes = db::embedding_to_bytes(&embedding);
899 let q8_bytes = Quantizer::new(self.inner.config.embedding.dimensions)
901 .quantize(&embedding)
902 .map(|qv| quantize::pack_quantized(&qv))
903 .ok();
904
905 let fid = fact_id.to_string();
906 let ct = content.to_string();
907 self.with_write_conn(move |conn| {
908 update_fact_with_fts(conn, &fid, &ct, &embedding_bytes, q8_bytes.as_deref())
909 })
910 .await?;
911
912 #[cfg(feature = "hnsw")]
913 self.sync_pending_hnsw_ops_best_effort("update_fact").await;
914
915 self.clear_search_cache();
916
917 Ok(())
918 }
919
920 #[cfg(feature = "admin-ops")]
925 pub async fn delete_fact(&self, fact_id: &str) -> Result<(), MemoryError> {
926 let fid = fact_id.to_string();
927 self.with_write_conn(move |conn| delete_fact_with_fts(conn, &fid))
928 .await?;
929
930 #[cfg(feature = "hnsw")]
931 self.sync_pending_hnsw_ops_best_effort("delete_fact").await;
932
933 self.clear_search_cache();
934
935 Ok(())
936 }
937
938 pub async fn delete_namespace(
940 &self,
941 namespace: &str,
942 ) -> Result<NamespaceDeleteReport, MemoryError> {
943 let ns = namespace.to_string();
944 let count = self
945 .with_write_conn(move |conn| delete_namespace(conn, &ns))
946 .await?;
947
948 #[cfg(feature = "hnsw")]
949 self.sync_pending_hnsw_ops_best_effort("delete_namespace")
950 .await;
951
952 self.clear_search_cache();
953
954 Ok(count)
955 }
956
957 pub async fn get_fact(&self, fact_id: &str) -> Result<Option<Fact>, MemoryError> {
959 let fid = fact_id.to_string();
960 self.with_read_conn(move |conn| get_fact(conn, &fid)).await
961 }
962
963 pub async fn get_fact_embedding(&self, fact_id: &str) -> Result<Option<Vec<f32>>, MemoryError> {
965 let fid = fact_id.to_string();
966 self.with_read_conn(move |conn| get_fact_embedding(conn, &fid))
967 .await
968 }
969
970 pub async fn list_facts(
972 &self,
973 namespace: &str,
974 limit: usize,
975 offset: usize,
976 ) -> Result<Vec<Fact>, MemoryError> {
977 let ns = namespace.to_string();
978 self.with_read_conn(move |conn| list_facts(conn, &ns, limit, offset))
979 .await
980 }
981
982 pub async fn list_fact_namespaces(&self) -> Result<Vec<String>, MemoryError> {
984 self.with_read_conn(move |conn| list_fact_namespaces(conn)).await
985 }
986}