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#[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#[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 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#[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#[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}