Skip to main content

kyval/adapter/
libsql.rs

1// Copyright © 2024 Aris Ripandi - All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9/*!
10 * Portions of this file are based on code from `chrisllontop/keyv-rust`.
11 * MIT Licensed, Copyright (c) 2023 Christian Llontop.
12 *
13 * Credits to Alexandru Bereghici: https://github.com/chrisllontop/keyv-rust
14 */
15
16use libsql::{params, params_from_iter};
17use libsql::{Builder, Connection};
18use serde_json::Value;
19use std::future::Future;
20use std::path::PathBuf;
21use std::pin::Pin;
22use std::sync::Arc;
23use std::time::Instant;
24
25use crate::{Store, StoreError, StoreModel, DEFAULT_NAMESPACE_NAME};
26
27/// Builder for creating a `KyvalStore`.
28///
29/// This builder allows for configuring a `KyvalStore` with custom
30/// settings such as a specific database file URI and a table name.
31/// It provides a flexible way to initialize the store depending on the
32/// application's requirements.
33///
34/// # Examples
35///
36/// ## Initializing with a Database File URI
37///
38/// ```rust,no_run
39/// # use kyval::adapter::KyvalStoreBuilder;
40/// #[tokio::main]
41/// async fn main(){
42///     let store = KyvalStoreBuilder::new()
43///         .uri(":memory:")
44///         .table_name("custom_table_name")
45///         .build()
46///         .await
47///         .unwrap();
48///  }
49/// ```
50///
51/// ## Using an Existing Connection
52///
53/// ```rust,no_run
54/// # use std::sync::Arc;
55/// # use kyval::adapter::KyvalStoreBuilder;
56/// # use libsql::Connection;
57/// #[tokio::main]
58/// async fn main() {
59///     let db_url = format!("libsql://localhost:8080?tls=0");
60///
61///     let conn = libsql::Builder::new_remote(db_url, format!(""))
62///         .build()
63///         .await
64///         .unwrap()
65///         .connect()
66///         .unwrap();
67///
68///     let store = KyvalStoreBuilder::new()
69///         .connnection(conn.into())
70///         .table_name("custom_table_name")
71///         .build()
72///         .await
73///         .unwrap();
74/// }
75/// ```
76pub struct KyvalStoreBuilder {
77    uri: Option<PathBuf>,
78    token: Option<String>,
79    connnection: Option<Arc<Connection>>,
80    table_name: Option<String>,
81}
82
83impl KyvalStoreBuilder {
84    pub fn new() -> Self {
85        Self {
86            uri: None,
87            token: None,
88            connnection: None,
89            table_name: None,
90        }
91    }
92
93    /// Sets the table name for the `KyvalStore`.
94    ///
95    /// This method configures the table name to be used by the store. If not set,
96    /// `DEFAULT_TABLE_NAME` from the configuration will be used.
97    pub fn table_name<S: Into<String>>(mut self, table: S) -> Self {
98        self.table_name = Some(table.into());
99        self
100    }
101
102    /// Sets the database URI for connecting to the SQLite database.
103    ///
104    /// This method configures the database URI. It's required if no existing connection is provided.
105    pub fn uri<S: Into<PathBuf>>(mut self, uri: S) -> Self {
106        self.uri = Some(uri.into());
107        self
108    }
109
110    /// Sets the database token for authentication with the database.
111    ///
112    /// This method configures the database token. It's required if using authentication.
113    pub fn token<S: Into<String>>(mut self, token: S) -> Self {
114        self.token = Some(token.into());
115        self
116    }
117
118    /// Uses an existing connection for the `KyvalStore`.
119    ///
120    /// This method allows for using an already configured `Pool`. If set,
121    /// the `uri` option is ignored.
122    pub fn connnection(mut self, connnection: Arc<Connection>) -> Self {
123        self.connnection = Some(connnection);
124        self
125    }
126
127    /// Builds the `KyvalStore` based on the provided configurations.
128    ///
129    /// Finalizes the builder and creates an `KyvalStore` instance.
130    /// It requires either a database URI or an existing connection to be set.
131    ///
132    /// # Returns
133    /// This method returns a `Result` which, on success, contains the initialized `KyvalStore`.
134    /// On failure, it returns a `StoreError` indicating what went wrong during the initialization.
135    pub async fn build(self) -> Result<KyvalStore, StoreError> {
136        let connnection = match self.connnection {
137            Some(connnection) => connnection,
138            None => {
139                let path = self
140                    .uri
141                    .expect("KyvalStore requires either a URI or an existing connnection to be set");
142
143                // If the token is set, use the remote database connection.
144                let db = if let Some(token) = self.token {
145                    Builder::new_remote(path.display().to_string(), token)
146                        .build()
147                        .await
148                        .map_err(|_| {
149                            StoreError::ConnectionError(
150                                "Failed to create database connection"
151                                    .to_string(),
152                            )
153                        })?
154                } else {
155                    Builder::new_local(path).build().await.map_err(|_| {
156                        StoreError::ConnectionError(
157                            "Failed to create database connection".to_string(),
158                        )
159                    })?
160                };
161
162                let conn = db.connect().map_err(|_| {
163                    StoreError::ConnectionError(
164                        "Failed to create database connnection".to_string(),
165                    )
166                })?;
167
168                Arc::new(conn)
169            }
170        };
171
172        let table_name = self.table_name.unwrap_or_else(|| {
173            log::warn!("Table name not set, using default table name");
174            DEFAULT_NAMESPACE_NAME.to_string()
175        });
176
177        Ok(KyvalStore {
178            connnection,
179            table_name,
180        })
181    }
182}
183
184pub struct KyvalStore {
185    pub(crate) connnection: Arc<Connection>,
186    pub(crate) table_name: String,
187}
188
189impl KyvalStore {
190    fn get_table_name(&self) -> String {
191        self.table_name.clone()
192    }
193}
194
195impl Store for KyvalStore {
196    fn initialize(
197        &self,
198    ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + '_>> {
199        let query = format!(
200            r#"
201                CREATE TABLE IF NOT EXISTS {table_name} (
202                    key TEXT PRIMARY KEY,
203                    value TEXT NOT NULL,
204                    updated_at TEXT DEFAULT (datetime('now', 'localtime')),
205                    UNIQUE(key)
206                ) STRICT;
207                CREATE INDEX IF NOT EXISTS {table_name}_key_idx ON {table_name} (key);
208                CREATE TRIGGER IF NOT EXISTS {table_name}_update_trigger
209                AFTER UPDATE ON {table_name}
210                BEGIN
211                    UPDATE {table_name} SET updated_at = datetime('now', 'localtime') WHERE key = NEW.key;
212                END;
213            "#,
214            table_name = self.get_table_name()
215        );
216
217        let conn = &*self.connnection;
218
219        Box::pin(async move {
220            conn.execute_batch(&query).await.map_err(|e| {
221                StoreError::QueryError(format!(
222                    "Failed to initialize the database table: {}",
223                    e
224                ))
225            })?;
226
227            Ok(())
228        })
229    }
230
231    fn get(
232        &self,
233        key: &str,
234    ) -> Pin<
235        Box<dyn Future<Output = Result<Option<Value>, StoreError>> + Send + '_>,
236    > {
237        let query = format!(
238            "SELECT value FROM {} WHERE key = ?1 LIMIT 1",
239            self.get_table_name()
240        );
241
242        let conn = &*self.connnection;
243        let key = key.to_string();
244
245        Box::pin(async move {
246            let start = Instant::now();
247
248            let mut stmt = conn.prepare(&query).await.map_err(|e| {
249                StoreError::QueryError(format!(
250                    "Failed to set the statement: {:?}",
251                    e
252                ))
253            })?;
254
255            let result =
256                stmt.query_row(params![key.clone()]).await.map_err(|e| {
257                    StoreError::QueryError(format!(
258                        "Failed to fetch the value: {:?}",
259                        e
260                    ))
261                })?;
262
263            let row_value: String = result.get(0).map_err(|e| {
264                StoreError::QueryError(format!(
265                    "Failed to get the value: {:?}",
266                    e
267                ))
268            })?;
269
270            let value = serde_json::to_value(row_value)
271                .map_err(|e| StoreError::SerializationError { source: e })?;
272
273            let duration = start.elapsed();
274            log::debug!(
275                "Kyval store get: {:?} | {} | {:?}",
276                duration,
277                key,
278                value
279            );
280
281            Ok(Some(value))
282        })
283    }
284
285    fn list(
286        &self,
287    ) -> Pin<
288        Box<
289            dyn Future<Output = Result<Vec<StoreModel>, StoreError>>
290                + Send
291                + '_,
292        >,
293    > {
294        let query = format!(
295            "SELECT key, value FROM {} ORDER BY key ASC;",
296            self.get_table_name()
297        );
298
299        let conn = &*self.connnection;
300
301        Box::pin(async move {
302            let start = Instant::now();
303
304            let mut stmt = conn.prepare(&query).await.map_err(|e| {
305                StoreError::QueryError(format!(
306                    "Failed to set the statement: {:?}",
307                    e
308                ))
309            })?;
310
311            let mut results = stmt.query(params![]).await.map_err(|e| {
312                StoreError::QueryError(format!(
313                    "Failed to fetch the value: {:?}",
314                    e
315                ))
316            })?;
317
318            let mut items: Vec<StoreModel> = Vec::new();
319
320            while let Some(row) = results.next().await.map_err(|e| {
321                StoreError::QueryError(format!(
322                    "Failed to iterate rows: {:?}",
323                    e
324                ))
325            })? {
326                let key: String = row.get(0).map_err(|e| {
327                    StoreError::QueryError(format!(
328                        "Failed to get the value: {:?}",
329                        e
330                    ))
331                })?;
332                let row_value: String = row.get(1).map_err(|e| {
333                    StoreError::QueryError(format!(
334                        "Failed to get the value: {:?}",
335                        e
336                    ))
337                })?;
338                let value = serde_json::to_value(row_value).map_err(|e| {
339                    StoreError::SerializationError { source: e }
340                })?;
341
342                items.push(StoreModel { key, value });
343            }
344
345            let duration = start.elapsed();
346            log::debug!("Kyval store list: {:?} | {:?}", duration, items);
347
348            Ok(items)
349        })
350    }
351
352    fn set(
353        &self,
354        key: &str,
355        value: Value,
356        _ttl: Option<u64>,
357    ) -> Pin<
358        Box<
359            dyn Future<Output = Result<Option<StoreModel>, StoreError>>
360                + Send
361                + '_,
362        >,
363    > {
364        let query = format!(
365            "INSERT INTO {} (key, value) VALUES (?1, ?2) ON CONFLICT(key) DO UPDATE SET value = EXCLUDED.value",
366            self.get_table_name()
367        );
368
369        let conn = &*self.connnection;
370        let key = key.to_string();
371
372        Box::pin(async move {
373            let start = Instant::now();
374
375            let value_str = match value {
376                Value::String(ref s) => s.clone(), // If the value is a string, use the original string.
377                Value::Number(ref n) => n.to_string(), // If the value is a number, use the number string representation.
378                Value::Null => "".to_string(), // If value is null, use the empty string.
379                _ => value.to_string(), // If the value is an object or other type, serialize it as JSON.
380            };
381
382            let mut stmt = conn.prepare(&query).await.map_err(|_| {
383                StoreError::QueryError(
384                    "Failed to set the statement".to_string(),
385                )
386            })?;
387
388            let mut response = stmt
389                .query(params![key.clone(), value_str.clone()])
390                .await
391                .map_err(|_| {
392                    StoreError::QueryError(
393                        "Failed to set the value".to_string(),
394                    )
395                })?;
396
397            let result = match response.next().await.map_err(|e| {
398                StoreError::QueryError(format!(
399                    "Failed to iterate rows: {:?}",
400                    e
401                ))
402            })? {
403                Some(row) => {
404                    let row_key = row.get_str(0).map_err(|e| {
405                        StoreError::QueryError(format!(
406                            "Failed to get the key: {:?}",
407                            e
408                        ))
409                    })?;
410
411                    let row_value = row.get_value(1).map_err(|e| {
412                        StoreError::QueryError(format!(
413                            "Failed to get the value: {:?}",
414                            e
415                        ))
416                    })?;
417
418                    Some(StoreModel {
419                        key: row_key.to_string(),
420                        value: serde_json::to_value(row_value).map_err(
421                            |e| StoreError::SerializationError { source: e },
422                        )?,
423                    })
424                }
425                None => None,
426            };
427
428            let duration = start.elapsed();
429            log::debug!(
430                "Kyval store set: {:?} | {} | {}",
431                duration,
432                key,
433                value_str
434            );
435
436            Ok(result)
437        })
438    }
439
440    fn remove(
441        &self,
442        key: &str,
443    ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + '_>> {
444        let query =
445            format!("DELETE FROM {} WHERE key = ?1", self.get_table_name());
446
447        let conn = &*self.connnection;
448
449        let key = key.to_string();
450
451        Box::pin(async move {
452            let start = Instant::now();
453
454            let mut stmt = conn.prepare(&query).await.map_err(|_| {
455                StoreError::QueryError(
456                    "Failed to set the statement".to_string(),
457                )
458            })?;
459
460            stmt.execute(params![key.clone()]).await.map_err(|_| {
461                StoreError::QueryError("Failed to remove the key".to_string())
462            })?;
463
464            let duration = start.elapsed();
465            log::debug!("Kyval store remove: {:?} | {}", duration, key);
466
467            Ok(())
468        })
469    }
470
471    fn remove_many(
472        &self,
473        keys: &[&str],
474    ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + '_>> {
475        let conn = &*self.connnection;
476
477        let placeholder = keys
478            .iter()
479            .enumerate()
480            .map(|(i, _)| format!("?{}", i + 1))
481            .collect::<Vec<String>>()
482            .join(", ");
483
484        let query = format!(
485            "DELETE FROM {} WHERE key IN ({})",
486            self.get_table_name(),
487            placeholder
488        );
489
490        let keys = keys.iter().map(|k| k.to_string()).collect::<Vec<String>>();
491
492        Box::pin(async move {
493            let start = Instant::now();
494
495            let mut stmt = conn.prepare(&query).await.map_err(|_| {
496                StoreError::QueryError(
497                    "Failed to set the statement".to_string(),
498                )
499            })?;
500
501            stmt.execute(params_from_iter(keys)).await.map_err(|_| {
502                StoreError::QueryError("Failed to remove the key".to_string())
503            })?;
504
505            let duration = start.elapsed();
506            log::debug!("Kyval store remove_many: {:?}", duration);
507
508            Ok(())
509        })
510    }
511
512    fn clear(
513        &self,
514    ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + '_>> {
515        let query = format!("DELETE FROM {}", self.get_table_name());
516
517        let conn = &*self.connnection;
518
519        Box::pin(async move {
520            conn.execute(&query, params![]).await.map_err(|_| {
521                StoreError::QueryError("Failed to clear the table".to_string())
522            })?;
523
524            Ok(())
525        })
526    }
527}