rbdc_mssql/
decode.rs

1use chrono::{FixedOffset, NaiveDateTime, Timelike};
2use fastdate::offset_sec;
3use rbdc::datetime::DateTime;
4use rbdc::Error;
5use rbs::{value, Value};
6use tiberius::numeric::BigDecimal;
7use tiberius::ColumnData;
8
9pub trait Decode {
10    fn decode(row: &ColumnData<'static>) -> Result<Value, Error>;
11}
12
13impl Decode for Value {
14    fn decode(row: &ColumnData<'static>) -> Result<Value, Error> {
15        Ok(match row {
16            ColumnData::U8(v) => match v {
17                None => Value::Null,
18                Some(v) => Value::U32(v.clone() as u32),
19            },
20            ColumnData::I16(v) => match v {
21                None => Value::Null,
22                Some(v) => Value::I32(v.clone() as i32),
23            },
24            ColumnData::I32(v) => match v {
25                None => Value::Null,
26                Some(v) => Value::I32(v.clone()),
27            },
28            ColumnData::I64(v) => match v {
29                None => Value::Null,
30                Some(v) => Value::I64(v.clone()),
31            },
32            ColumnData::F32(v) => match v {
33                None => Value::Null,
34                Some(v) => Value::F32(v.clone()),
35            },
36            ColumnData::F64(v) => match v {
37                None => Value::Null,
38                Some(v) => Value::F64(v.clone()),
39            },
40            ColumnData::Bit(v) => match v {
41                None => Value::Null,
42                Some(v) => Value::Bool(v.clone()),
43            },
44            ColumnData::String(v) => match v {
45                None => Value::Null,
46                Some(v) => Value::String(v.to_string()),
47            },
48            ColumnData::Guid(v) => match v {
49                None => Value::Null,
50                Some(v) => Value::String(v.to_string()).into_ext("Uuid"),
51            },
52            ColumnData::Binary(v) => match v {
53                None => Value::Null,
54                Some(v) => Value::Binary(v.to_vec()),
55            },
56            ColumnData::Numeric(v) => match v {
57                None => Value::Null,
58                Some(_) => {
59                    let v: tiberius::Result<Option<BigDecimal>> = tiberius::FromSql::from_sql(row);
60                    match v {
61                        Ok(v) => match v {
62                            None => Value::Null,
63                            Some(v) => Value::String(v.to_string()).into_ext("Decimal"),
64                        },
65                        Err(e) => {
66                            return Err(Error::from(e.to_string()));
67                        }
68                    }
69                }
70            },
71            ColumnData::Xml(v) => match v {
72                None => Value::Null,
73                Some(v) => Value::String(v.to_string()).into_ext("Xml"),
74            },
75            ColumnData::DateTime(v) => match v {
76                None => Value::Null,
77                Some(_) => {
78                    let v: tiberius::Result<Option<NaiveDateTime>> =
79                        tiberius::FromSql::from_sql(row);
80                    match v {
81                        Ok(v) => match v {
82                            None => Value::Null,
83                            Some(v) => value!(DateTime(
84                                <fastdate::DateTime as DateTimeFromNativeDatetime>::from(v)
85                            )),
86                        },
87                        Err(e) => {
88                            return Err(Error::from(e.to_string()));
89                        }
90                    }
91                }
92            },
93            ColumnData::SmallDateTime(m) => match m {
94                None => Value::Null,
95                Some(_) => {
96                    let v: tiberius::Result<Option<NaiveDateTime>> =
97                        tiberius::FromSql::from_sql(row);
98                    match v {
99                        Ok(v) => match v {
100                            None => Value::Null,
101                            Some(v) => value!(DateTime(
102                                <fastdate::DateTime as DateTimeFromNativeDatetime>::from(v)
103                            )),
104                        },
105                        Err(e) => {
106                            return Err(Error::from(e.to_string()));
107                        }
108                    }
109                }
110            },
111            ColumnData::Time(v) => match v {
112                None => Value::Null,
113                Some(_) => {
114                    let v: tiberius::Result<Option<chrono::NaiveTime>> =
115                        tiberius::FromSql::from_sql(row);
116                    match v {
117                        Ok(v) => match v {
118                            None => Value::Null,
119                            Some(v) => Value::String(v.to_string()).into_ext("Time"),
120                        },
121                        Err(e) => {
122                            return Err(Error::from(e.to_string()));
123                        }
124                    }
125                }
126            },
127            ColumnData::Date(v) => match v {
128                None => Value::Null,
129                Some(_) => {
130                    let v: tiberius::Result<Option<chrono::NaiveDate>> =
131                        tiberius::FromSql::from_sql(row);
132                    match v {
133                        Ok(v) => match v {
134                            None => Value::Null,
135                            Some(v) => Value::String(v.to_string()).into_ext("Date"),
136                        },
137                        Err(e) => {
138                            return Err(Error::from(e.to_string()));
139                        }
140                    }
141                }
142            },
143            ColumnData::DateTime2(v) => match v {
144                None => Value::Null,
145                Some(_) => {
146                    let v: tiberius::Result<Option<NaiveDateTime>> =
147                        tiberius::FromSql::from_sql(row);
148                    match v {
149                        Ok(v) => match v {
150                            None => Value::Null,
151                            Some(v) => value!(DateTime(
152                                <fastdate::DateTime as DateTimeFromNativeDatetime>::from(v)
153                            )),
154                        },
155                        Err(e) => {
156                            return Err(Error::from(e.to_string()));
157                        }
158                    }
159                }
160            },
161            ColumnData::DateTimeOffset(v) => match v {
162                None => Value::Null,
163                Some(_) => {
164                    let v: tiberius::Result<Option<chrono::DateTime<FixedOffset>>> =
165                        tiberius::FromSql::from_sql(row);
166                    match v {
167                        Ok(v) => match v {
168                            None => Value::Null,
169                            Some(v) => {
170                                // Handle dates beyond nanosecond precision range (year 2262+)
171                                let dt = match v.timestamp_nanos_opt() {
172                                    Some(nanos) => DateTime(
173                                        fastdate::DateTime::from_timestamp_nano(
174                                            nanos as i128
175                                                - (v.offset().utc_minus_local() * 60) as i128,
176                                        )
177                                        .set_offset(v.offset().utc_minus_local() * 60),
178                                    ),
179                                    None => {
180                                        // Fallback to second precision for dates beyond nanosecond range
181                                        let timestamp_secs = v.timestamp();
182                                        let subsec_nanos = v.nanosecond();
183                                        DateTime(
184                                            fastdate::DateTime::from_timestamp_nano(
185                                                (timestamp_secs as i128) * 1_000_000_000
186                                                    + (subsec_nanos as i128)
187                                                    - (v.offset().utc_minus_local() * 60) as i128,
188                                            )
189                                            .set_offset(v.offset().utc_minus_local() * 60),
190                                        )
191                                    }
192                                };
193                                value!(dt)
194                            }
195                        },
196                        Err(e) => {
197                            return Err(Error::from(e.to_string()));
198                        }
199                    }
200                }
201            },
202        })
203    }
204}
205
206pub trait DateTimeFromNativeDatetime {
207    fn from(arg: chrono::NaiveDateTime) -> Self;
208}
209
210pub trait DateTimeFromDateTimeFixedOffset {
211    fn from(arg: chrono::DateTime<FixedOffset>) -> Self;
212}
213
214impl DateTimeFromNativeDatetime for fastdate::DateTime {
215    fn from(arg: NaiveDateTime) -> Self {
216        // Handle dates beyond nanosecond precision range (year 2262+)
217        match arg.and_utc().timestamp_nanos_opt() {
218            Some(nanos) => fastdate::DateTime::from_timestamp_nano(nanos as i128)
219                .set_offset(offset_sec())
220                .add_sub_sec(-offset_sec() as i64),
221            None => {
222                // Fallback to second precision for dates beyond nanosecond range
223                let timestamp_secs = arg.and_utc().timestamp();
224                let subsec_nanos = arg.nanosecond();
225                fastdate::DateTime::from_timestamp_nano(
226                    (timestamp_secs as i128) * 1_000_000_000 + (subsec_nanos as i128),
227                )
228                .set_offset(offset_sec())
229                .add_sub_sec(-offset_sec() as i64)
230            }
231        }
232    }
233}
234
235impl DateTimeFromDateTimeFixedOffset for fastdate::DateTime {
236    fn from(arg: chrono::DateTime<FixedOffset>) -> Self {
237        // Handle dates beyond nanosecond precision range (year 2262+)
238        match arg.timestamp_nanos_opt() {
239            Some(nanos) => fastdate::DateTime::from_timestamp_nano(nanos as i128)
240                .set_offset(arg.offset().local_minus_utc()),
241            None => {
242                // Fallback to second precision for dates beyond nanosecond range
243                let timestamp_secs = arg.timestamp();
244                let subsec_nanos = arg.nanosecond();
245                fastdate::DateTime::from_timestamp_nano(
246                    (timestamp_secs as i128) * 1_000_000_000 + (subsec_nanos as i128),
247                )
248                .set_offset(arg.offset().local_minus_utc())
249            }
250        }
251    }
252}
253
254#[cfg(test)]
255mod test {
256    use crate::decode::{DateTimeFromDateTimeFixedOffset, DateTimeFromNativeDatetime};
257    use chrono::{FixedOffset, NaiveDateTime};
258    use fastdate::DateTime;
259    
260    #[test]
261    fn test_decode_far_future_date() {
262        // Test date beyond year 2262 (nanosecond precision limit)
263        // Create a date in year 2500
264        use chrono::{NaiveDate, NaiveTime};
265
266        let date = NaiveDate::from_ymd_opt(2500, 12, 31).unwrap();
267        let time = NaiveTime::from_hms_opt(23, 59, 59).unwrap();
268        let dt = NaiveDateTime::new(date, time);
269
270        // This should not panic anymore
271        let de = <DateTime as DateTimeFromNativeDatetime>::from(dt);
272        println!("Far future date: {}", de.to_string());
273
274        // Verify the year is preserved
275        assert!(de.to_string().contains("2500"));
276    }
277
278    #[test]
279    fn test_decode_far_future_date_with_offset() {
280        // Test DateTimeOffset beyond year 2262
281        use chrono::{NaiveDate, NaiveTime};
282
283        let date = NaiveDate::from_ymd_opt(2500, 6, 15).unwrap(); // Use middle of year to avoid timezone edge cases
284        let time = NaiveTime::from_hms_opt(12, 0, 0).unwrap();
285        let dt = NaiveDateTime::new(date, time);
286        let offset = FixedOffset::east_opt(8 * 60 * 60).unwrap(); // +8 hours
287        let dt_with_offset = chrono::DateTime::from_naive_utc_and_offset(dt, offset);
288
289        // This should not panic anymore
290        let de = <DateTime as DateTimeFromDateTimeFixedOffset>::from(dt_with_offset);
291        println!("Far future date with offset: {}", de.to_string());
292
293        // Verify the year is preserved (should be 2500)
294        assert!(de.to_string().contains("2500"));
295    }
296}