Skip to main content

nautilus_core/
value.rs

1//! Database value types.
2
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4
5/// Database value type.
6///
7/// Implements custom JSON serialization for cross-language compatibility:
8/// - `Decimal` → string (avoid precision loss)
9/// - `DateTime` → RFC3339 string
10/// - `Uuid` → hyphenated lowercase string
11/// - `Bytes` → base64 string
12#[derive(Debug, Clone, PartialEq)]
13pub enum Value {
14    /// NULL value.
15    Null,
16    /// Boolean.
17    Bool(bool),
18    /// 32-bit integer.
19    I32(i32),
20    /// 64-bit integer.
21    I64(i64),
22    /// 64-bit float.
23    F64(f64),
24    /// Decimal number with arbitrary precision.
25    Decimal(rust_decimal::Decimal),
26    /// Date and time (without timezone).
27    DateTime(chrono::NaiveDateTime),
28    /// UUID.
29    Uuid(uuid::Uuid),
30    /// JSON value.
31    Json(serde_json::Value),
32    /// String.
33    String(String),
34    /// Byte array.
35    Bytes(Vec<u8>),
36    /// Array of values (PostgreSQL native arrays).
37    Array(Vec<Value>),
38    /// 2D array of values (PostgreSQL multi-dimensional arrays).
39    Array2D(Vec<Vec<Value>>),
40    /// A database enum value with its PostgreSQL type name.
41    ///
42    /// Carries the variant string (e.g. `"ADMIN"`) together with the
43    /// lowercase PG type name (e.g. `"role"`) so that the PostgreSQL
44    /// dialect can emit the required explicit cast (`$1::role`).
45    /// All other backends treat this identically to `Value::String`.
46    Enum {
47        /// The enum variant string sent to / received from the DB.
48        value: String,
49        /// Lowercase PostgreSQL type name (e.g. `"role"`, `"poststatus"`).
50        type_name: String,
51    },
52}
53
54impl From<bool> for Value {
55    fn from(v: bool) -> Self {
56        Value::Bool(v)
57    }
58}
59
60impl From<i32> for Value {
61    fn from(v: i32) -> Self {
62        Value::I32(v)
63    }
64}
65
66impl From<i64> for Value {
67    fn from(v: i64) -> Self {
68        Value::I64(v)
69    }
70}
71
72impl From<f64> for Value {
73    fn from(v: f64) -> Self {
74        Value::F64(v)
75    }
76}
77
78impl From<rust_decimal::Decimal> for Value {
79    fn from(v: rust_decimal::Decimal) -> Self {
80        Value::Decimal(v)
81    }
82}
83
84impl From<chrono::NaiveDateTime> for Value {
85    fn from(v: chrono::NaiveDateTime) -> Self {
86        Value::DateTime(v)
87    }
88}
89
90impl From<uuid::Uuid> for Value {
91    fn from(v: uuid::Uuid) -> Self {
92        Value::Uuid(v)
93    }
94}
95
96impl From<serde_json::Value> for Value {
97    fn from(v: serde_json::Value) -> Self {
98        Value::Json(v)
99    }
100}
101
102impl From<String> for Value {
103    fn from(v: String) -> Self {
104        Value::String(v)
105    }
106}
107
108impl From<&str> for Value {
109    fn from(v: &str) -> Self {
110        Value::String(v.to_string())
111    }
112}
113
114impl From<Vec<u8>> for Value {
115    fn from(v: Vec<u8>) -> Self {
116        Value::Bytes(v)
117    }
118}
119
120// Array conversions — generated for all scalar types that map cleanly to Value
121// via `Into<Value>`. `Vec<u8>` is intentionally excluded: it maps to
122// `Value::Bytes`, not `Value::Array`.
123macro_rules! impl_vec_from {
124    ($($t:ty),* $(,)?) => {
125        $(
126            impl From<Vec<$t>> for Value {
127                fn from(v: Vec<$t>) -> Self {
128                    Value::Array(v.into_iter().map(|x| x.into()).collect())
129                }
130            }
131
132            impl From<Vec<Vec<$t>>> for Value {
133                fn from(v: Vec<Vec<$t>>) -> Self {
134                    Value::Array2D(
135                        v.into_iter()
136                            .map(|row| row.into_iter().map(|x| x.into()).collect())
137                            .collect(),
138                    )
139                }
140            }
141        )*
142    };
143}
144
145impl_vec_from!(
146    i32,
147    i64,
148    f64,
149    bool,
150    String,
151    rust_decimal::Decimal,
152    uuid::Uuid,
153    chrono::NaiveDateTime,
154    serde_json::Value,
155);
156
157// Option<T> conversions — map None → Value::Null.
158// `Option<&str>` is kept manual because `&str` requires an explicit `.to_string()`
159// and does not implement `Into<Value>` through the generic `v.into()` path.
160macro_rules! impl_option_from {
161    ($($t:ty),* $(,)?) => {
162        $(
163            impl From<Option<$t>> for Value {
164                fn from(v: Option<$t>) -> Self {
165                    v.map(|x| x.into()).unwrap_or(Value::Null)
166                }
167            }
168        )*
169    };
170}
171
172impl_option_from!(
173    bool,
174    i32,
175    i64,
176    f64,
177    String,
178    rust_decimal::Decimal,
179    uuid::Uuid,
180    chrono::NaiveDateTime,
181);
182
183impl From<Option<&str>> for Value {
184    fn from(v: Option<&str>) -> Self {
185        v.map(|s| Value::String(s.to_string()))
186            .unwrap_or(Value::Null)
187    }
188}
189
190// Custom Serialize/Deserialize implementation for Value
191impl Serialize for Value {
192    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
193    where
194        S: Serializer,
195    {
196        match self {
197            Value::Null => serializer.serialize_none(),
198            Value::Bool(b) => serializer.serialize_bool(*b),
199            Value::I32(i) => serializer.serialize_i32(*i),
200            Value::I64(i) => serializer.serialize_i64(*i),
201            Value::F64(f) => serializer.serialize_f64(*f),
202            Value::Decimal(d) => serializer.serialize_str(&d.to_string()),
203            Value::DateTime(dt) => {
204                // Serialize as RFC3339/ISO 8601 string
205                serializer.serialize_str(&dt.format("%Y-%m-%dT%H:%M:%S%.fZ").to_string())
206            }
207            Value::Uuid(u) => serializer.serialize_str(&u.to_string()),
208            Value::Bytes(b) => {
209                // Serialize as base64 string
210                use base64::Engine;
211                serializer.serialize_str(&base64::engine::general_purpose::STANDARD.encode(b))
212            }
213            Value::Json(j) => j.serialize(serializer),
214            Value::String(s) => serializer.serialize_str(s),
215            Value::Array(arr) => {
216                use serde::ser::SerializeSeq;
217                let mut seq = serializer.serialize_seq(Some(arr.len()))?;
218                for item in arr {
219                    seq.serialize_element(item)?;
220                }
221                seq.end()
222            }
223            Value::Array2D(arr2d) => {
224                use serde::ser::SerializeSeq;
225                let mut seq = serializer.serialize_seq(Some(arr2d.len()))?;
226                for row in arr2d {
227                    let row_values: Vec<_> = row.iter().collect();
228                    seq.serialize_element(&row_values)?;
229                }
230                seq.end()
231            }
232            Value::Enum { value, .. } => serializer.serialize_str(value),
233        }
234    }
235}
236
237/// Deserializes a [`Value`] from any serde format.
238///
239/// # Implementation note
240///
241/// This implementation routes through [`serde_json::Value`] as an intermediate
242/// step. As a consequence, binary serde formats (bincode, postcard, rmp, etc.)
243/// are **not supported** — they will succeed structurally but lose type
244/// fidelity because all values are first encoded as JSON. Use this impl only
245/// with text/JSON-compatible formats.
246impl<'de> Deserialize<'de> for Value {
247    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
248    where
249        D: Deserializer<'de>,
250    {
251        // Deserialize as serde_json::Value first, then convert
252        let json: serde_json::Value = Deserialize::deserialize(deserializer)?;
253        Ok(json_to_value_ref(&json))
254    }
255}
256
257/// Convert a `&serde_json::Value` reference to a [`Value`].
258///
259/// This is the canonical JSON→Value conversion used throughout the crate.
260/// It is `pub(crate)` so that other modules (e.g. `column.rs`) can reuse it
261/// without duplicating the logic.
262///
263/// Numbers are coerced to `I32` before `I64` when they fit, then `F64`.
264/// Arrays of arrays are **not** auto-promoted to `Array2D` here; that
265/// promotion happens in the connector stream decoders where full schema
266/// knowledge is available.
267pub(crate) fn json_to_value_ref(json: &serde_json::Value) -> Value {
268    match json {
269        serde_json::Value::Null => Value::Null,
270        serde_json::Value::Bool(b) => Value::Bool(*b),
271        serde_json::Value::Number(n) => {
272            if let Some(i) = n.as_i64() {
273                // Try I32 first if it fits
274                if i >= i32::MIN as i64 && i <= i32::MAX as i64 {
275                    Value::I32(i as i32)
276                } else {
277                    Value::I64(i)
278                }
279            } else if let Some(f) = n.as_f64() {
280                Value::F64(f)
281            } else {
282                Value::String(n.to_string())
283            }
284        }
285        serde_json::Value::String(s) => Value::String(s.clone()),
286        serde_json::Value::Array(arr) => Value::Array(arr.iter().map(json_to_value_ref).collect()),
287        serde_json::Value::Object(_) => Value::Json(json.clone()),
288    }
289}
290
291#[cfg(test)]
292mod tests {
293    use core::f64;
294
295    use super::*;
296
297    #[test]
298    fn test_value_variants() {
299        assert_eq!(Value::Null, Value::Null);
300        assert_eq!(Value::Bool(true), Value::from(true));
301        assert_eq!(Value::I32(42), Value::from(42i32));
302        assert_eq!(Value::I64(42), Value::from(42i64));
303        assert_eq!(Value::F64(2.5), Value::from(2.5f64));
304        assert_eq!(Value::String("hello".to_string()), Value::from("hello"));
305        assert_eq!(Value::Bytes(vec![1, 2, 3]), Value::from(vec![1u8, 2, 3]));
306
307        // Test new types
308        use rust_decimal::Decimal;
309        let dec = Decimal::new(12345, 2); // 123.45
310        assert_eq!(Value::Decimal(dec), Value::from(dec));
311
312        use chrono::NaiveDate;
313        let dt = NaiveDate::from_ymd_opt(2024, 1, 1)
314            .unwrap()
315            .and_hms_opt(12, 0, 0)
316            .unwrap();
317        assert_eq!(Value::DateTime(dt), Value::from(dt));
318
319        use uuid::Uuid;
320        let id = Uuid::nil();
321        assert_eq!(Value::Uuid(id), Value::from(id));
322
323        use serde_json::json;
324        let j = json!({"key": "value"});
325        assert_eq!(Value::Json(j.clone()), Value::from(j));
326    }
327
328    #[test]
329    fn test_value_serialization_null() {
330        let value = Value::Null;
331        let json = serde_json::to_value(&value).unwrap();
332        assert_eq!(json, serde_json::Value::Null);
333
334        let deserialized: Value = serde_json::from_value(json).unwrap();
335        assert_eq!(deserialized, Value::Null);
336    }
337
338    #[test]
339    fn test_value_serialization_bool() {
340        let value = Value::Bool(true);
341        let json = serde_json::to_value(&value).unwrap();
342        assert_eq!(json, serde_json::Value::Bool(true));
343
344        let deserialized: Value = serde_json::from_value(json).unwrap();
345        assert_eq!(deserialized, Value::Bool(true));
346    }
347
348    #[test]
349    fn test_value_serialization_numbers() {
350        // I32
351        let value = Value::I32(42);
352        let json = serde_json::to_value(&value).unwrap();
353        assert_eq!(json.as_i64(), Some(42));
354
355        // I64
356        let value = Value::I64(9007199254740991);
357        let json = serde_json::to_value(&value).unwrap();
358        assert_eq!(json.as_i64(), Some(9007199254740991));
359
360        // F64
361        let value = Value::F64(f64::consts::PI);
362        let json = serde_json::to_value(&value).unwrap();
363        assert_eq!(json.as_f64(), Some(f64::consts::PI));
364    }
365
366    #[test]
367    fn test_value_serialization_string() {
368        let value = Value::String("hello world".to_string());
369        let json = serde_json::to_value(&value).unwrap();
370        assert_eq!(json.as_str(), Some("hello world"));
371
372        let deserialized: Value = serde_json::from_value(json).unwrap();
373        assert_eq!(deserialized, Value::String("hello world".to_string()));
374    }
375
376    #[test]
377    fn test_value_serialization_decimal() {
378        use rust_decimal::Decimal;
379        let dec = Decimal::new(12345, 2); // 123.45
380        let value = Value::Decimal(dec);
381
382        let json = serde_json::to_value(&value).unwrap();
383        assert_eq!(json.as_str(), Some("123.45"));
384    }
385
386    #[test]
387    fn test_value_serialization_datetime() {
388        use chrono::NaiveDate;
389        let dt = NaiveDate::from_ymd_opt(2026, 2, 18)
390            .unwrap()
391            .and_hms_opt(10, 30, 45)
392            .unwrap();
393        let value = Value::DateTime(dt);
394
395        let json = serde_json::to_value(&value).unwrap();
396        let s = json.as_str().unwrap();
397        assert!(s.starts_with("2026-02-18T10:30:45"));
398    }
399
400    #[test]
401    fn test_value_serialization_uuid() {
402        use uuid::Uuid;
403        let id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
404        let value = Value::Uuid(id);
405
406        let json = serde_json::to_value(&value).unwrap();
407        assert_eq!(json.as_str(), Some("550e8400-e29b-41d4-a716-446655440000"));
408    }
409
410    #[test]
411    fn test_value_serialization_bytes() {
412        let value = Value::Bytes(vec![72, 101, 108, 108, 111]); // "Hello"
413
414        let json = serde_json::to_value(&value).unwrap();
415        // Should be base64 encoded
416        assert_eq!(json.as_str(), Some("SGVsbG8="));
417    }
418
419    #[test]
420    fn test_value_serialization_json() {
421        use serde_json::json;
422        let j = json!({"name": "Alice", "age": 30});
423        let value = Value::Json(j.clone());
424
425        let serialized = serde_json::to_value(&value).unwrap();
426        assert_eq!(serialized, j);
427
428        let deserialized: Value = serde_json::from_value(serialized).unwrap();
429        assert_eq!(deserialized, Value::Json(j));
430    }
431
432    #[test]
433    fn test_value_serialization_array() {
434        let value = Value::Array(vec![
435            Value::String("a".to_string()),
436            Value::String("b".to_string()),
437            Value::String("c".to_string()),
438        ]);
439
440        let json = serde_json::to_value(&value).unwrap();
441        assert_eq!(json[0].as_str(), Some("a"));
442        assert_eq!(json[1].as_str(), Some("b"));
443        assert_eq!(json[2].as_str(), Some("c"));
444
445        let deserialized: Value = serde_json::from_value(json).unwrap();
446        assert_eq!(deserialized, value);
447    }
448
449    #[test]
450    fn test_value_serialization_array2d() {
451        let value = Value::Array2D(vec![
452            vec![Value::I32(1), Value::I32(2)],
453            vec![Value::I32(3), Value::I32(4)],
454        ]);
455
456        // Serialization: Array2D renders as a nested JSON array — values are
457        // preserved losslessly.
458        let json = serde_json::to_value(&value).unwrap();
459        assert_eq!(json[0][0].as_i64(), Some(1));
460        assert_eq!(json[0][1].as_i64(), Some(2));
461        assert_eq!(json[1][0].as_i64(), Some(3));
462        assert_eq!(json[1][1].as_i64(), Some(4));
463
464        // Deserialization: without schema context the `Array2D` heuristic is
465        // intentionally absent from `json_to_value_ref`. A nested JSON array
466        // round-trips as `Array(Array(_))`. Promotion to `Array2D` is the
467        // connector stream's responsibility.
468        let expected = Value::Array(vec![
469            Value::Array(vec![Value::I32(1), Value::I32(2)]),
470            Value::Array(vec![Value::I32(3), Value::I32(4)]),
471        ]);
472        let deserialized: Value = serde_json::from_value(json).unwrap();
473        assert_eq!(deserialized, expected);
474    }
475
476    #[test]
477    fn test_value_round_trip() {
478        let values = vec![
479            Value::Null,
480            Value::Bool(false),
481            Value::I32(-42),
482            Value::I64(9007199254740991), // Large I64 beyond i32 range
483            Value::F64(f64::consts::E),
484            Value::String("test".to_string()),
485            Value::Array(vec![Value::I32(1), Value::I32(2)]),
486        ];
487
488        for value in values {
489            let json = serde_json::to_value(&value).unwrap();
490            let deserialized: Value = serde_json::from_value(json).unwrap();
491            assert_eq!(deserialized, value);
492        }
493    }
494}