unistore-sqlite 0.1.0

SQLite embedded database capability for UniStore
Documentation
//! SQLite 类型映射
//!
//! 职责:定义 Rust 与 SQLite 之间的类型转换

use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use std::fmt;

/// SQLite 值类型
#[derive(Debug, Clone, PartialEq)]
pub enum SqlValue {
    /// NULL 值
    Null,
    /// 整数
    Integer(i64),
    /// 浮点数
    Real(f64),
    /// 文本
    Text(String),
    /// 二进制数据
    Blob(Vec<u8>),
}

impl SqlValue {
    /// 判断是否为 NULL
    pub fn is_null(&self) -> bool {
        matches!(self, Self::Null)
    }

    /// 尝试转换为 i64
    pub fn as_i64(&self) -> Option<i64> {
        match self {
            Self::Integer(v) => Some(*v),
            Self::Real(v) => Some(*v as i64),
            _ => None,
        }
    }

    /// 尝试转换为 f64
    pub fn as_f64(&self) -> Option<f64> {
        match self {
            Self::Real(v) => Some(*v),
            Self::Integer(v) => Some(*v as f64),
            _ => None,
        }
    }

    /// 尝试转换为字符串引用
    pub fn as_str(&self) -> Option<&str> {
        match self {
            Self::Text(s) => Some(s),
            _ => None,
        }
    }

    /// 尝试转换为字节切片
    pub fn as_bytes(&self) -> Option<&[u8]> {
        match self {
            Self::Blob(b) => Some(b),
            Self::Text(s) => Some(s.as_bytes()),
            _ => None,
        }
    }

    /// 尝试转换为 bool
    pub fn as_bool(&self) -> Option<bool> {
        match self {
            Self::Integer(v) => Some(*v != 0),
            _ => None,
        }
    }
}

impl fmt::Display for SqlValue {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Null => write!(f, "NULL"),
            Self::Integer(v) => write!(f, "{}", v),
            Self::Real(v) => write!(f, "{}", v),
            Self::Text(s) => write!(f, "'{}'", s),
            Self::Blob(b) => write!(f, "BLOB({} bytes)", b.len()),
        }
    }
}

impl FromSql for SqlValue {
    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
        Ok(match value {
            ValueRef::Null => Self::Null,
            ValueRef::Integer(i) => Self::Integer(i),
            ValueRef::Real(r) => Self::Real(r),
            ValueRef::Text(t) => Self::Text(
                std::str::from_utf8(t)
                    .map_err(|e| FromSqlError::Other(Box::new(e)))?
                    .to_string(),
            ),
            ValueRef::Blob(b) => Self::Blob(b.to_vec()),
        })
    }
}

impl ToSql for SqlValue {
    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
        Ok(match self {
            Self::Null => ToSqlOutput::Owned(rusqlite::types::Value::Null),
            Self::Integer(i) => ToSqlOutput::Owned(rusqlite::types::Value::Integer(*i)),
            Self::Real(r) => ToSqlOutput::Owned(rusqlite::types::Value::Real(*r)),
            Self::Text(s) => ToSqlOutput::Owned(rusqlite::types::Value::Text(s.clone())),
            Self::Blob(b) => ToSqlOutput::Owned(rusqlite::types::Value::Blob(b.clone())),
        })
    }
}

/// 查询参数
///
/// 支持多种 Rust 类型到 SQLite 参数的转换
#[derive(Debug, Clone)]
pub enum Param {
    /// NULL
    Null,
    /// 布尔值
    Bool(bool),
    /// 整数
    Int(i64),
    /// 无符号整数
    Uint(u64),
    /// 浮点数
    Float(f64),
    /// 字符串
    Text(String),
    /// 二进制
    Blob(Vec<u8>),
}

impl Param {
    /// 创建 NULL 参数
    pub fn null() -> Self {
        Self::Null
    }
}

// 实现各种类型到 Param 的转换
impl From<bool> for Param {
    fn from(v: bool) -> Self {
        Self::Bool(v)
    }
}

impl From<i32> for Param {
    fn from(v: i32) -> Self {
        Self::Int(v as i64)
    }
}

impl From<i64> for Param {
    fn from(v: i64) -> Self {
        Self::Int(v)
    }
}

impl From<u32> for Param {
    fn from(v: u32) -> Self {
        Self::Uint(v as u64)
    }
}

impl From<u64> for Param {
    fn from(v: u64) -> Self {
        Self::Uint(v)
    }
}

impl From<usize> for Param {
    fn from(v: usize) -> Self {
        Self::Uint(v as u64)
    }
}

impl From<f32> for Param {
    fn from(v: f32) -> Self {
        Self::Float(v as f64)
    }
}

impl From<f64> for Param {
    fn from(v: f64) -> Self {
        Self::Float(v)
    }
}

impl From<&str> for Param {
    fn from(v: &str) -> Self {
        Self::Text(v.to_string())
    }
}

impl From<String> for Param {
    fn from(v: String) -> Self {
        Self::Text(v)
    }
}

