1use std::borrow::Cow;
4use std::cell::RefCell;
5use std::collections::HashMap;
6use std::path::PathBuf;
7use std::rc::Rc;
8use std::sync::Arc;
9use std::sync::Mutex;
10use std::sync::OnceLock;
11
12use async_trait::async_trait;
13use deno_core::OpState;
14use deno_core::unsync::spawn_blocking;
15use deno_error::JsErrorBox;
16use deno_permissions::OpenAccessKind;
17use deno_permissions::PermissionsContainer;
18pub use denokv_sqlite::SqliteBackendError;
19use denokv_sqlite::SqliteConfig;
20use denokv_sqlite::SqliteNotifier;
21use rand::SeedableRng;
22use rusqlite::OpenFlags;
23
24use crate::DatabaseHandler;
25
26static SQLITE_NOTIFIERS_MAP: OnceLock<Mutex<HashMap<PathBuf, SqliteNotifier>>> =
27 OnceLock::new();
28
29pub struct SqliteDbHandler {
30 pub default_storage_dir: Option<PathBuf>,
31 versionstamp_rng_seed: Option<u64>,
32}
33
34impl SqliteDbHandler {
35 pub fn new(
36 default_storage_dir: Option<PathBuf>,
37 versionstamp_rng_seed: Option<u64>,
38 ) -> Self {
39 Self {
40 default_storage_dir,
41 versionstamp_rng_seed,
42 }
43 }
44}
45
46deno_error::js_error_wrapper!(
47 SqliteBackendError,
48 JsSqliteBackendError,
49 "TypeError"
50);
51
52#[derive(Debug)]
53enum Mode {
54 Disk,
55 InMemory,
56}
57
58#[async_trait(?Send)]
59impl DatabaseHandler for SqliteDbHandler {
60 type DB = denokv_sqlite::Sqlite;
61
62 async fn open(
63 &self,
64 state: Rc<RefCell<OpState>>,
65 path: Option<String>,
66 ) -> Result<Self::DB, JsErrorBox> {
67 enum PathOrInMemory {
68 InMemory,
69 Path(PathBuf),
70 }
71
72 #[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"]
73 fn validate_path(
74 state: &RefCell<OpState>,
75 path: Option<String>,
76 ) -> Result<Option<PathOrInMemory>, JsErrorBox> {
77 let Some(path) = path else {
78 return Ok(None);
79 };
80 if path == ":memory:" {
81 return Ok(Some(PathOrInMemory::InMemory));
82 }
83 if path.is_empty() {
84 return Err(JsErrorBox::type_error("Filename cannot be empty"));
85 }
86 if path.starts_with(':') {
87 return Err(JsErrorBox::type_error(
88 "Filename cannot start with ':' unless prefixed with './'",
89 ));
90 }
91 {
92 let state = state.borrow();
93 let permissions = state.borrow::<PermissionsContainer>();
94 let path = permissions
95 .check_open(
96 Cow::Owned(PathBuf::from(path)),
97 OpenAccessKind::ReadWriteNoFollow,
98 Some("Deno.openKv"),
99 )
100 .map_err(JsErrorBox::from_err)?;
101 Ok(Some(PathOrInMemory::Path(path.into_owned_path())))
102 }
103 }
104
105 let path = validate_path(&state, path)?;
106 let default_storage_dir = self.default_storage_dir.clone();
107 type ConnGen =
108 Arc<dyn Fn() -> rusqlite::Result<rusqlite::Connection> + Send + Sync>;
109 let (conn_gen, notifier_key): (ConnGen, _) = spawn_blocking(move || {
110 denokv_sqlite::sqlite_retry_loop(move || {
111 let mode = match std::env::var("DENO_KV_DB_MODE")
112 .unwrap_or_default()
113 .as_str()
114 {
115 "disk" | "" => Mode::Disk,
116 "memory" => Mode::InMemory,
117 _ => {
118 log::warn!("Unknown DENO_KV_DB_MODE value, defaulting to disk");
119 Mode::Disk
120 }
121 };
122
123 if matches!(mode, Mode::InMemory) {
124 return Ok::<_, SqliteBackendError>((
125 Arc::new(rusqlite::Connection::open_in_memory) as ConnGen,
126 None,
127 ));
128 }
129
130 let (conn, notifier_key) = match (path.as_ref(), &default_storage_dir) {
131 (Some(PathOrInMemory::InMemory), _) | (None, None) => (
132 Arc::new(rusqlite::Connection::open_in_memory) as ConnGen,
133 None,
134 ),
135 (Some(PathOrInMemory::Path(path)), _) => {
136 let flags =
137 OpenFlags::default().difference(OpenFlags::SQLITE_OPEN_URI);
138 let resolved_path =
139 deno_path_util::fs::canonicalize_path_maybe_not_exists(
140 &sys_traits::impls::RealSys,
142 path,
143 )
144 .map_err(JsErrorBox::from_err)?;
145 let path = path.clone();
146 (
147 Arc::new(move || {
148 rusqlite::Connection::open_with_flags(&path, flags)
149 }) as ConnGen,
150 Some(resolved_path),
151 )
152 }
153 (None, Some(path)) => {
154 #[allow(
155 clippy::disallowed_methods,
156 reason = "the storage directory is always on the real fs"
157 )]
158 std::fs::create_dir_all(path).map_err(JsErrorBox::from_err)?;
159 let path = path.join("kv.sqlite3");
160 let path2 = path.clone();
161 (
162 Arc::new(move || rusqlite::Connection::open(&path2)) as ConnGen,
163 Some(path),
164 )
165 }
166 };
167
168 Ok::<_, SqliteBackendError>((conn, notifier_key))
169 })
170 })
171 .await
172 .unwrap()
173 .map_err(JsErrorBox::from_err)?;
174
175 let notifier = if let Some(notifier_key) = notifier_key {
176 SQLITE_NOTIFIERS_MAP
177 .get_or_init(Default::default)
178 .lock()
179 .unwrap()
180 .entry(notifier_key)
181 .or_default()
182 .clone()
183 } else {
184 SqliteNotifier::default()
185 };
186
187 let versionstamp_rng_seed = self.versionstamp_rng_seed;
188
189 let config = SqliteConfig {
190 batch_timeout: None,
191 num_workers: 1,
192 };
193
194 denokv_sqlite::Sqlite::new(
195 move || {
196 let conn =
197 conn_gen().map_err(|e| JsErrorBox::generic(e.to_string()))?;
198 conn
199 .pragma_update(None, "journal_mode", "wal")
200 .map_err(|e| JsErrorBox::generic(e.to_string()))?;
201 Ok((
202 conn,
203 match versionstamp_rng_seed {
204 Some(seed) => Box::new(rand::rngs::StdRng::seed_from_u64(seed)),
205 None => Box::new(rand::rngs::StdRng::from_entropy()),
206 },
207 ))
208 },
209 notifier,
210 config,
211 )
212 .map_err(|e| JsErrorBox::generic(e.to_string()))
213 }
214}