Skip to main content

sqlite_graphrag/storage/memories/
soft_delete.rs

1//! Soft-delete lifecycle helpers for the `memories` table.
2//!
3//! Soft-delete sets `deleted_at = unixepoch()` without removing versions or
4//! chunks, so `restore` can undo until `purge` reclaims storage permanently.
5
6use crate::errors::AppError;
7use rusqlite::{params, Connection};
8
9/// Looks up a memory by `(namespace, name)` regardless of deletion state.
10///
11/// Returns `Some((id, is_deleted))` when the row exists.
12/// `is_deleted` is `true` when `deleted_at IS NOT NULL`.
13///
14/// # Errors
15///
16/// Propagates [`AppError::Database`] on SQLite failures.
17pub fn find_by_name_any_state(
18    conn: &Connection,
19    namespace: &str,
20    name: &str,
21) -> Result<Option<(i64, bool)>, AppError> {
22    let mut stmt = conn.prepare_cached(
23        "SELECT id, (deleted_at IS NOT NULL) AS is_deleted
24         FROM memories WHERE namespace = ?1 AND name = ?2",
25    )?;
26    let result = stmt.query_row(params![namespace, name], |r| {
27        Ok((r.get::<_, i64>(0)?, r.get::<_, bool>(1)?))
28    });
29    match result {
30        Ok(row) => Ok(Some(row)),
31        Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
32        Err(e) => Err(AppError::Database(e)),
33    }
34}
35
36/// Clears `deleted_at` to restore a soft-deleted memory.
37///
38/// # Errors
39///
40/// Propagates [`AppError::Database`] on SQLite failures.
41pub fn clear_deleted_at(conn: &Connection, memory_id: i64) -> Result<(), AppError> {
42    conn.execute(
43        "UPDATE memories SET deleted_at = NULL WHERE id = ?1",
44        params![memory_id],
45    )?;
46    Ok(())
47}
48
49/// Soft-deletes a memory by setting `deleted_at = unixepoch()`.
50///
51/// Versions and chunks are preserved so `restore` can undo the operation
52/// until a subsequent `purge` reclaims the storage permanently.
53///
54/// # Returns
55///
56/// `Ok(true)` when a live memory was soft-deleted, `Ok(false)` when no
57/// matching live row existed.
58///
59/// # Errors
60///
61/// Returns `Err(AppError::Database)` on any `rusqlite` failure.
62pub fn soft_delete(conn: &Connection, namespace: &str, name: &str) -> Result<bool, AppError> {
63    let affected = conn.execute(
64        "UPDATE memories SET deleted_at = unixepoch() WHERE namespace=?1 AND name=?2 AND deleted_at IS NULL",
65        params![namespace, name],
66    )?;
67    Ok(affected == 1)
68}
69
70/// Fetches all memory_ids in a namespace that are soft-deleted and whose
71/// `deleted_at` is older than `before_ts` (unix epoch seconds).
72///
73/// Used by `purge` to collect stale rows for permanent deletion.
74///
75/// # Errors
76///
77/// Returns `Err(AppError::Database)` on any `rusqlite` failure.
78pub fn list_deleted_before(
79    conn: &Connection,
80    namespace: &str,
81    before_ts: i64,
82) -> Result<Vec<i64>, AppError> {
83    let mut stmt = conn.prepare_cached(
84        "SELECT id FROM memories WHERE namespace = ?1 AND deleted_at IS NOT NULL AND deleted_at < ?2",
85    )?;
86    let ids = stmt
87        .query_map(params![namespace, before_ts], |r| r.get::<_, i64>(0))?
88        .collect::<Result<Vec<_>, _>>()?;
89    Ok(ids)
90}