pg_upsert/
types.rs

1use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Utc};
2use sqlx::Arguments;
3use sqlx::postgres::PgArguments;
4
5#[derive(Debug, Clone)]
6pub enum FieldValue {
7    Int32(i32),
8    Int64(i64),
9    Float32(f32),
10    Float64(f64),
11    Bool(bool),
12    String(String),
13    Bytes(Vec<u8>),
14    Date(NaiveDate),
15    Time(NaiveTime),
16    DateTime(NaiveDateTime),
17    DateTimeUtc(DateTime<Utc>),
18    Null,
19}
20
21impl FieldValue {
22    pub fn bind_to(&self, args: &mut PgArguments) {
23        match self {
24            FieldValue::Int32(v) => args.add(v).unwrap(),
25            FieldValue::Int64(v) => args.add(v).unwrap(),
26            FieldValue::Float32(v) => args.add(v).unwrap(),
27            FieldValue::Float64(v) => args.add(v).unwrap(),
28            FieldValue::Bool(v) => args.add(v).unwrap(),
29            FieldValue::String(v) => args.add(v).unwrap(),
30            FieldValue::Bytes(v) => args.add(v).unwrap(),
31            FieldValue::Date(v) => args.add(v).unwrap(),
32            FieldValue::Time(v) => args.add(v).unwrap(),
33            FieldValue::DateTime(v) => args.add(v).unwrap(),
34            FieldValue::DateTimeUtc(v) => args.add(v).unwrap(),
35            FieldValue::Null => args.add(None::<i32>).unwrap(),
36        }
37    }
38}
39
40impl From<i32> for FieldValue {
41    fn from(v: i32) -> Self {
42        FieldValue::Int32(v)
43    }
44}
45
46impl From<i64> for FieldValue {
47    fn from(v: i64) -> Self {
48        FieldValue::Int64(v)
49    }
50}
51
52impl From<f32> for FieldValue {
53    fn from(v: f32) -> Self {
54        FieldValue::Float32(v)
55    }
56}
57
58impl From<f64> for FieldValue {
59    fn from(v: f64) -> Self {
60        FieldValue::Float64(v)
61    }
62}
63
64impl From<bool> for FieldValue {
65    fn from(v: bool) -> Self {
66        FieldValue::Bool(v)
67    }
68}
69
70impl From<String> for FieldValue {
71    fn from(v: String) -> Self {
72        FieldValue::String(v)
73    }
74}
75
76impl From<&str> for FieldValue {
77    fn from(v: &str) -> Self {
78        FieldValue::String(v.to_owned())
79    }
80}
81
82impl From<Vec<u8>> for FieldValue {
83    fn from(v: Vec<u8>) -> Self {
84        FieldValue::Bytes(v)
85    }
86}
87
88impl From<NaiveDate> for FieldValue {
89    fn from(v: NaiveDate) -> Self {
90        FieldValue::Date(v)
91    }
92}
93
94impl From<NaiveTime> for FieldValue {
95    fn from(v: NaiveTime) -> Self {
96        FieldValue::Time(v)
97    }
98}
99
100impl From<NaiveDateTime> for FieldValue {
101    fn from(v: NaiveDateTime) -> Self {
102        FieldValue::DateTime(v)
103    }
104}
105
106impl From<DateTime<Utc>> for FieldValue {
107    fn from(v: DateTime<Utc>) -> Self {
108        FieldValue::DateTimeUtc(v)
109    }
110}
111
112impl<T: Into<FieldValue>> From<Option<T>> for FieldValue {
113    fn from(v: Option<T>) -> Self {
114        match v {
115            Some(val) => val.into(),
116            None => FieldValue::Null,
117        }
118    }
119}
120
121#[derive(Debug, Clone)]
122pub struct Field {
123    pub name: String,
124    pub value: FieldValue,
125}
126
127impl Field {
128    pub fn new(name: impl Into<String>, value: impl Into<FieldValue>) -> Self {
129        Self {
130            name: name.into(),
131            value: value.into(),
132        }
133    }
134}
135
136#[derive(Debug, Clone, Default)]
137pub struct UpsertOptions {
138    pub version_field: Option<String>,
139    pub do_nothing_on_conflict: bool,
140}
141
142impl UpsertOptions {
143    pub fn new() -> Self {
144        Self::default()
145    }
146
147    pub fn with_version_field(mut self, field: impl Into<String>) -> Self {
148        self.version_field = Some(field.into());
149        self
150    }
151
152    pub fn with_do_nothing_on_conflict(mut self, do_nothing: bool) -> Self {
153        self.do_nothing_on_conflict = do_nothing;
154        self
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161
162    #[test]
163    fn test_date_field_conversion() {
164        let date = NaiveDate::from_ymd_opt(2025, 12, 26).unwrap();
165        let field_value: FieldValue = date.into();
166        assert!(matches!(field_value, FieldValue::Date(_)));
167    }
168
169    #[test]
170    fn test_time_field_conversion() {
171        let time = NaiveTime::from_hms_opt(14, 30, 0).unwrap();
172        let field_value: FieldValue = time.into();
173        assert!(matches!(field_value, FieldValue::Time(_)));
174    }
175
176    #[test]
177    fn test_datetime_field_conversion() {
178        let datetime = NaiveDate::from_ymd_opt(2025, 12, 26)
179            .unwrap()
180            .and_hms_opt(14, 30, 0)
181            .unwrap();
182        let field_value: FieldValue = datetime.into();
183        assert!(matches!(field_value, FieldValue::DateTime(_)));
184    }
185
186    #[test]
187    fn test_datetime_utc_field_conversion() {
188        let datetime = DateTime::<Utc>::from_timestamp(1735225800, 0).unwrap();
189        let field_value: FieldValue = datetime.into();
190        assert!(matches!(field_value, FieldValue::DateTimeUtc(_)));
191    }
192
193    #[test]
194    fn test_optional_date_field() {
195        let some_date: Option<NaiveDate> = Some(NaiveDate::from_ymd_opt(2025, 12, 26).unwrap());
196        let field_value: FieldValue = some_date.into();
197        assert!(matches!(field_value, FieldValue::Date(_)));
198
199        let none_date: Option<NaiveDate> = None;
200        let field_value: FieldValue = none_date.into();
201        assert!(matches!(field_value, FieldValue::Null));
202    }
203}