1#[cfg(not(target_os = "linux"))]
13use std::env;
14use std::path::{Path, PathBuf};
15
16use uuid::Uuid;
17
18#[cfg(not(target_arch = "wasm32"))]
19pub mod connection;
20#[cfg(not(target_arch = "wasm32"))]
21pub mod error;
22#[cfg(not(target_arch = "wasm32"))]
23pub mod pragma;
24
25#[derive(Debug, Clone, Eq, PartialEq)]
26pub enum DbPath {
27 File(PathBuf),
28
29 Tmpfs(PathBuf),
30
31 Memory(PathBuf),
32}
33
34fn memory_dir() -> PathBuf {
35 #[cfg(target_os = "linux")]
36 {
37 PathBuf::from("/dev/shm")
38 }
39 #[cfg(not(target_os = "linux"))]
40 {
41 env::temp_dir()
42 }
43}
44
45#[derive(Debug, Clone)]
46pub struct SqliteConfig {
47 pub path: DbPath,
48 pub flags: OpenFlags,
49 pub journal_mode: JournalMode,
50 pub synchronous_mode: SynchronousMode,
51 pub temp_store: TempStore,
52 pub cache_size: u32,
53 pub wal_autocheckpoint: u32,
54 pub page_size: u32,
55 pub mmap_size: u64,
56
57 pub prepared_statement_cache_capacity: u32,
58}
59
60impl SqliteConfig {
61 pub fn new<P: AsRef<Path>>(path: P) -> Self {
62 Self {
63 path: DbPath::File(path.as_ref().to_path_buf()),
64 flags: OpenFlags::default(),
65 journal_mode: JournalMode::Wal,
66 synchronous_mode: SynchronousMode::Normal,
67 temp_store: TempStore::Memory,
68 cache_size: 2000,
69 wal_autocheckpoint: 1000,
70 page_size: 4096,
71 mmap_size: 64 * 1024 * 1024,
72 prepared_statement_cache_capacity: 128,
73 }
74 }
75
76 pub fn safe<P: AsRef<Path>>(path: P) -> Self {
77 Self {
78 path: DbPath::File(path.as_ref().to_path_buf()),
79 flags: OpenFlags::default(),
80 journal_mode: JournalMode::Wal,
81 synchronous_mode: SynchronousMode::Full,
82 temp_store: TempStore::File,
83 cache_size: 2000,
84 wal_autocheckpoint: 1000,
85 page_size: 4096,
86 mmap_size: 0,
87 prepared_statement_cache_capacity: 128,
88 }
89 }
90
91 pub fn fast<P: AsRef<Path>>(path: P) -> Self {
92 Self {
93 path: DbPath::File(path.as_ref().to_path_buf()),
94 flags: OpenFlags::default(),
95 journal_mode: JournalMode::Wal,
96 synchronous_mode: SynchronousMode::Off,
97 temp_store: TempStore::Memory,
98 cache_size: 10000,
99 wal_autocheckpoint: 10000,
100 page_size: 16384,
101 mmap_size: 256 * 1024 * 1024,
102 prepared_statement_cache_capacity: 256,
103 }
104 }
105
106 pub fn tmpfs() -> Self {
107 Self {
108 path: DbPath::Tmpfs(PathBuf::from(format!("/tmp/reifydb_{}.db", Uuid::new_v4()))),
109 flags: OpenFlags::default(),
110 journal_mode: JournalMode::Wal,
111 synchronous_mode: SynchronousMode::Off,
112 temp_store: TempStore::Memory,
113 cache_size: 2000,
114 wal_autocheckpoint: 10000,
115 page_size: 16384,
116 mmap_size: 0,
117 prepared_statement_cache_capacity: 128,
118 }
119 }
120
121 pub fn in_memory() -> Self {
122 Self {
123 path: DbPath::Memory(memory_dir().join(format!("reifydb_{}.db", Uuid::new_v4()))),
124 flags: OpenFlags::default(),
125 journal_mode: JournalMode::Wal,
126 synchronous_mode: SynchronousMode::Off,
127 temp_store: TempStore::Memory,
128 cache_size: 2000,
129 wal_autocheckpoint: 10000,
130 page_size: 16384,
131 mmap_size: 0,
132 prepared_statement_cache_capacity: 128,
133 }
134 }
135
136 pub fn test() -> Self {
137 Self {
138 path: DbPath::Memory(memory_dir().join(format!("reifydb_{}.db", Uuid::new_v4()))),
139 flags: OpenFlags::default(),
140 journal_mode: JournalMode::Wal,
141 synchronous_mode: SynchronousMode::Off,
142 temp_store: TempStore::Memory,
143 cache_size: 1000,
144 wal_autocheckpoint: 10000,
145 page_size: 4096,
146 mmap_size: 0,
147 prepared_statement_cache_capacity: 32,
148 }
149 }
150
151 pub fn path<P: AsRef<Path>>(mut self, path: P) -> Self {
152 self.path = DbPath::File(path.as_ref().to_path_buf());
153 self
154 }
155
156 pub fn flags(mut self, flags: OpenFlags) -> Self {
157 self.flags = flags;
158 self
159 }
160
161 pub fn journal_mode(mut self, mode: JournalMode) -> Self {
162 self.journal_mode = mode;
163 self
164 }
165
166 pub fn synchronous_mode(mut self, mode: SynchronousMode) -> Self {
167 self.synchronous_mode = mode;
168 self
169 }
170
171 pub fn temp_store(mut self, store: TempStore) -> Self {
172 self.temp_store = store;
173 self
174 }
175
176 pub fn cache_size(mut self, size_kb: u32) -> Self {
177 self.cache_size = size_kb;
178 self
179 }
180
181 pub fn wal_autocheckpoint(mut self, pages: u32) -> Self {
182 self.wal_autocheckpoint = pages;
183 self
184 }
185
186 pub fn page_size(mut self, size: u32) -> Self {
187 self.page_size = size;
188 self
189 }
190
191 pub fn mmap_size(mut self, size: u64) -> Self {
192 self.mmap_size = size;
193 self
194 }
195}
196
197impl Default for SqliteConfig {
198 fn default() -> Self {
199 Self::new("reifydb.db")
200 }
201}
202
203#[derive(Debug, Clone)]
204pub struct OpenFlags {
205 pub read_write: bool,
206 pub create: bool,
207 pub full_mutex: bool,
208 pub no_mutex: bool,
209 pub shared_cache: bool,
210 pub private_cache: bool,
211 pub uri: bool,
212}
213
214impl OpenFlags {
215 pub fn new() -> Self {
216 Self::default()
217 }
218
219 pub fn read_write(mut self, enabled: bool) -> Self {
220 self.read_write = enabled;
221 self
222 }
223
224 pub fn create(mut self, enabled: bool) -> Self {
225 self.create = enabled;
226 self
227 }
228
229 pub fn full_mutex(mut self, enabled: bool) -> Self {
230 self.full_mutex = enabled;
231 self.no_mutex = !enabled;
232 self
233 }
234
235 pub fn no_mutex(mut self, enabled: bool) -> Self {
236 self.no_mutex = enabled;
237 self.full_mutex = !enabled;
238 self
239 }
240
241 pub fn shared_cache(mut self, enabled: bool) -> Self {
242 self.shared_cache = enabled;
243 self.private_cache = !enabled;
244 self
245 }
246
247 pub fn private_cache(mut self, enabled: bool) -> Self {
248 self.private_cache = enabled;
249 self.shared_cache = !enabled;
250 self
251 }
252
253 pub fn uri(mut self, enabled: bool) -> Self {
254 self.uri = enabled;
255 self
256 }
257}
258
259impl Default for OpenFlags {
260 fn default() -> Self {
261 Self {
262 read_write: true,
263 create: true,
264 full_mutex: true,
265 no_mutex: false,
266 shared_cache: false,
267 private_cache: false,
268 uri: false,
269 }
270 }
271}
272
273#[derive(Debug, Clone, Copy, PartialEq, Eq)]
274pub enum JournalMode {
275 Delete,
276 Truncate,
277 Persist,
278 Memory,
279 Wal,
280 Off,
281}
282
283impl JournalMode {
284 pub fn as_str(&self) -> &'static str {
285 match self {
286 JournalMode::Delete => "DELETE",
287 JournalMode::Truncate => "TRUNCATE",
288 JournalMode::Persist => "PERSIST",
289 JournalMode::Memory => "MEMORY",
290 JournalMode::Wal => "WAL",
291 JournalMode::Off => "OFF",
292 }
293 }
294}
295
296#[derive(Debug, Clone, Copy, PartialEq, Eq)]
297pub enum SynchronousMode {
298 Off,
299 Normal,
300 Full,
301 Extra,
302}
303
304impl SynchronousMode {
305 pub fn as_str(&self) -> &'static str {
306 match self {
307 SynchronousMode::Off => "OFF",
308 SynchronousMode::Normal => "NORMAL",
309 SynchronousMode::Full => "FULL",
310 SynchronousMode::Extra => "EXTRA",
311 }
312 }
313}
314
315#[derive(Debug, Clone, Copy, PartialEq, Eq)]
316pub enum TempStore {
317 Default,
318 File,
319 Memory,
320}
321
322impl TempStore {
323 pub fn as_str(&self) -> &'static str {
324 match self {
325 TempStore::Default => "DEFAULT",
326 TempStore::File => "FILE",
327 TempStore::Memory => "MEMORY",
328 }
329 }
330}
331
332#[cfg(test)]
333mod tests {
334 use reifydb_testing::tempdir::temp_dir;
335
336 use super::*;
337
338 #[test]
339 fn test_config_fluent_api() {
340 let config = SqliteConfig::new("/tmp/test.reifydb")
341 .journal_mode(JournalMode::Wal)
342 .synchronous_mode(SynchronousMode::Normal)
343 .temp_store(TempStore::Memory)
344 .cache_size(30000)
345 .flags(OpenFlags::new().read_write(true).create(true).full_mutex(true));
346
347 assert_eq!(config.path, DbPath::File(PathBuf::from("/tmp/test.reifydb")));
348 assert_eq!(config.journal_mode, JournalMode::Wal);
349 assert_eq!(config.synchronous_mode, SynchronousMode::Normal);
350 assert_eq!(config.temp_store, TempStore::Memory);
351 assert_eq!(config.cache_size, 30000);
352 assert!(config.flags.read_write);
353 assert!(config.flags.create);
354 assert!(config.flags.full_mutex);
355 }
356
357 #[test]
358 fn test_enum_string_conversion() {
359 assert_eq!(JournalMode::Wal.as_str(), "WAL");
360 assert_eq!(SynchronousMode::Normal.as_str(), "NORMAL");
361 assert_eq!(TempStore::Memory.as_str(), "MEMORY");
362 }
363
364 #[test]
365 fn test_all_journal_modes() {
366 assert_eq!(JournalMode::Delete.as_str(), "DELETE");
367 assert_eq!(JournalMode::Truncate.as_str(), "TRUNCATE");
368 assert_eq!(JournalMode::Persist.as_str(), "PERSIST");
369 assert_eq!(JournalMode::Memory.as_str(), "MEMORY");
370 assert_eq!(JournalMode::Wal.as_str(), "WAL");
371 assert_eq!(JournalMode::Off.as_str(), "OFF");
372 }
373
374 #[test]
375 fn test_all_synchronous_modes() {
376 assert_eq!(SynchronousMode::Off.as_str(), "OFF");
377 assert_eq!(SynchronousMode::Normal.as_str(), "NORMAL");
378 assert_eq!(SynchronousMode::Full.as_str(), "FULL");
379 assert_eq!(SynchronousMode::Extra.as_str(), "EXTRA");
380 }
381
382 #[test]
383 fn test_all_temp_store_modes() {
384 assert_eq!(TempStore::Default.as_str(), "DEFAULT");
385 assert_eq!(TempStore::File.as_str(), "FILE");
386 assert_eq!(TempStore::Memory.as_str(), "MEMORY");
387 }
388
389 #[test]
390 fn test_default_config() {
391 let config = SqliteConfig::default();
392 assert_eq!(config.path, DbPath::File(PathBuf::from("reifydb.db")));
393 assert_eq!(config.journal_mode, JournalMode::Wal);
394 assert_eq!(config.synchronous_mode, SynchronousMode::Normal);
395 assert_eq!(config.temp_store, TempStore::Memory);
396 }
397
398 #[test]
399 fn test_safe_config() {
400 temp_dir(|db_path| {
401 let db_file = db_path.join("safe.reifydb");
402 let config = SqliteConfig::safe(&db_file);
403
404 assert_eq!(config.path, DbPath::File(db_file));
405 assert_eq!(config.journal_mode, JournalMode::Wal);
406 assert_eq!(config.synchronous_mode, SynchronousMode::Full);
407 assert_eq!(config.temp_store, TempStore::File);
408 Ok(())
409 })
410 .expect("test failed");
411 }
412
413 #[test]
414 fn test_fast_config() {
415 temp_dir(|db_path| {
416 let db_file = db_path.join("fast.reifydb");
417 let config = SqliteConfig::fast(&db_file);
418
419 assert_eq!(config.path, DbPath::File(db_file));
420 assert_eq!(config.journal_mode, JournalMode::Wal);
421 assert_eq!(config.synchronous_mode, SynchronousMode::Off);
422 assert_eq!(config.temp_store, TempStore::Memory);
423 Ok(())
424 })
425 .expect("test failed");
426 }
427
428 #[test]
429 fn test_tmpfs_config() {
430 let config = SqliteConfig::tmpfs();
431
432 match config.path {
433 DbPath::Tmpfs(path) => {
434 assert!(path.to_string_lossy().starts_with("/tmp/reifydb_"));
435 assert!(path.to_string_lossy().ends_with(".db"));
436 }
437 _ => panic!("Expected DbPath::Tmpfs variant"),
438 }
439
440 assert_eq!(config.journal_mode, JournalMode::Wal);
441 assert_eq!(config.synchronous_mode, SynchronousMode::Off);
442 assert_eq!(config.temp_store, TempStore::Memory);
443 assert_eq!(config.cache_size, 2000);
444 assert_eq!(config.wal_autocheckpoint, 10000);
445 }
446
447 #[test]
448 fn test_config_chaining() {
449 temp_dir(|db_path| {
450 let db_file = db_path.join("chain.reifydb");
451
452 let config = SqliteConfig::new(&db_file)
453 .journal_mode(JournalMode::Delete)
454 .synchronous_mode(SynchronousMode::Extra)
455 .temp_store(TempStore::File)
456 .flags(OpenFlags::new().read_write(false).create(false).shared_cache(true));
457
458 assert_eq!(config.journal_mode, JournalMode::Delete);
459 assert_eq!(config.synchronous_mode, SynchronousMode::Extra);
460 assert_eq!(config.temp_store, TempStore::File);
461 assert!(!config.flags.read_write);
462 assert!(!config.flags.create);
463 assert!(config.flags.shared_cache);
464 Ok(())
465 })
466 .expect("test failed");
467 }
468
469 #[test]
470 fn test_open_flags_mutex_exclusivity() {
471 let flags = OpenFlags::new().full_mutex(true);
472 assert!(flags.full_mutex);
473 assert!(!flags.no_mutex);
474
475 let flags = OpenFlags::new().no_mutex(true);
476 assert!(!flags.full_mutex);
477 assert!(flags.no_mutex);
478 }
479
480 #[test]
481 fn test_open_flags_cache_exclusivity() {
482 let flags = OpenFlags::new().shared_cache(true);
483 assert!(flags.shared_cache);
484 assert!(!flags.private_cache);
485
486 let flags = OpenFlags::new().private_cache(true);
487 assert!(!flags.shared_cache);
488 assert!(flags.private_cache);
489 }
490
491 #[test]
492 fn test_open_flags_all_combinations() {
493 let flags =
494 OpenFlags::new().read_write(true).create(true).full_mutex(true).shared_cache(true).uri(true);
495
496 assert!(flags.read_write);
497 assert!(flags.create);
498 assert!(flags.full_mutex);
499 assert!(!flags.no_mutex);
500 assert!(flags.shared_cache);
501 assert!(!flags.private_cache);
502 assert!(flags.uri);
503 }
504
505 #[test]
506 fn test_path_handling() {
507 temp_dir(|db_path| {
508 let file_path = db_path.join("test.reifydb");
509 let config = SqliteConfig::new(&file_path);
510 assert_eq!(config.path, DbPath::File(file_path));
511
512 let config = SqliteConfig::new(db_path);
513 assert_eq!(config.path, DbPath::File(db_path.to_path_buf()));
514 Ok(())
515 })
516 .expect("test failed");
517 }
518}