Skip to main content

reifydb_store_single/hot/sqlite/
config.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4#[cfg(not(target_os = "linux"))]
5use std::env;
6use std::path::{Path, PathBuf};
7
8use uuid::Uuid;
9
10#[derive(Debug, Clone, Eq, PartialEq)]
11pub enum DbPath {
12	File(PathBuf),
13	Tmpfs(PathBuf),  // tmpfs-backed file for WAL support + cleanup
14	Memory(PathBuf), // RAM-backed file for storage with WAL support + cleanup
15}
16
17fn memory_dir() -> PathBuf {
18	#[cfg(target_os = "linux")]
19	{
20		PathBuf::from("/dev/shm")
21	}
22	#[cfg(not(target_os = "linux"))]
23	{
24		env::temp_dir()
25	}
26}
27
28/// Configuration for SQLite storage backend
29#[derive(Debug, Clone)]
30pub struct SqliteConfig {
31	pub path: DbPath,
32	pub flags: OpenFlags,
33	pub journal_mode: JournalMode,
34	pub synchronous_mode: SynchronousMode,
35	pub temp_store: TempStore,
36	pub cache_size: u32,
37	pub wal_autocheckpoint: u32,
38	pub page_size: u32, // Page size in bytes (must be power of 2, 512-65536)
39	pub mmap_size: u64, // Memory-mapped I/O size in bytes
40}
41
42impl SqliteConfig {
43	/// Create a new SqliteConfig with the specified database path
44	pub fn new<P: AsRef<Path>>(path: P) -> Self {
45		Self {
46			path: DbPath::File(path.as_ref().to_path_buf()),
47			flags: OpenFlags::default(),
48			journal_mode: JournalMode::Wal,
49			synchronous_mode: SynchronousMode::Normal,
50			temp_store: TempStore::Memory,
51			cache_size: 20000,
52			wal_autocheckpoint: 1000,
53			page_size: 4096, // SQLite default
54			mmap_size: 0,    // Disabled by default
55		}
56	}
57
58	/// Create an in-memory configuration for production use
59	/// - RAM-only database with WAL mode for concurrent access
60	/// - Uses /dev/shm on Linux, temp dir on other platforms
61	/// - WAL journal mode for concurrent readers + single writer
62	/// - NORMAL synchronous mode (safe for RAM storage)
63	/// - MEMORY temp store
64	/// - Automatic cleanup on drop
65	pub fn in_memory() -> Self {
66		Self {
67			path: DbPath::Memory(memory_dir().join(format!("reifydb_mem_{}.db", Uuid::new_v4()))),
68			flags: OpenFlags::default(),
69			journal_mode: JournalMode::Wal,
70			synchronous_mode: SynchronousMode::Off,
71			temp_store: TempStore::Memory,
72			cache_size: 20000,
73			wal_autocheckpoint: 10000,
74			page_size: 16384,     // Larger page size for bulk operations
75			mmap_size: 268435456, // 256MB mmap for RAM-backed storage
76		}
77	}
78
79	/// Create a test configuration optimized for testing with in-memory database
80	/// - RAM-only database with WAL mode for concurrent access
81	/// - Uses /dev/shm on Linux, temp dir on other platforms
82	/// - WAL journal mode for concurrent readers + single writer
83	/// - FULL synchronous mode for test safety
84	/// - MEMORY temp store for fastest temp operations
85	/// - Automatic cleanup on drop
86	pub fn test() -> Self {
87		Self {
88			path: DbPath::Memory(memory_dir().join(format!("reifydb_test_{}.db", Uuid::new_v4()))),
89			flags: OpenFlags::default(),
90			journal_mode: JournalMode::Wal,
91			synchronous_mode: SynchronousMode::Off,
92			temp_store: TempStore::Memory,
93			cache_size: 10000,
94			wal_autocheckpoint: 10000,
95			page_size: 4096, // Default for tests
96			mmap_size: 0,    // Disabled for tests
97		}
98	}
99}
100
101impl Default for SqliteConfig {
102	fn default() -> Self {
103		Self::new("reify.reifydb")
104	}
105}
106
107/// SQLite database open flags
108#[derive(Debug, Clone)]
109pub struct OpenFlags {
110	pub read_write: bool,
111	pub create: bool,
112	pub full_mutex: bool,
113	pub no_mutex: bool,
114	pub shared_cache: bool,
115	pub private_cache: bool,
116	pub uri: bool,
117}
118
119impl Default for OpenFlags {
120	fn default() -> Self {
121		Self {
122			read_write: true,
123			create: true,
124			full_mutex: true,
125			no_mutex: false,
126			shared_cache: false,
127			private_cache: false,
128			uri: false,
129		}
130	}
131}
132
133/// SQLite journal mode options
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
135pub enum JournalMode {
136	/// Delete journal files after each transaction
137	Delete,
138	/// Truncate journal files to zero length instead of deleting
139	Truncate,
140	/// Persist journal files
141	Persist,
142	/// Use memory for journaling
143	Memory,
144	/// Write-Ahead Logging mode (recommended for concurrent access)
145	Wal,
146	/// No journaling (unsafe)
147	Off,
148}
149
150impl JournalMode {
151	pub(crate) fn as_str(&self) -> &'static str {
152		match self {
153			JournalMode::Delete => "DELETE",
154			JournalMode::Truncate => "TRUNCATE",
155			JournalMode::Persist => "PERSIST",
156			JournalMode::Memory => "MEMORY",
157			JournalMode::Wal => "WAL",
158			JournalMode::Off => "OFF",
159		}
160	}
161}
162
163/// SQLite synchronous mode options
164#[derive(Debug, Clone, Copy, PartialEq, Eq)]
165pub enum SynchronousMode {
166	/// No sync calls (fastest, but may corrupt on power loss)
167	Off,
168	/// Sync only at critical moments (good balance of safety and speed)
169	Normal,
170	/// Sync more frequently (safer but slower)
171	Full,
172	/// Sync even more frequently (safest but slowest)
173	Extra,
174}
175
176impl SynchronousMode {
177	pub(crate) fn as_str(&self) -> &'static str {
178		match self {
179			SynchronousMode::Off => "OFF",
180			SynchronousMode::Normal => "NORMAL",
181			SynchronousMode::Full => "FULL",
182			SynchronousMode::Extra => "EXTRA",
183		}
184	}
185}
186
187/// SQLite temporary storage location
188#[derive(Debug, Clone, Copy, PartialEq, Eq)]
189pub enum TempStore {
190	/// Use default storage (usually disk)
191	Default,
192	/// Store temporary data in files
193	File,
194	/// Store temporary data in memory (faster)
195	Memory,
196}
197
198impl TempStore {
199	pub(crate) fn as_str(&self) -> &'static str {
200		match self {
201			TempStore::Default => "DEFAULT",
202			TempStore::File => "FILE",
203			TempStore::Memory => "MEMORY",
204		}
205	}
206}
207
208#[cfg(test)]
209pub mod tests {
210	use reifydb_testing::tempdir::temp_dir;
211
212	use super::*;
213
214	#[test]
215	fn test_enum_string_conversion() {
216		assert_eq!(JournalMode::Wal.as_str(), "WAL");
217		assert_eq!(SynchronousMode::Normal.as_str(), "NORMAL");
218		assert_eq!(TempStore::Memory.as_str(), "MEMORY");
219	}
220
221	#[test]
222	fn test_all_journal_modes() {
223		assert_eq!(JournalMode::Delete.as_str(), "DELETE");
224		assert_eq!(JournalMode::Truncate.as_str(), "TRUNCATE");
225		assert_eq!(JournalMode::Persist.as_str(), "PERSIST");
226		assert_eq!(JournalMode::Memory.as_str(), "MEMORY");
227		assert_eq!(JournalMode::Wal.as_str(), "WAL");
228		assert_eq!(JournalMode::Off.as_str(), "OFF");
229	}
230
231	#[test]
232	fn test_all_synchronous_modes() {
233		assert_eq!(SynchronousMode::Off.as_str(), "OFF");
234		assert_eq!(SynchronousMode::Normal.as_str(), "NORMAL");
235		assert_eq!(SynchronousMode::Full.as_str(), "FULL");
236		assert_eq!(SynchronousMode::Extra.as_str(), "EXTRA");
237	}
238
239	#[test]
240	fn test_all_temp_store_modes() {
241		assert_eq!(TempStore::Default.as_str(), "DEFAULT");
242		assert_eq!(TempStore::File.as_str(), "FILE");
243		assert_eq!(TempStore::Memory.as_str(), "MEMORY");
244	}
245
246	#[test]
247	fn test_default_config() {
248		let config = SqliteConfig::default();
249		assert_eq!(config.path, DbPath::File(PathBuf::from("reify.reifydb")));
250		assert_eq!(config.journal_mode, JournalMode::Wal);
251		assert_eq!(config.synchronous_mode, SynchronousMode::Normal);
252		assert_eq!(config.temp_store, TempStore::Memory);
253	}
254
255	#[test]
256	fn test_path_handling() {
257		temp_dir(|db_path| {
258			// Test with file path
259			let file_path = db_path.join("test.reifydb");
260			let config = SqliteConfig::new(&file_path);
261			assert_eq!(config.path, DbPath::File(file_path));
262
263			// Test with directory path
264			let config = SqliteConfig::new(db_path);
265			assert_eq!(config.path, DbPath::File(db_path.to_path_buf()));
266			Ok(())
267		})
268		.expect("test failed");
269	}
270}