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}