rbdc_sqlite/options/mod.rs
1use std::path::Path;
2
3mod auto_vacuum;
4mod connect;
5mod journal_mode;
6mod locking_mode;
7mod parse;
8mod synchronous;
9
10pub use auto_vacuum::SqliteAutoVacuum;
11use futures_core::future::BoxFuture;
12pub use journal_mode::SqliteJournalMode;
13pub use locking_mode::SqliteLockingMode;
14use std::cmp::Ordering;
15use std::str::FromStr;
16use std::sync::Arc;
17use std::{borrow::Cow, time::Duration};
18pub use synchronous::SqliteSynchronous;
19
20use crate::connection::collation::Collation;
21use indexmap::IndexMap;
22use rbdc::common::DebugFn;
23use rbdc::db::{ConnectOptions, Connection};
24use rbdc::Error;
25use serde::{Deserialize, Deserializer};
26
27/// Options and flags which can be used to configure a SQLite connection.
28///
29/// A value of `SqliteConnectOptions` can be parsed from a connection URI,
30/// as described by [SQLite](https://www.sqlite.org/uri.html).
31///
32/// | URI | Description |
33/// | -- | -- |
34/// `sqlite::memory:` | Open an in-memory database. |
35/// `sqlite:data.db` | Open the file `data.db` in the current directory. |
36/// `sqlite://data.db` | Open the file `data.db` in the current directory. |
37/// `sqlite:///data.db` | Open the file `data.db` from the root (`/`) directory. |
38/// `sqlite://data.db?mode=ro` | Open the file `data.db` for read-only access. |
39///
40#[derive(Clone, Debug)]
41pub struct SqliteConnectOptions {
42 pub(crate) filename: Cow<'static, Path>,
43 pub(crate) in_memory: bool,
44 pub(crate) read_only: bool,
45 pub(crate) create_if_missing: bool,
46 pub(crate) shared_cache: bool,
47 pub(crate) statement_cache_capacity: usize,
48 pub(crate) busy_timeout: Duration,
49 pub(crate) immutable: bool,
50 pub(crate) pragmas: IndexMap<Cow<'static, str>, Cow<'static, str>>,
51
52 pub(crate) command_channel_size: usize,
53 pub(crate) row_channel_size: usize,
54
55 pub(crate) collations: Vec<Collation>,
56
57 pub(crate) serialized: bool,
58 pub(crate) thread_name: Arc<DebugFn<dyn Fn(u64) -> String + Send + Sync + 'static>>,
59}
60
61impl Default for SqliteConnectOptions {
62 fn default() -> Self {
63 Self::new()
64 }
65}
66
67impl<'de> Deserialize<'de> for SqliteConnectOptions {
68 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
69 where
70 D: Deserializer<'de>,
71 {
72 #[derive(Deserialize)]
73 pub struct SqliteConnectOptions {
74 pub(crate) filename: Cow<'static, Path>,
75 pub(crate) in_memory: bool,
76 pub(crate) read_only: bool,
77 pub(crate) create_if_missing: bool,
78 pub(crate) shared_cache: bool,
79 pub(crate) statement_cache_capacity: usize,
80 pub(crate) busy_timeout: Duration,
81 pub(crate) immutable: bool,
82 pub(crate) command_channel_size: usize,
83 pub(crate) row_channel_size: usize,
84
85 // pub(crate) collations: Vec<Collation>,
86 pub(crate) serialized: bool,
87 // pub(crate) thread_name: Arc<DebugFn<dyn Fn(u64) -> String + Send + Sync + 'static>>,
88 }
89 let op = SqliteConnectOptions::deserialize(deserializer)?;
90 let mut s = Self::default();
91 s.filename = op.filename;
92 s.in_memory = op.in_memory;
93 s.read_only = op.read_only;
94 s.create_if_missing = op.create_if_missing;
95 s.shared_cache = op.shared_cache;
96 s.statement_cache_capacity = op.statement_cache_capacity;
97 s.busy_timeout = op.busy_timeout;
98 s.immutable = op.immutable;
99 s.command_channel_size = op.command_channel_size;
100 s.row_channel_size = op.row_channel_size;
101 s.serialized = op.serialized;
102 Ok(s)
103 }
104}
105
106impl SqliteConnectOptions {
107 /// Construct `Self` with default options.
108 ///
109 /// See the source of this method for the current defaults.
110 pub fn new() -> Self {
111 // set default pragmas
112 let mut pragmas: IndexMap<Cow<'static, str>, Cow<'static, str>> = IndexMap::new();
113
114 let locking_mode: SqliteLockingMode = Default::default();
115 let auto_vacuum: SqliteAutoVacuum = Default::default();
116
117 // page_size must be set before any other action on the database.
118 pragmas.insert("page_size".into(), "4096".into());
119
120 // Note that locking_mode should be set before journal_mode; see
121 // https://www.sqlite.org/wal.html#use_of_wal_without_shared_memory .
122 pragmas.insert("locking_mode".into(), locking_mode.as_str().into());
123
124 pragmas.insert(
125 "journal_mode".into(),
126 SqliteJournalMode::Wal.as_str().into(),
127 );
128
129 pragmas.insert("foreign_keys".into(), "ON".into());
130
131 pragmas.insert(
132 "synchronous".into(),
133 SqliteSynchronous::Full.as_str().into(),
134 );
135
136 pragmas.insert("auto_vacuum".into(), auto_vacuum.as_str().into());
137
138 Self {
139 filename: Cow::Borrowed(Path::new(":memory:")),
140 in_memory: false,
141 read_only: false,
142 create_if_missing: true,
143 shared_cache: false,
144 statement_cache_capacity: 100,
145 busy_timeout: Duration::from_secs(5),
146 immutable: false,
147 pragmas,
148 collations: Default::default(),
149 serialized: false,
150 thread_name: Arc::new(DebugFn(|id| format!("rbdc-sqlite-worker-{}", id))),
151 command_channel_size: 50,
152 row_channel_size: 50,
153 }
154 }
155
156 /// Sets the name of the database file.
157 pub fn filename(mut self, filename: impl AsRef<Path>) -> Self {
158 self.filename = Cow::Owned(filename.as_ref().to_owned());
159 self
160 }
161
162 /// Set the enforcement of [foreign key constraints](https://www.sqlite.org/pragma.html#pragma_foreign_keys).
163 ///
164 /// By default, this is enabled.
165 pub fn foreign_keys(mut self, on: bool) -> Self {
166 self.pragmas.insert(
167 "foreign_keys".into(),
168 (if on { "ON" } else { "OFF" }).into(),
169 );
170 self
171 }
172
173 /// Set the [`SQLITE_OPEN_SHAREDCACHE` flag](https://sqlite.org/sharedcache.html).
174 ///
175 /// By default, this is disabled.
176 pub fn shared_cache(mut self, on: bool) -> Self {
177 self.shared_cache = on;
178 self
179 }
180
181 /// Sets the [journal mode](https://www.sqlite.org/pragma.html#pragma_journal_mode) for the database connection.
182 ///
183 /// The default journal mode is WAL. For most use cases this can be significantly faster but
184 /// there are [disadvantages](https://www.sqlite.org/wal.html).
185 pub fn journal_mode(mut self, mode: SqliteJournalMode) -> Self {
186 self.pragmas
187 .insert("journal_mode".into(), mode.as_str().into());
188 self
189 }
190
191 /// Sets the [locking mode](https://www.sqlite.org/pragma.html#pragma_locking_mode) for the database connection.
192 ///
193 /// The default locking mode is NORMAL.
194 pub fn locking_mode(mut self, mode: SqliteLockingMode) -> Self {
195 self.pragmas
196 .insert("locking_mode".into(), mode.as_str().into());
197 self
198 }
199
200 /// Sets the [access mode](https://www.sqlite.org/c3ref/open.html) to open the database
201 /// for read-only access.
202 pub fn read_only(mut self, read_only: bool) -> Self {
203 self.read_only = read_only;
204 self
205 }
206
207 /// Sets the [access mode](https://www.sqlite.org/c3ref/open.html) to create the database file
208 /// if the file does not exist.
209 ///
210 /// By default, a new file **will not be** created if one is not found.
211 pub fn create_if_missing(mut self, create: bool) -> Self {
212 self.create_if_missing = create;
213 self
214 }
215
216 /// Sets the capacity of the connection's statement cache in a number of stored
217 /// distinct statements. Caching is handled using LRU, meaning when the
218 /// amount of queries hits the defined limit, the oldest statement will get
219 /// dropped.
220 ///
221 /// The default cache capacity is 100 statements.
222 pub fn statement_cache_capacity(mut self, capacity: usize) -> Self {
223 self.statement_cache_capacity = capacity;
224 self
225 }
226
227 /// Sets a timeout value to wait when the database is locked, before
228 /// returning a busy timeout error.
229 ///
230 /// The default busy timeout is 5 seconds.
231 pub fn busy_timeout(mut self, timeout: Duration) -> Self {
232 self.busy_timeout = timeout;
233 self
234 }
235
236 /// Sets the [synchronous](https://www.sqlite.org/pragma.html#pragma_synchronous) setting for the database connection.
237 ///
238 /// The default synchronous settings is FULL. However, if durability is not a concern,
239 /// then NORMAL is normally all one needs in WAL mode.
240 pub fn synchronous(mut self, synchronous: SqliteSynchronous) -> Self {
241 self.pragmas
242 .insert("synchronous".into(), synchronous.as_str().into());
243 self
244 }
245
246 /// Sets the [auto_vacuum](https://www.sqlite.org/pragma.html#pragma_auto_vacuum) setting for the database connection.
247 ///
248 /// The default auto_vacuum setting is NONE.
249 pub fn auto_vacuum(mut self, auto_vacuum: SqliteAutoVacuum) -> Self {
250 self.pragmas
251 .insert("auto_vacuum".into(), auto_vacuum.as_str().into());
252 self
253 }
254
255 /// Sets the [page_size](https://www.sqlite.org/pragma.html#pragma_page_size) setting for the database connection.
256 ///
257 /// The default page_size setting is 4096.
258 pub fn page_size(mut self, page_size: u32) -> Self {
259 self.pragmas
260 .insert("page_size".into(), page_size.to_string().into());
261 self
262 }
263
264 /// Sets custom initial pragma for the database connection.
265 pub fn pragma<K, V>(mut self, key: K, value: V) -> Self
266 where
267 K: Into<Cow<'static, str>>,
268 V: Into<Cow<'static, str>>,
269 {
270 self.pragmas.insert(key.into(), value.into());
271 self
272 }
273
274 /// Add a custom collation for comparing strings in SQL.
275 ///
276 /// If a collation with the same name already exists, it will be replaced.
277 ///
278 /// See [`sqlite3_create_collation()`](https://www.sqlite.org/c3ref/create_collation.html) for details.
279 ///
280 /// Note this excerpt:
281 /// > The collating function must obey the following properties for all strings A, B, and C:
282 /// >
283 /// > If A==B then B==A.
284 /// > If A==B and B==C then A==C.
285 /// > If A\<B then B>A.
286 /// > If A<B and B<C then A<C.
287 /// >
288 /// > If a collating function fails any of the above constraints and that collating function is
289 /// > registered and used, then the behavior of SQLite is undefined.
290 pub fn collation<N, F>(mut self, name: N, collate: F) -> Self
291 where
292 N: Into<Arc<str>>,
293 F: Fn(&str, &str) -> Ordering + Send + Sync + 'static,
294 {
295 self.collations.push(Collation::new(name, collate));
296 self
297 }
298
299 /// Set to `true` to signal to SQLite that the database file is on read-only media.
300 ///
301 /// If enabled, SQLite assumes the database file _cannot_ be modified, even by higher
302 /// privileged processes, and so disables locking and change detection. This is intended
303 /// to improve performance but can produce incorrect query results or errors if the file
304 /// _does_ change.
305 ///
306 /// Note that this is different from the `SQLITE_OPEN_READONLY` flag set by
307 /// [`.read_only()`][Self::read_only], though the documentation suggests that this
308 /// does _imply_ `SQLITE_OPEN_READONLY`.
309 ///
310 /// See [`sqlite3_open`](https://www.sqlite.org/capi3ref.html#sqlite3_open) (subheading
311 /// "URI Filenames") for details.
312 pub fn immutable(mut self, immutable: bool) -> Self {
313 self.immutable = immutable;
314 self
315 }
316
317 /// Sets the [threading mode](https://www.sqlite.org/threadsafe.html) for the database connection.
318 ///
319 /// The default setting is `false` corersponding to using `OPEN_NOMUTEX`, if `true` then `OPEN_FULLMUTEX`.
320 ///
321 /// See [open](https://www.sqlite.org/c3ref/open.html) for more details.
322 ///
323 /// ### Note
324 /// Setting this to `true` may help if you are getting access violation errors or segmentation
325 /// faults, but will also incur a significant performance penalty. You should leave this
326 /// set to `false` if at all possible.
327 ///
328 /// If you do end up needing to set this to `true` for some reason, please
329 /// [open an issue](https://github.com/rbatis/rbatis/issues/new/choose) as this may indicate
330 /// a concurrency bug in rbdc. Please provide clear instructions for reproducing the issue,
331 /// including a sample database schema if applicable.
332 pub fn serialized(mut self, serialized: bool) -> Self {
333 self.serialized = serialized;
334 self
335 }
336
337 /// Provide a callback to generate the name of the background worker thread.
338 ///
339 /// The value passed to the callback is an auto-incremented integer for use as the thread ID.
340 pub fn thread_name(
341 mut self,
342 generator: impl Fn(u64) -> String + Send + Sync + 'static,
343 ) -> Self {
344 self.thread_name = Arc::new(DebugFn(generator));
345 self
346 }
347
348 /// Set the maximum number of commands to buffer for the worker thread before backpressure is
349 /// applied.
350 ///
351 /// Given that most commands sent to the worker thread involve waiting for a result,
352 /// the command channel is unlikely to fill up unless a lot queries are executed in a short
353 /// period but cancelled before their full resultsets are returned.
354 pub fn command_buffer_size(mut self, size: usize) -> Self {
355 self.command_channel_size = size;
356 self
357 }
358
359 /// Set the maximum number of rows to buffer back to the calling task when a query is executed.
360 ///
361 /// If the calling task cannot keep up, backpressure will be applied to the worker thread
362 /// in order to limit CPU and memory usage.
363 pub fn row_buffer_size(mut self, size: usize) -> Self {
364 self.row_channel_size = size;
365 self
366 }
367}
368
369impl ConnectOptions for SqliteConnectOptions {
370 fn connect(&self) -> BoxFuture<Result<Box<dyn Connection>, Error>> {
371 Box::pin(async move {
372 let c = self.connect().await?;
373 Ok(Box::new(c) as Box<dyn Connection>)
374 })
375 }
376
377 fn set_uri(&mut self, uri: &str) -> Result<(), Error> {
378 *self = SqliteConnectOptions::from_str(uri).map_err(|e| Error::from(e.to_string()))?;
379 Ok(())
380 }
381}