unistore-sqlite 0.1.0

SQLite embedded database capability for UniStore
Documentation
//! SQLite 配置
//!
//! 职责:定义数据库配置选项和预设

use std::path::PathBuf;

/// 同步模式
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SynchronousMode {
    /// 完全异步(最快,断电可能损坏)
    Off,
    /// 普通模式(默认)
    #[default]
    Normal,
    /// 完全同步(最安全,最慢)
    Full,
    /// 额外同步
    Extra,
}

impl SynchronousMode {
    /// 转换为 PRAGMA 值
    pub fn as_pragma(&self) -> &'static str {
        match self {
            Self::Off => "OFF",
            Self::Normal => "NORMAL",
            Self::Full => "FULL",
            Self::Extra => "EXTRA",
        }
    }
}

/// 日志模式
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum JournalMode {
    /// 删除模式(默认)
    Delete,
    /// 截断模式
    Truncate,
    /// 持久模式
    Persist,
    /// 内存模式
    Memory,
    /// WAL 模式(推荐用于并发)
    #[default]
    Wal,
    /// 关闭日志
    Off,
}

impl JournalMode {
    /// 转换为 PRAGMA 值
    pub fn as_pragma(&self) -> &'static str {
        match self {
            Self::Delete => "DELETE",
            Self::Truncate => "TRUNCATE",
            Self::Persist => "PERSIST",
            Self::Memory => "MEMORY",
            Self::Wal => "WAL",
            Self::Off => "OFF",
        }
    }
}

/// SQLite 配置
#[derive(Debug, Clone)]
pub struct SqliteConfig {
    /// 数据库路径(None 表示内存数据库)
    pub path: Option<PathBuf>,

    /// 同步模式
    pub synchronous: SynchronousMode,

    /// 日志模式
    pub journal_mode: JournalMode,

    /// 是否启用外键约束
    pub foreign_keys: bool,

    /// 繁忙超时(毫秒)
    pub busy_timeout_ms: u32,

    /// 缓存大小(页数,负数表示 KB)
    pub cache_size: i32,

    /// 页大小(字节)
    pub page_size: u32,

    /// 是否只读
    pub read_only: bool,

    /// 创建时如果不存在
    pub create_if_missing: bool,
}

impl Default for SqliteConfig {
    fn default() -> Self {
        Self {
            path: None,
            synchronous: SynchronousMode::Normal,
            journal_mode: JournalMode::Wal,
            foreign_keys: true,
            busy_timeout_ms: 5000,
            cache_size: -2000, // 2MB
            page_size: 4096,
            read_only: false,
            create_if_missing: true,
        }
    }
}

impl SqliteConfig {
    /// 创建内存数据库配置
    pub fn memory() -> Self {
        Self {
            path: None,
            journal_mode: JournalMode::Memory,
            ..Default::default()
        }
    }

    /// 创建文件数据库配置
    pub fn file(path: impl Into<PathBuf>) -> Self {
        Self {
            path: Some(path.into()),
            ..Default::default()
        }
    }

    /// 创建性能优先配置
    ///
    /// - WAL 模式
    /// - 异步同步
    /// - 大缓存
    pub fn performance() -> Self {
        Self {
            synchronous: SynchronousMode::Off,
            journal_mode: JournalMode::Wal,
            cache_size: -10000, // 10MB
            ..Default::default()
        }
    }

    /// 创建持久性优先配置
    ///
    /// - 完全同步
    /// - 启用外键
    pub fn durable() -> Self {
        Self {
            synchronous: SynchronousMode::Full,
            journal_mode: JournalMode::Wal,
            foreign_keys: true,
            ..Default::default()
        }
    }

    /// 设置数据库路径
    pub fn with_path(mut self, path: impl Into<PathBuf>) -> Self {
        self.path = Some(path.into());
        self
    }

    /// 设置同步模式
    pub fn with_synchronous(mut self, mode: SynchronousMode) -> Self {
        self.synchronous = mode;
        self
    }

    /// 设置日志模式
    pub fn with_journal_mode(mut self, mode: JournalMode) -> Self {
        self.journal_mode = mode;
        self
    }

    /// 设置外键约束
    pub fn with_foreign_keys(mut self, enabled: bool) -> Self {
        self.foreign_keys = enabled;
        self
    }

    /// 设置繁忙超时
    pub fn with_busy_timeout(mut self, ms: u32) -> Self {
        self.busy_timeout_ms = ms;
        self
    }

    /// 设置只读模式
    pub fn with_read_only(mut self, read_only: bool) -> Self {
        self.read_only = read_only;
        self
    }

    /// 生成 PRAGMA 语句列表
    pub fn to_pragmas(&self) -> Vec<String> {
        let mut pragmas = Vec::new();

        // 页大小必须在打开数据库时设置
        pragmas.push(format!("PRAGMA page_size = {};", self.page_size));

        // 日志模式
        pragmas.push(format!("PRAGMA journal_mode = {};", self.journal_mode.as_pragma()));

        // 同步模式
        pragmas.push(format!("PRAGMA synchronous = {};", self.synchronous.as_pragma()));

        // 外键约束
        pragmas.push(format!(
            "PRAGMA foreign_keys = {};",
            if self.foreign_keys { "ON" } else { "OFF" }
        ));

        // 繁忙超时
        pragmas.push(format!("PRAGMA busy_timeout = {};", self.busy_timeout_ms));

        // 缓存大小
        pragmas.push(format!("PRAGMA cache_size = {};", self.cache_size));

        pragmas
    }

    /// 获取数据库路径字符串(用于 rusqlite)
    pub fn path_string(&self) -> String {
        match &self.path {
            Some(p) => p.to_string_lossy().to_string(),
            None => ":memory:".to_string(),
        }
    }

    /// 判断是否为内存数据库
    pub fn is_memory(&self) -> bool {
        self.path.is_none()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_memory_config() {
        let config = SqliteConfig::memory();
        assert!(config.is_memory());
        assert_eq!(config.path_string(), ":memory:");
    }

    #[test]
    fn test_file_config() {
        let config = SqliteConfig::file("/tmp/test.db");
        assert!(!config.is_memory());
        assert!(config.path_string().contains("test.db"));
    }

    #[test]
    fn test_pragmas() {
        let config = SqliteConfig::default();
        let pragmas = config.to_pragmas();
        assert!(pragmas.iter().any(|p| p.contains("journal_mode")));
        assert!(pragmas.iter().any(|p| p.contains("synchronous")));
        assert!(pragmas.iter().any(|p| p.contains("foreign_keys")));
    }

    #[test]
    fn test_presets() {
        let perf = SqliteConfig::performance();
        assert_eq!(perf.synchronous, SynchronousMode::Off);

        let durable = SqliteConfig::durable();
        assert_eq!(durable.synchronous, SynchronousMode::Full);
    }
}