Skip to main content

floz_orm/
value.rs

1//! The `Value` enum — a type-safe wrapper for all SQL parameter values.
2//!
3//! Every nullable type has a dedicated `Option*` variant instead of a generic `Null`,
4//! because PostgreSQL requires typed NULLs for parameter binding. Binding an untyped NULL
5//! causes: `ERROR: could not determine data type of parameter $N`.
6
7use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Utc};
8use uuid::Uuid;
9
10/// Represents a SQL parameter value with full type information.
11///
12/// # Design Decision: Typed Nulls
13///
14/// There is no generic `Value::Null` variant. Each nullable type has its own `Option*`
15/// variant (e.g., `OptionInt(Option<i32>)`) so that PostgreSQL receives the correct
16/// type OID even when the value is NULL.
17#[derive(Debug, Clone, PartialEq)]
18pub enum Value {
19    // ── Non-nullable types ──
20    Short(i16),
21    Int(i32),
22    BigInt(i64),
23    Real(f32),
24    Double(f64),
25    Bool(bool),
26    String(String),
27    Bytes(Vec<u8>),
28    Uuid(Uuid),
29    DateTime(DateTime<Utc>),
30    NaiveDateTime(NaiveDateTime),
31    NaiveDate(NaiveDate),
32    NaiveTime(NaiveTime),
33    Json(serde_json::Value),
34    Jsonb(serde_json::Value),
35
36    // ── Nullable types (preserves PostgreSQL type OID for NULL) ──
37    OptionShort(Option<i16>),
38    OptionInt(Option<i32>),
39    OptionBigInt(Option<i64>),
40    OptionReal(Option<f32>),
41    OptionDouble(Option<f64>),
42    OptionBool(Option<bool>),
43    OptionString(Option<String>),
44    OptionBytes(Option<Vec<u8>>),
45    OptionUuid(Option<Uuid>),
46    OptionDateTime(Option<DateTime<Utc>>),
47    OptionNaiveDateTime(Option<NaiveDateTime>),
48    OptionNaiveDate(Option<NaiveDate>),
49    OptionNaiveTime(Option<NaiveTime>),
50    OptionJson(Option<serde_json::Value>),
51    OptionJsonb(Option<serde_json::Value>),
52}
53
54// ── From<T> implementations for non-nullable types ──
55
56impl From<i16> for Value {
57    fn from(v: i16) -> Self { Value::Short(v) }
58}
59
60impl From<i32> for Value {
61    fn from(v: i32) -> Self { Value::Int(v) }
62}
63
64impl From<i64> for Value {
65    fn from(v: i64) -> Self { Value::BigInt(v) }
66}
67
68impl From<f32> for Value {
69    fn from(v: f32) -> Self { Value::Real(v) }
70}
71
72impl From<f64> for Value {
73    fn from(v: f64) -> Self { Value::Double(v) }
74}
75
76impl From<bool> for Value {
77    fn from(v: bool) -> Self { Value::Bool(v) }
78}
79
80impl From<String> for Value {
81    fn from(v: String) -> Self { Value::String(v) }
82}
83
84impl From<&str> for Value {
85    fn from(v: &str) -> Self { Value::String(v.to_owned()) }
86}
87
88impl From<Vec<u8>> for Value {
89    fn from(v: Vec<u8>) -> Self { Value::Bytes(v) }
90}
91
92impl From<Uuid> for Value {
93    fn from(v: Uuid) -> Self { Value::Uuid(v) }
94}
95
96impl From<DateTime<Utc>> for Value {
97    fn from(v: DateTime<Utc>) -> Self { Value::DateTime(v) }
98}
99
100impl From<NaiveDateTime> for Value {
101    fn from(v: NaiveDateTime) -> Self { Value::NaiveDateTime(v) }
102}
103
104impl From<NaiveDate> for Value {
105    fn from(v: NaiveDate) -> Self { Value::NaiveDate(v) }
106}
107
108impl From<NaiveTime> for Value {
109    fn from(v: NaiveTime) -> Self { Value::NaiveTime(v) }
110}
111
112// ── From<Option<T>> implementations for nullable types ──
113
114impl From<Option<i16>> for Value {
115    fn from(v: Option<i16>) -> Self { Value::OptionShort(v) }
116}
117
118impl From<Option<i32>> for Value {
119    fn from(v: Option<i32>) -> Self { Value::OptionInt(v) }
120}
121
122impl From<Option<i64>> for Value {
123    fn from(v: Option<i64>) -> Self { Value::OptionBigInt(v) }
124}
125
126impl From<Option<f32>> for Value {
127    fn from(v: Option<f32>) -> Self { Value::OptionReal(v) }
128}
129
130impl From<Option<f64>> for Value {
131    fn from(v: Option<f64>) -> Self { Value::OptionDouble(v) }
132}
133
134impl From<Option<bool>> for Value {
135    fn from(v: Option<bool>) -> Self { Value::OptionBool(v) }
136}
137
138impl From<Option<String>> for Value {
139    fn from(v: Option<String>) -> Self { Value::OptionString(v) }
140}
141
142impl From<Option<Vec<u8>>> for Value {
143    fn from(v: Option<Vec<u8>>) -> Self { Value::OptionBytes(v) }
144}
145
146impl From<Option<Uuid>> for Value {
147    fn from(v: Option<Uuid>) -> Self { Value::OptionUuid(v) }
148}
149
150impl From<Option<DateTime<Utc>>> for Value {
151    fn from(v: Option<DateTime<Utc>>) -> Self { Value::OptionDateTime(v) }
152}
153
154impl From<Option<NaiveDateTime>> for Value {
155    fn from(v: Option<NaiveDateTime>) -> Self { Value::OptionNaiveDateTime(v) }
156}
157
158impl From<Option<NaiveDate>> for Value {
159    fn from(v: Option<NaiveDate>) -> Self { Value::OptionNaiveDate(v) }
160}
161
162impl From<Option<NaiveTime>> for Value {
163    fn from(v: Option<NaiveTime>) -> Self { Value::OptionNaiveTime(v) }
164}
165
166// ── Tests ──
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171
172    #[test]
173    fn value_from_i16() {
174        assert!(matches!(Value::from(42i16), Value::Short(42)));
175    }
176
177    #[test]
178    fn value_from_i32() {
179        assert!(matches!(Value::from(42i32), Value::Int(42)));
180    }
181
182    #[test]
183    fn value_from_i64() {
184        assert!(matches!(Value::from(42i64), Value::BigInt(42)));
185    }
186
187    #[test]
188    fn value_from_f32() {
189        let v = Value::from(3.14f32);
190        assert!(matches!(v, Value::Real(_)));
191    }
192
193    #[test]
194    fn value_from_f64() {
195        let v = Value::from(3.14f64);
196        assert!(matches!(v, Value::Double(_)));
197    }
198
199    #[test]
200    fn value_from_bool() {
201        assert!(matches!(Value::from(true), Value::Bool(true)));
202        assert!(matches!(Value::from(false), Value::Bool(false)));
203    }
204
205    #[test]
206    fn value_from_string() {
207        let v = Value::from("hello".to_string());
208        assert!(matches!(v, Value::String(ref s) if s == "hello"));
209    }
210
211    #[test]
212    fn value_from_str() {
213        let v = Value::from("hello");
214        assert!(matches!(v, Value::String(ref s) if s == "hello"));
215    }
216
217    #[test]
218    fn value_from_uuid() {
219        let id = Uuid::new_v4();
220        let v = Value::from(id);
221        assert!(matches!(v, Value::Uuid(_)));
222    }
223
224    #[test]
225    fn value_from_bytes() {
226        let v = Value::from(vec![1u8, 2, 3]);
227        assert!(matches!(v, Value::Bytes(ref b) if b.len() == 3));
228    }
229
230    // ── Nullable type tests ──
231
232    #[test]
233    fn value_from_none_i32() {
234        let v = Value::from(None::<i32>);
235        assert!(matches!(v, Value::OptionInt(None)));
236    }
237
238    #[test]
239    fn value_from_some_i32() {
240        let v = Value::from(Some(42i32));
241        assert!(matches!(v, Value::OptionInt(Some(42))));
242    }
243
244    #[test]
245    fn value_from_none_string() {
246        let v = Value::from(None::<String>);
247        assert!(matches!(v, Value::OptionString(None)));
248    }
249
250    #[test]
251    fn value_from_some_string() {
252        let v = Value::from(Some("hi".to_string()));
253        assert!(matches!(v, Value::OptionString(Some(ref s)) if s == "hi"));
254    }
255
256    #[test]
257    fn value_from_none_uuid() {
258        let v = Value::from(None::<Uuid>);
259        assert!(matches!(v, Value::OptionUuid(None)));
260    }
261
262    #[test]
263    fn value_from_some_uuid() {
264        let id = Uuid::new_v4();
265        let v = Value::from(Some(id));
266        assert!(matches!(v, Value::OptionUuid(Some(_))));
267    }
268
269    #[test]
270    fn value_from_none_bool() {
271        let v = Value::from(None::<bool>);
272        assert!(matches!(v, Value::OptionBool(None)));
273    }
274
275    #[test]
276    fn value_from_none_datetime() {
277        let v = Value::from(None::<DateTime<Utc>>);
278        assert!(matches!(v, Value::OptionDateTime(None)));
279    }
280
281    #[test]
282    fn value_from_none_naive_date() {
283        let v = Value::from(None::<NaiveDate>);
284        assert!(matches!(v, Value::OptionNaiveDate(None)));
285    }
286
287    #[test]
288    fn value_from_none_naive_time() {
289        let v = Value::from(None::<NaiveTime>);
290        assert!(matches!(v, Value::OptionNaiveTime(None)));
291    }
292
293    #[test]
294    fn value_from_none_bytes() {
295        let v = Value::from(None::<Vec<u8>>);
296        assert!(matches!(v, Value::OptionBytes(None)));
297    }
298
299    // ── Equality tests ──
300
301    #[test]
302    fn value_equality() {
303        assert_eq!(Value::Int(42), Value::Int(42));
304        assert_ne!(Value::Int(42), Value::Int(43));
305        assert_ne!(Value::Int(42), Value::Short(42));
306    }
307
308    #[test]
309    fn value_option_equality() {
310        assert_eq!(Value::OptionInt(Some(1)), Value::OptionInt(Some(1)));
311        assert_eq!(Value::OptionInt(None), Value::OptionInt(None));
312        assert_ne!(Value::OptionInt(None), Value::OptionString(None));
313    }
314}