gemini_adk_rs/session/
sqlite.rs

1//! SQLite session service — lightweight persistent session storage.
2//!
3//! Mirrors ADK-Python's `sqlite_session_service`. Provides session
4//! persistence using a local SQLite database file.
5//!
6//! When the `database-sessions` feature is enabled this delegates to the real
7//! `sqlx`-backed [`DatabaseSessionService`](super::DatabaseSessionService).
8//! Without that feature it falls back to an in-memory stub so the default
9//! build stays dependency-free.
10
11use std::path::PathBuf;
12
13use async_trait::async_trait;
14
15use super::{Session, SessionError, SessionId, SessionService};
16use crate::events::Event;
17
18/// Configuration for the SQLite session service.
19#[derive(Debug, Clone)]
20pub struct SqliteSessionConfig {
21    /// Path to the SQLite database file.
22    pub db_path: PathBuf,
23}
24
25impl SqliteSessionConfig {
26    /// Create a config for an in-memory SQLite database.
27    pub fn in_memory() -> Self {
28        Self {
29            db_path: PathBuf::from(":memory:"),
30        }
31    }
32}
33
34/// Backend used by [`SqliteSessionService`].
35///
36/// With `database-sessions` this is the real `sqlx`-backed service; otherwise
37/// it is an in-memory stub.
38#[cfg(feature = "database-sessions")]
39type Backend = super::DatabaseSessionService;
40#[cfg(not(feature = "database-sessions"))]
41type Backend = super::InMemorySessionService;
42
43/// Session service backed by SQLite.
44///
45/// Provides lightweight, file-based session persistence suitable for
46/// single-process deployments and development environments.
47///
48/// The database schema is automatically created on first use.
49pub struct SqliteSessionService {
50    config: SqliteSessionConfig,
51    inner: Backend,
52}
53
54impl SqliteSessionService {
55    /// Create a new SQLite session service.
56    ///
57    /// With the `database-sessions` feature this builds a real `sqlx` pool
58    /// (opened lazily on first use). Without it, an in-memory stub is used.
59    pub fn new(config: SqliteSessionConfig) -> Self {
60        let inner = Self::build_backend(&config);
61        Self { config, inner }
62    }
63
64    #[cfg(feature = "database-sessions")]
65    fn build_backend(config: &SqliteSessionConfig) -> Backend {
66        let path = config.db_path.to_string_lossy();
67        // `:memory:` maps to sqlx's in-memory URL; file paths map to
68        // `sqlite://<path>`.
69        let url = if path == ":memory:" {
70            "sqlite::memory:".to_string()
71        } else {
72            format!("sqlite://{path}")
73        };
74        super::DatabaseSessionService::new(url)
75    }
76
77    #[cfg(not(feature = "database-sessions"))]
78    fn build_backend(_config: &SqliteSessionConfig) -> Backend {
79        super::InMemorySessionService::new()
80    }
81
82    /// Returns the configured database path.
83    pub fn db_path(&self) -> &std::path::Path {
84        &self.config.db_path
85    }
86}
87
88#[async_trait]
89impl SessionService for SqliteSessionService {
90    async fn create_session(&self, app_name: &str, user_id: &str) -> Result<Session, SessionError> {
91        self.inner.create_session(app_name, user_id).await
92    }
93
94    async fn get_session(&self, id: &SessionId) -> Result<Option<Session>, SessionError> {
95        self.inner.get_session(id).await
96    }
97
98    async fn list_sessions(
99        &self,
100        app_name: &str,
101        user_id: &str,
102    ) -> Result<Vec<Session>, SessionError> {
103        self.inner.list_sessions(app_name, user_id).await
104    }
105
106    async fn delete_session(&self, id: &SessionId) -> Result<(), SessionError> {
107        self.inner.delete_session(id).await
108    }
109
110    async fn append_event(&self, id: &SessionId, event: Event) -> Result<(), SessionError> {
111        self.inner.append_event(id, event).await
112    }
113
114    async fn get_events(&self, id: &SessionId) -> Result<Vec<Event>, SessionError> {
115        self.inner.get_events(id).await
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[tokio::test]
124    async fn create_and_get() {
125        let svc = SqliteSessionService::new(SqliteSessionConfig::in_memory());
126        let session = svc.create_session("app", "user").await.unwrap();
127        let fetched = svc.get_session(&session.id).await.unwrap();
128        assert!(fetched.is_some());
129    }
130
131    #[test]
132    fn db_path() {
133        let svc = SqliteSessionService::new(SqliteSessionConfig {
134            db_path: PathBuf::from("/tmp/test.db"),
135        });
136        assert_eq!(svc.db_path(), std::path::Path::new("/tmp/test.db"));
137    }
138
139    #[test]
140    fn in_memory_config() {
141        let config = SqliteSessionConfig::in_memory();
142        assert_eq!(config.db_path, PathBuf::from(":memory:"));
143    }
144}