Skip to main content

zeph_memory/sqlite/
history.rs

1// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use super::SqliteStore;
5use crate::error::MemoryError;
6
7impl SqliteStore {
8    /// Load the most recent input history entries, ordered chronologically.
9    ///
10    /// # Errors
11    ///
12    /// Returns an error if the query fails.
13    pub async fn load_input_history(&self, limit: i64) -> Result<Vec<String>, MemoryError> {
14        let rows: Vec<(String,)> =
15            sqlx::query_as("SELECT input FROM input_history ORDER BY id ASC LIMIT ?")
16                .bind(limit)
17                .fetch_all(&self.pool)
18                .await?;
19        Ok(rows.into_iter().map(|(s,)| s).collect())
20    }
21
22    /// Persist a new input history entry.
23    ///
24    /// # Errors
25    ///
26    /// Returns an error if the insert fails.
27    pub async fn save_input_entry(&self, text: &str) -> Result<(), MemoryError> {
28        sqlx::query("INSERT INTO input_history (input) VALUES (?)")
29            .bind(text)
30            .execute(&self.pool)
31            .await?;
32        Ok(())
33    }
34
35    /// Delete all input history entries.
36    ///
37    /// # Errors
38    ///
39    /// Returns an error if the delete fails.
40    pub async fn clear_input_history(&self) -> Result<(), MemoryError> {
41        sqlx::query("DELETE FROM input_history")
42            .execute(&self.pool)
43            .await?;
44        Ok(())
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51
52    async fn test_store() -> SqliteStore {
53        SqliteStore::new(":memory:").await.unwrap()
54    }
55
56    #[tokio::test]
57    async fn load_input_history_empty() {
58        let store = test_store().await;
59        let entries = store.load_input_history(100).await.unwrap();
60        assert!(entries.is_empty());
61    }
62
63    #[tokio::test]
64    async fn save_and_load_input_history() {
65        let store = test_store().await;
66        store.save_input_entry("hello").await.unwrap();
67        store.save_input_entry("world").await.unwrap();
68        let entries = store.load_input_history(100).await.unwrap();
69        assert_eq!(entries, vec!["hello", "world"]);
70    }
71
72    #[tokio::test]
73    async fn load_input_history_respects_limit() {
74        let store = test_store().await;
75        for i in 0..10 {
76            store.save_input_entry(&format!("entry {i}")).await.unwrap();
77        }
78        let entries = store.load_input_history(3).await.unwrap();
79        assert_eq!(entries.len(), 3);
80        assert_eq!(entries[0], "entry 0");
81    }
82
83    #[tokio::test]
84    async fn clear_input_history_removes_all() {
85        let store = test_store().await;
86        store.save_input_entry("a").await.unwrap();
87        store.save_input_entry("b").await.unwrap();
88        store.clear_input_history().await.unwrap();
89        let entries = store.load_input_history(100).await.unwrap();
90        assert!(entries.is_empty());
91    }
92}