Skip to main content

cdk_sqlite/
common.rs

1use std::path::PathBuf;
2use std::sync::atomic::AtomicBool;
3use std::sync::Arc;
4use std::time::Duration;
5
6use cdk_sql_common::pool::{self, DatabasePool};
7use cdk_sql_common::value::Value;
8use rusqlite::Connection;
9
10use crate::async_sqlite;
11
12/// The config need to create a new SQLite connection
13#[derive(Clone, Debug)]
14pub struct Config {
15    path: Option<String>,
16    password: Option<String>,
17}
18
19impl pool::DatabaseConfig for Config {
20    fn default_timeout(&self) -> Duration {
21        Duration::from_secs(5)
22    }
23
24    fn max_size(&self) -> usize {
25        if self.path.is_none() {
26            1
27        } else {
28            20
29        }
30    }
31}
32
33/// Sqlite connection manager
34#[derive(Debug)]
35pub struct SqliteConnectionManager;
36
37impl DatabasePool for SqliteConnectionManager {
38    type Config = Config;
39
40    type Connection = async_sqlite::AsyncSqlite;
41
42    type Error = rusqlite::Error;
43
44    fn new_resource(
45        config: &Self::Config,
46        _stale: Arc<AtomicBool>,
47        _timeout: Duration,
48    ) -> Result<Self::Connection, pool::Error<Self::Error>> {
49        let conn = if let Some(path) = config.path.as_ref() {
50            // Check if parent directory exists before attempting to open database
51            let path_buf = PathBuf::from(path);
52            if let Some(parent) = path_buf.parent() {
53                if !parent.to_str().unwrap_or_default().is_empty() && !parent.exists() {
54                    return Err(pool::Error::Resource(rusqlite::Error::InvalidPath(
55                        path_buf.clone(),
56                    )));
57                }
58            }
59            Connection::open(path)?
60        } else {
61            Connection::open_in_memory()?
62        };
63
64        if let Some(password) = config.password.as_ref() {
65            conn.execute_batch(&format!("pragma key = '{password}';"))?;
66        }
67
68        conn.execute_batch(
69            r#"
70            pragma busy_timeout = 10000;
71            pragma journal_mode = WAL;
72            pragma synchronous = normal;
73            pragma temp_store = memory;
74            pragma mmap_size = 5242880;
75            pragma cache = shared;
76            "#,
77        )?;
78
79        conn.busy_timeout(Duration::from_secs(10))?;
80
81        Ok(async_sqlite::AsyncSqlite::new(conn))
82    }
83}
84
85impl From<PathBuf> for Config {
86    fn from(path: PathBuf) -> Self {
87        path.to_str().unwrap_or_default().into()
88    }
89}
90
91impl From<(PathBuf, String)> for Config {
92    fn from((path, password): (PathBuf, String)) -> Self {
93        (path.to_str().unwrap_or_default(), password.as_str()).into()
94    }
95}
96
97impl From<&PathBuf> for Config {
98    fn from(path: &PathBuf) -> Self {
99        path.to_str().unwrap_or_default().into()
100    }
101}
102
103impl From<&str> for Config {
104    fn from(path: &str) -> Self {
105        if path.contains(":memory:") {
106            Config {
107                path: None,
108                password: None,
109            }
110        } else {
111            Config {
112                path: Some(path.to_owned()),
113                password: None,
114            }
115        }
116    }
117}
118
119impl From<(&str, &str)> for Config {
120    fn from((path, pass): (&str, &str)) -> Self {
121        if path.contains(":memory:") {
122            Config {
123                path: None,
124                password: Some(pass.to_owned()),
125            }
126        } else {
127            Config {
128                path: Some(path.to_owned()),
129                password: Some(pass.to_owned()),
130            }
131        }
132    }
133}
134
135/// Convert cdk_sql_common::value::Value to rusqlite Value
136#[inline(always)]
137pub fn to_sqlite(v: Value) -> rusqlite::types::Value {
138    match v {
139        Value::Blob(blob) => rusqlite::types::Value::Blob(blob),
140        Value::Integer(i) => rusqlite::types::Value::Integer(i),
141        Value::Null => rusqlite::types::Value::Null,
142        Value::Text(t) => rusqlite::types::Value::Text(t),
143        Value::Real(r) => rusqlite::types::Value::Real(r),
144    }
145}
146
147/// Convert from rusqlite Valute to cdk_sql_common::value::Value
148#[inline(always)]
149pub fn from_sqlite(v: rusqlite::types::Value) -> Value {
150    match v {
151        rusqlite::types::Value::Blob(blob) => Value::Blob(blob),
152        rusqlite::types::Value::Integer(i) => Value::Integer(i),
153        rusqlite::types::Value::Null => Value::Null,
154        rusqlite::types::Value::Text(t) => Value::Text(t),
155        rusqlite::types::Value::Real(r) => Value::Real(r),
156    }
157}