Skip to main content

sqlmodel_frankensqlite/
value.rs

1//! Bidirectional conversion between `sqlmodel_core::Value` and `fsqlite_types::value::SqliteValue`.
2
3use fsqlite_types::value::SqliteValue;
4use sqlmodel_core::value::Value;
5
6/// Convert a `sqlmodel_core::Value` to a `SqliteValue` for parameter binding.
7#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
8pub fn value_to_sqlite(v: &Value) -> SqliteValue {
9    match v {
10        Value::Null | Value::Default => SqliteValue::Null,
11        Value::Bool(b) => SqliteValue::Integer(i64::from(*b)),
12        Value::TinyInt(i) => SqliteValue::Integer(i64::from(*i)),
13        Value::SmallInt(i) => SqliteValue::Integer(i64::from(*i)),
14        Value::Int(i) => SqliteValue::Integer(i64::from(*i)),
15        Value::BigInt(i) => SqliteValue::Integer(*i),
16        Value::Float(f) => SqliteValue::Float(f64::from(*f)),
17        Value::Double(f) => SqliteValue::Float(*f),
18        Value::Decimal(s) => {
19            // Try integer first, then float, then text
20            if let Ok(i) = s.parse::<i64>() {
21                SqliteValue::Integer(i)
22            } else if let Ok(f) = s.parse::<f64>() {
23                SqliteValue::Float(f)
24            } else {
25                SqliteValue::Text(s.clone())
26            }
27        }
28        Value::Text(s) => SqliteValue::Text(s.clone()),
29        Value::Bytes(b) => SqliteValue::Blob(b.clone()),
30        Value::Date(d) => SqliteValue::Integer(i64::from(*d)),
31        Value::Time(t) => SqliteValue::Integer(*t),
32        Value::Timestamp(ts) => SqliteValue::Integer(*ts),
33        Value::TimestampTz(ts) => SqliteValue::Integer(*ts),
34        Value::Uuid(bytes) => SqliteValue::Blob(bytes.to_vec()),
35        Value::Json(v) => SqliteValue::Text(serde_json::to_string(v).unwrap_or_default()),
36        Value::Array(_) => SqliteValue::Null, // Arrays not supported in SQLite
37    }
38}
39
40/// Convert a `SqliteValue` to a `sqlmodel_core::Value` for result extraction.
41pub fn sqlite_to_value(sv: &SqliteValue) -> Value {
42    match sv {
43        SqliteValue::Null => Value::Null,
44        SqliteValue::Integer(i) => Value::BigInt(*i),
45        SqliteValue::Float(f) => Value::Double(*f),
46        SqliteValue::Text(s) => Value::Text(s.clone()),
47        SqliteValue::Blob(b) => Value::Bytes(b.clone()),
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn null_roundtrip() {
57        assert!(matches!(value_to_sqlite(&Value::Null), SqliteValue::Null));
58        assert!(matches!(sqlite_to_value(&SqliteValue::Null), Value::Null));
59    }
60
61    #[test]
62    fn bool_to_integer() {
63        assert_eq!(value_to_sqlite(&Value::Bool(true)), SqliteValue::Integer(1));
64        assert_eq!(
65            value_to_sqlite(&Value::Bool(false)),
66            SqliteValue::Integer(0)
67        );
68    }
69
70    #[test]
71    fn integer_variants() {
72        assert_eq!(
73            value_to_sqlite(&Value::TinyInt(42)),
74            SqliteValue::Integer(42)
75        );
76        assert_eq!(
77            value_to_sqlite(&Value::SmallInt(1000)),
78            SqliteValue::Integer(1000)
79        );
80        assert_eq!(
81            value_to_sqlite(&Value::Int(100_000)),
82            SqliteValue::Integer(100_000)
83        );
84        assert_eq!(
85            value_to_sqlite(&Value::BigInt(i64::MAX)),
86            SqliteValue::Integer(i64::MAX)
87        );
88    }
89
90    #[test]
91    fn float_variants() {
92        let sv = value_to_sqlite(&Value::Float(3.14));
93        assert!(matches!(sv, SqliteValue::Float(_)));
94
95        assert_eq!(
96            value_to_sqlite(&Value::Double(2.718)),
97            SqliteValue::Float(2.718)
98        );
99    }
100
101    #[test]
102    fn text_roundtrip() {
103        let v = Value::Text("hello".into());
104        let sv = value_to_sqlite(&v);
105        assert_eq!(sv, SqliteValue::Text("hello".into()));
106        assert_eq!(sqlite_to_value(&sv), v);
107    }
108
109    #[test]
110    fn bytes_roundtrip() {
111        let v = Value::Bytes(vec![0xDE, 0xAD]);
112        let sv = value_to_sqlite(&v);
113        assert_eq!(sv, SqliteValue::Blob(vec![0xDE, 0xAD]));
114        assert_eq!(sqlite_to_value(&sv), v);
115    }
116
117    #[test]
118    fn json_to_text() {
119        let json = serde_json::json!({"key": "value"});
120        let sv = value_to_sqlite(&Value::Json(json.clone()));
121        assert!(matches!(sv, SqliteValue::Text(_)));
122    }
123
124    #[test]
125    fn uuid_to_blob() {
126        let uuid = [1u8; 16];
127        let sv = value_to_sqlite(&Value::Uuid(uuid));
128        assert_eq!(sv, SqliteValue::Blob(uuid.to_vec()));
129    }
130
131    #[test]
132    fn decimal_string_to_integer() {
133        let sv = value_to_sqlite(&Value::Decimal("42".into()));
134        assert_eq!(sv, SqliteValue::Integer(42));
135    }
136
137    #[test]
138    fn decimal_string_to_float() {
139        let sv = value_to_sqlite(&Value::Decimal("3.14".into()));
140        assert!(matches!(sv, SqliteValue::Float(_)));
141    }
142
143    #[test]
144    fn timestamp_to_integer() {
145        let v = Value::Timestamp(1_700_000_000_000_000);
146        let sv = value_to_sqlite(&v);
147        assert_eq!(sv, SqliteValue::Integer(1_700_000_000_000_000));
148    }
149
150    #[test]
151    fn default_to_null() {
152        assert!(matches!(
153            value_to_sqlite(&Value::Default),
154            SqliteValue::Null
155        ));
156    }
157
158    #[test]
159    fn sqlite_integer_to_bigint() {
160        let v = sqlite_to_value(&SqliteValue::Integer(42));
161        assert_eq!(v, Value::BigInt(42));
162    }
163
164    #[test]
165    fn sqlite_float_to_double() {
166        let v = sqlite_to_value(&SqliteValue::Float(3.14));
167        assert_eq!(v, Value::Double(3.14));
168    }
169}