impl From<&String> for Param {
    fn from(v: &String) -> Self {
        Self::Text(v.clone())
    }
}

impl From<Vec<u8>> for Param {
    fn from(v: Vec<u8>) -> Self {
        Self::Blob(v)
    }
}

impl From<&[u8]> for Param {
    fn from(v: &[u8]) -> Self {
        Self::Blob(v.to_vec())
    }
}

impl<T> From<Option<T>> for Param
where
    T: Into<Param>,
{
    fn from(v: Option<T>) -> Self {
        match v {
            Some(inner) => inner.into(),
            None => Self::Null,
        }
    }
}

impl ToSql for Param {
    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
        Ok(match self {
            Self::Null => ToSqlOutput::Owned(rusqlite::types::Value::Null),
            Self::Bool(b) => ToSqlOutput::Owned(rusqlite::types::Value::Integer(if *b { 1 } else { 0 })),
            Self::Int(i) => ToSqlOutput::Owned(rusqlite::types::Value::Integer(*i)),
            Self::Uint(u) => ToSqlOutput::Owned(rusqlite::types::Value::Integer(*u as i64)),
            Self::Float(f) => ToSqlOutput::Owned(rusqlite::types::Value::Real(*f)),
            Self::Text(s) => ToSqlOutput::Owned(rusqlite::types::Value::Text(s.clone())),
            Self::Blob(b) => ToSqlOutput::Owned(rusqlite::types::Value::Blob(b.clone())),
        })
    }
}

/// 查询结果行
///
/// 包含列名到值的映射
#[derive(Debug, Clone, Default)]
pub struct Row {
    /// 列名列表(保持顺序)
    columns: Vec<String>,
    /// 值列表
    values: Vec<SqlValue>,
}

impl Row {
    /// 创建新行
    pub fn new() -> Self {
        Self::default()
    }

    /// 添加列值
    pub fn push(&mut self, name: impl Into<String>, value: SqlValue) {
        self.columns.push(name.into());
        self.values.push(value);
    }

    /// 获取列数
    pub fn len(&self) -> usize {
        self.columns.len()
    }

    /// 判断是否为空
    pub fn is_empty(&self) -> bool {
        self.columns.is_empty()
    }

    /// 获取列名列表
    pub fn columns(&self) -> &[String] {
        &self.columns
    }

    /// 按索引获取值
    pub fn get(&self, index: usize) -> Option<&SqlValue> {
        self.values.get(index)
    }

    /// 按列名获取值
    pub fn get_by_name(&self, name: &str) -> Option<&SqlValue> {
        self.columns
            .iter()
            .position(|c| c == name)
            .and_then(|i| self.values.get(i))
    }

    /// 尝试获取 i64 值
    pub fn get_i64(&self, name: &str) -> Option<i64> {
        self.get_by_name(name).and_then(|v| v.as_i64())
    }

    /// 尝试获取 f64 值
    pub fn get_f64(&self, name: &str) -> Option<f64> {
        self.get_by_name(name).and_then(|v| v.as_f64())
    }

    /// 尝试获取字符串值
    pub fn get_str(&self, name: &str) -> Option<&str> {
        self.get_by_name(name).and_then(|v| v.as_str())
    }

    /// 尝试获取字符串值(返回 String)
    pub fn get_string(&self, name: &str) -> Option<String> {
        self.get_str(name).map(|s| s.to_string())
    }

    /// 尝试获取 bool 值
    pub fn get_bool(&self, name: &str) -> Option<bool> {
        self.get_by_name(name).and_then(|v| v.as_bool())
    }

    /// 尝试获取 bytes 值
    pub fn get_bytes(&self, name: &str) -> Option<&[u8]> {
        self.get_by_name(name).and_then(|v| v.as_bytes())
    }
}

/// 多行结果集
pub type Rows = Vec<Row>;

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

    #[test]
    fn test_sql_value() {
        let v = SqlValue::Integer(42);
        assert_eq!(v.as_i64(), Some(42));
        assert_eq!(v.as_f64(), Some(42.0));

        let v = SqlValue::Text("hello".into());
        assert_eq!(v.as_str(), Some("hello"));

        let v = SqlValue::Null;
        assert!(v.is_null());
    }

    #[test]
    fn test_param_conversions() {
        let p: Param = 42i32.into();
        assert!(matches!(p, Param::Int(42)));

        let p: Param = "hello".into();
        assert!(matches!(p, Param::Text(_)));

        let p: Param = None::<i32>.into();
        assert!(matches!(p, Param::Null));
    }

    #[test]
    fn test_row() {
        let mut row = Row::new();
        row.push("id", SqlValue::Integer(1));
        row.push("name", SqlValue::Text("Alice".into()));

        assert_eq!(row.len(), 2);
        assert_eq!(row.get_i64("id"), Some(1));
        assert_eq!(row.get_str("name"), Some("Alice"));
        assert_eq!(row.get_str("unknown"), None);
    }
}