unistore_sqlite/
config.rs

1//! SQLite 配置
2//!
3//! 职责:定义数据库配置选项和预设
4
5use std::path::PathBuf;
6
7/// 同步模式
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
9pub enum SynchronousMode {
10    /// 完全异步(最快,断电可能损坏)
11    Off,
12    /// 普通模式(默认)
13    #[default]
14    Normal,
15    /// 完全同步(最安全,最慢)
16    Full,
17    /// 额外同步
18    Extra,
19}
20
21impl SynchronousMode {
22    /// 转换为 PRAGMA 值
23    pub fn as_pragma(&self) -> &'static str {
24        match self {
25            Self::Off => "OFF",
26            Self::Normal => "NORMAL",
27            Self::Full => "FULL",
28            Self::Extra => "EXTRA",
29        }
30    }
31}
32
33/// 日志模式
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
35pub enum JournalMode {
36    /// 删除模式(默认)
37    Delete,
38    /// 截断模式
39    Truncate,
40    /// 持久模式
41    Persist,
42    /// 内存模式
43    Memory,
44    /// WAL 模式(推荐用于并发)
45    #[default]
46    Wal,
47    /// 关闭日志
48    Off,
49}
50
51impl JournalMode {
52    /// 转换为 PRAGMA 值
53    pub fn as_pragma(&self) -> &'static str {
54        match self {
55            Self::Delete => "DELETE",
56            Self::Truncate => "TRUNCATE",
57            Self::Persist => "PERSIST",
58            Self::Memory => "MEMORY",
59            Self::Wal => "WAL",
60            Self::Off => "OFF",
61        }
62    }
63}
64
65/// SQLite 配置
66#[derive(Debug, Clone)]
67pub struct SqliteConfig {
68    /// 数据库路径(None 表示内存数据库)
69    pub path: Option<PathBuf>,
70
71    /// 同步模式
72    pub synchronous: SynchronousMode,
73
74    /// 日志模式
75    pub journal_mode: JournalMode,
76
77    /// 是否启用外键约束
78    pub foreign_keys: bool,
79
80    /// 繁忙超时(毫秒)
81    pub busy_timeout_ms: u32,
82
83    /// 缓存大小(页数,负数表示 KB)
84    pub cache_size: i32,
85
86    /// 页大小(字节)
87    pub page_size: u32,
88
89    /// 是否只读
90    pub read_only: bool,
91
92    /// 创建时如果不存在
93    pub create_if_missing: bool,
94}
95
96impl Default for SqliteConfig {
97    fn default() -> Self {
98        Self {
99            path: None,
100            synchronous: SynchronousMode::Normal,
101            journal_mode: JournalMode::Wal,
102            foreign_keys: true,
103            busy_timeout_ms: 5000,
104            cache_size: -2000, // 2MB
105            page_size: 4096,
106            read_only: false,
107            create_if_missing: true,
108        }
109    }
110}
111
112impl SqliteConfig {
113    /// 创建内存数据库配置
114    pub fn memory() -> Self {
115        Self {
116            path: None,
117            journal_mode: JournalMode::Memory,
118            ..Default::default()
119        }
120    }
121
122    /// 创建文件数据库配置
123    pub fn file(path: impl Into<PathBuf>) -> Self {
124        Self {
125            path: Some(path.into()),
126            ..Default::default()
127        }
128    }
129
130    /// 创建性能优先配置
131    ///
132    /// - WAL 模式
133    /// - 异步同步
134    /// - 大缓存
135    pub fn performance() -> Self {
136        Self {
137            synchronous: SynchronousMode::Off,
138            journal_mode: JournalMode::Wal,
139            cache_size: -10000, // 10MB
140            ..Default::default()
141        }
142    }
143
144    /// 创建持久性优先配置
145    ///
146    /// - 完全同步
147    /// - 启用外键
148    pub fn durable() -> Self {
149        Self {
150            synchronous: SynchronousMode::Full,
151            journal_mode: JournalMode::Wal,
152            foreign_keys: true,
153            ..Default::default()
154        }
155    }
156
157    /// 设置数据库路径
158    pub fn with_path(mut self, path: impl Into<PathBuf>) -> Self {
159        self.path = Some(path.into());
160        self
161    }
162
163    /// 设置同步模式
164    pub fn with_synchronous(mut self, mode: SynchronousMode) -> Self {
165        self.synchronous = mode;
166        self
167    }
168
169    /// 设置日志模式
170    pub fn with_journal_mode(mut self, mode: JournalMode) -> Self {
171        self.journal_mode = mode;
172        self
173    }
174
175    /// 设置外键约束
176    pub fn with_foreign_keys(mut self, enabled: bool) -> Self {
177        self.foreign_keys = enabled;
178        self
179    }
180
181    /// 设置繁忙超时
182    pub fn with_busy_timeout(mut self, ms: u32) -> Self {
183        self.busy_timeout_ms = ms;
184        self
185    }
186
187    /// 设置只读模式
188    pub fn with_read_only(mut self, read_only: bool) -> Self {
189        self.read_only = read_only;
190        self
191    }
192
193    /// 生成 PRAGMA 语句列表
194    pub fn to_pragmas(&self) -> Vec<String> {
195        let mut pragmas = Vec::new();
196
197        // 页大小必须在打开数据库时设置
198        pragmas.push(format!("PRAGMA page_size = {};", self.page_size));
199
200        // 日志模式
201        pragmas.push(format!("PRAGMA journal_mode = {};", self.journal_mode.as_pragma()));
202
203        // 同步模式
204        pragmas.push(format!("PRAGMA synchronous = {};", self.synchronous.as_pragma()));
205
206        // 外键约束
207        pragmas.push(format!(
208            "PRAGMA foreign_keys = {};",
209            if self.foreign_keys { "ON" } else { "OFF" }
210        ));
211
212        // 繁忙超时
213        pragmas.push(format!("PRAGMA busy_timeout = {};", self.busy_timeout_ms));
214
215        // 缓存大小
216        pragmas.push(format!("PRAGMA cache_size = {};", self.cache_size));
217
218        pragmas
219    }
220
221    /// 获取数据库路径字符串(用于 rusqlite)
222    pub fn path_string(&self) -> String {
223        match &self.path {
224            Some(p) => p.to_string_lossy().to_string(),
225            None => ":memory:".to_string(),
226        }
227    }
228
229    /// 判断是否为内存数据库
230    pub fn is_memory(&self) -> bool {
231        self.path.is_none()
232    }
233}
234
235#[cfg(test)]
236mod tests {
237    use super::*;
238
239    #[test]
240    fn test_memory_config() {
241        let config = SqliteConfig::memory();
242        assert!(config.is_memory());
243        assert_eq!(config.path_string(), ":memory:");
244    }
245
246    #[test]
247    fn test_file_config() {
248        let config = SqliteConfig::file("/tmp/test.db");
249        assert!(!config.is_memory());
250        assert!(config.path_string().contains("test.db"));
251    }
252
253    #[test]
254    fn test_pragmas() {
255        let config = SqliteConfig::default();
256        let pragmas = config.to_pragmas();
257        assert!(pragmas.iter().any(|p| p.contains("journal_mode")));
258        assert!(pragmas.iter().any(|p| p.contains("synchronous")));
259        assert!(pragmas.iter().any(|p| p.contains("foreign_keys")));
260    }
261
262    #[test]
263    fn test_presets() {
264        let perf = SqliteConfig::performance();
265        assert_eq!(perf.synchronous, SynchronousMode::Off);
266
267        let durable = SqliteConfig::durable();
268        assert_eq!(durable.synchronous, SynchronousMode::Full);
269    }
270}