Skip to main content

valence_backend_sqlite/
backend.rs

1//! SQLite storage engine.
2
3use sqlx::sqlite::{SqliteConnectOptions, SqlitePool};
4use std::str::FromStr;
5
6use valence_backend_sql::{
7    create_record_sqlite, define_unique_index_sqlite, delete_record_sqlite, ensure_table_sqlite,
8    execute_select_sqlite, get_edge_targets_sqlite, get_record_sqlite, merge_record_sqlite,
9    relate_edge_sqlite, sql_capabilities, ttl_deferred, unrelate_edge_sqlite, update_record_sqlite,
10};
11use valence_core::backend::DatabaseBackend;
12use valence_core::compiled_query::CompiledQuery;
13use valence_core::error::{Error, Result};
14use valence_core::record_id::RecordId;
15use valence_core::ttl::SchemaTtlPolicy;
16use valence_core::{Database, DatabaseFromEngine, KnownEngines};
17
18/// Stable engine slug for router keys (`sqlite:logical_name`).
19pub const ENGINE_ID: &str = KnownEngines::SQLITE;
20
21/// Schema evaluator const for `database:` routing.
22pub const PRIMARY: DatabaseFromEngine = Database::from_engine("primary", ENGINE_ID);
23
24/// SQLite-backed [`DatabaseBackend`] using JSON document rows.
25///
26/// # Examples
27///
28/// ```ignore
29/// use std::sync::Arc;
30/// use valence::{
31///     valence_schema, Database, DatabaseFromEngine, FieldType, SqliteBackend, Valence,
32///     SQLITE_ENGINE_ID,
33/// };
34///
35/// const COUNTER_DB: DatabaseFromEngine =
36///     Database::from_engine("default", SQLITE_ENGINE_ID);
37///
38/// valence_schema! {
39///     Counter {
40///         table: "counter",
41///         version: "0.1.0",
42///         database: COUNTER_DB,
43///         fields: [
44///             id: { r#type: FieldType::String, primary_key: true, required: true },
45///             value: { r#type: FieldType::Integer, required: true },
46///         ],
47///     }
48/// }
49///
50/// let backend = SqliteBackend::connect_memory().await?;
51/// let valence = Valence::builder()
52///     .add_backend("default", Arc::new(backend))
53///     .build()?;
54/// assert_eq!(
55///     valence.backend_for_table("counter")?.engine_id(),
56///     SQLITE_ENGINE_ID
57/// );
58/// # Ok::<(), valence::Error>(())
59/// ```
60#[derive(Debug, Clone)]
61pub struct SqliteBackend {
62    pool: SqlitePool,
63}
64
65impl SqliteBackend {
66    /// Connect to an in-memory SQLite database.
67    pub async fn connect_memory() -> Result<Self> {
68        Self::connect(":memory:").await
69    }
70
71    /// Connect to a SQLite database at `path` (`:memory:` for ephemeral).
72    pub async fn connect(path: &str) -> Result<Self> {
73        let options = SqliteConnectOptions::from_str(path)
74            .or_else(|_| SqliteConnectOptions::from_str(&format!("sqlite:{path}")))
75            .map_err(|e| Error::Database(e.to_string()))?
76            .create_if_missing(true);
77        let pool = SqlitePool::connect_with(options)
78            .await
79            .map_err(|e| Error::Database(e.to_string()))?;
80        valence_backend_sql::ensure_edges_sqlite(&pool).await?;
81        Ok(Self { pool })
82    }
83
84    /// Borrow the underlying pool.
85    pub fn pool(&self) -> &SqlitePool {
86        &self.pool
87    }
88}
89
90#[async_trait::async_trait]
91impl DatabaseBackend for SqliteBackend {
92    fn engine_id(&self) -> &'static str {
93        ENGINE_ID
94    }
95
96    fn capabilities(&self) -> valence_core::BackendCapabilities {
97        sql_capabilities("sqlite")
98    }
99
100    async fn execute_compiled_query(
101        &self,
102        compiled: &CompiledQuery,
103    ) -> Result<Vec<serde_json::Value>> {
104        execute_select_sqlite(&self.pool, compiled, "").await
105    }
106
107    async fn ensure_schemaless_table(&self, table: &str) -> Result<()> {
108        ensure_table_sqlite(&self.pool, table).await
109    }
110
111    async fn get_record(&self, table: &str, id: &str) -> Result<Option<serde_json::Value>> {
112        get_record_sqlite(&self.pool, table, id).await
113    }
114
115    async fn create_record(
116        &self,
117        table: &str,
118        content: serde_json::Value,
119    ) -> Result<serde_json::Value> {
120        create_record_sqlite(&self.pool, table, content).await
121    }
122
123    async fn update_record(
124        &self,
125        table: &str,
126        id: &str,
127        content: serde_json::Value,
128    ) -> Result<serde_json::Value> {
129        update_record_sqlite(&self.pool, table, id, content).await
130    }
131
132    async fn merge_record(
133        &self,
134        table: &str,
135        id: &str,
136        patch: serde_json::Value,
137    ) -> Result<serde_json::Value> {
138        merge_record_sqlite(&self.pool, table, id, patch).await
139    }
140
141    async fn upsert_record(
142        &self,
143        table: &str,
144        id: &str,
145        content: serde_json::Value,
146    ) -> Result<serde_json::Value> {
147        if self.get_record(table, id).await?.is_some() {
148            self.update_record(table, id, content).await
149        } else {
150            let mut c = content;
151            if let Some(obj) = c.as_object_mut() {
152                obj.insert("id".into(), serde_json::json!({"table": table, "id": id}));
153            }
154            self.create_record(table, c).await
155        }
156    }
157
158    async fn delete_record(&self, table: &str, id: &str) -> Result<()> {
159        delete_record_sqlite(&self.pool, table, id).await
160    }
161
162    async fn relate_edge(&self, from: &RecordId, edge_table: &str, to: &RecordId) -> Result<()> {
163        relate_edge_sqlite(&self.pool, from, edge_table, to).await
164    }
165
166    async fn unrelate_edge(&self, from: &RecordId, edge_table: &str, to: &RecordId) -> Result<()> {
167        unrelate_edge_sqlite(&self.pool, from, edge_table, to).await
168    }
169
170    async fn get_edge_targets(&self, from: &RecordId, edge_table: &str) -> Result<Vec<RecordId>> {
171        get_edge_targets_sqlite(&self.pool, from, edge_table).await
172    }
173
174    async fn define_unique_index(&self, table: &str, field: &str) -> Result<()> {
175        define_unique_index_sqlite(&self.pool, table, field).await
176    }
177
178    fn ttl_capability(&self) -> valence_core::ttl::BackendTtlCapability {
179        ttl_deferred()
180    }
181
182    async fn apply_ttl_policy(&self, _table: &str, _policy: &SchemaTtlPolicy) -> Result<()> {
183        Ok(())
184    }
185}