Skip to main content

spring_batch_rs/item/rdbc/
column_value.rs

1//! Type-erased column value for RDBC item writers.
2
3/// A type-erased value that can be bound to a database column.
4///
5/// Used by RDBC item writers to carry field values from item extractor closures
6/// to database parameter binding at write time. This enum allows storing values
7/// of different types in a single collection without requiring static type dispatch.
8///
9/// # Examples
10///
11/// ```
12/// use spring_batch_rs::item::rdbc::ColumnValue;
13///
14/// let v: ColumnValue = 42i32.into();
15/// assert!(matches!(v, ColumnValue::Int(42)));
16///
17/// let v: ColumnValue = None::<i32>.into();
18/// assert!(matches!(v, ColumnValue::Null));
19/// ```
20#[derive(Clone, Debug, PartialEq)]
21pub enum ColumnValue {
22    /// Signed integer (covers i32, i64).
23    Int(i64),
24    /// Floating-point number (covers f32, f64).
25    Float(f64),
26    /// UTF-8 text (covers &str, String).
27    Text(String),
28    /// Boolean value.
29    Bool(bool),
30    /// Raw bytes.
31    Bytes(Vec<u8>),
32    /// SQL NULL — produced by Option::None.
33    Null,
34}
35
36impl From<i16> for ColumnValue {
37    fn from(v: i16) -> Self {
38        ColumnValue::Int(v as i64)
39    }
40}
41
42impl From<i32> for ColumnValue {
43    fn from(v: i32) -> Self {
44        ColumnValue::Int(v as i64)
45    }
46}
47
48impl From<i64> for ColumnValue {
49    fn from(v: i64) -> Self {
50        ColumnValue::Int(v)
51    }
52}
53
54impl From<f32> for ColumnValue {
55    fn from(v: f32) -> Self {
56        ColumnValue::Float(v as f64)
57    }
58}
59
60impl From<f64> for ColumnValue {
61    fn from(v: f64) -> Self {
62        ColumnValue::Float(v)
63    }
64}
65
66impl From<bool> for ColumnValue {
67    fn from(v: bool) -> Self {
68        ColumnValue::Bool(v)
69    }
70}
71
72impl From<&str> for ColumnValue {
73    fn from(v: &str) -> Self {
74        ColumnValue::Text(v.to_string())
75    }
76}
77
78impl From<String> for ColumnValue {
79    fn from(v: String) -> Self {
80        ColumnValue::Text(v)
81    }
82}
83
84impl From<Vec<u8>> for ColumnValue {
85    fn from(v: Vec<u8>) -> Self {
86        ColumnValue::Bytes(v)
87    }
88}
89
90impl<T: Into<ColumnValue>> From<Option<T>> for ColumnValue {
91    fn from(v: Option<T>) -> Self {
92        match v {
93            Some(inner) => inner.into(),
94            None => ColumnValue::Null,
95        }
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn should_convert_i16_to_int() {
105        assert!(matches!(ColumnValue::from(7i16), ColumnValue::Int(7)));
106    }
107
108    #[test]
109    fn should_convert_i32_to_int() {
110        assert!(matches!(ColumnValue::from(42i32), ColumnValue::Int(42)));
111    }
112
113    #[test]
114    fn should_convert_i64_to_int() {
115        assert!(matches!(ColumnValue::from(100i64), ColumnValue::Int(100)));
116    }
117
118    #[test]
119    fn should_convert_f32_to_float() {
120        let v = ColumnValue::from(1.5f32);
121        assert!(matches!(v, ColumnValue::Float(_)));
122        if let ColumnValue::Float(f) = v {
123            assert!((f - 1.5f64).abs() < 1e-5, "f32 should be widened to f64");
124        }
125    }
126
127    #[test]
128    fn should_convert_f64_to_float() {
129        assert!(matches!(ColumnValue::from(3.14f64), ColumnValue::Float(_)));
130    }
131
132    #[test]
133    fn should_convert_bool_to_bool() {
134        assert!(matches!(ColumnValue::from(true), ColumnValue::Bool(true)));
135        assert!(matches!(ColumnValue::from(false), ColumnValue::Bool(false)));
136    }
137
138    #[test]
139    fn should_convert_str_to_text() {
140        assert!(matches!(ColumnValue::from("hello"), ColumnValue::Text(_)));
141    }
142
143    #[test]
144    fn should_convert_string_to_text() {
145        assert!(matches!(
146            ColumnValue::from("world".to_string()),
147            ColumnValue::Text(_)
148        ));
149    }
150
151    #[test]
152    fn should_convert_bytes_to_bytes() {
153        let v = ColumnValue::from(vec![1u8, 2, 3]);
154        assert!(matches!(v, ColumnValue::Bytes(_)));
155    }
156
157    #[test]
158    fn should_convert_some_i32_to_int() {
159        assert!(matches!(ColumnValue::from(Some(7i32)), ColumnValue::Int(7)));
160    }
161
162    #[test]
163    fn should_convert_none_i32_to_null() {
164        assert!(matches!(ColumnValue::from(None::<i32>), ColumnValue::Null));
165    }
166
167    #[test]
168    fn should_convert_some_string_to_text() {
169        let v = ColumnValue::from(Some("abc".to_string()));
170        assert!(matches!(v, ColumnValue::Text(_)));
171    }
172
173    #[test]
174    fn should_convert_none_string_to_null() {
175        assert!(matches!(
176            ColumnValue::from(None::<String>),
177            ColumnValue::Null
178        ));
179    }
180}