rbdc-mssql 4.6.0

The Rust SQL Toolkit and ORM Library. An async, pure Rust SQL crate featuring compile-time Dynamic SQL
Documentation
use chrono::{FixedOffset, NaiveDateTime};
use fastdate::offset_sec;
use rbdc::datetime::DateTime;
use rbdc::Error;
use rbs::{value, Value};
use tiberius::numeric::BigDecimal;
use tiberius::ColumnData;

pub trait Decode {
    fn decode(row: &ColumnData<'static>) -> Result<Value, Error>;
}

impl Decode for Value {
    fn decode(row: &ColumnData<'static>) -> Result<Value, Error> {
        Ok(match row {
            ColumnData::U8(v) => match v {
                None => Value::Null,
                Some(v) => Value::U32(v.clone() as u32),
            },
            ColumnData::I16(v) => match v {
                None => Value::Null,
                Some(v) => Value::I32(v.clone() as i32),
            },
            ColumnData::I32(v) => match v {
                None => Value::Null,
                Some(v) => Value::I32(v.clone()),
            },
            ColumnData::I64(v) => match v {
                None => Value::Null,
                Some(v) => Value::I64(v.clone()),
            },
            ColumnData::F32(v) => match v {
                None => Value::Null,
                Some(v) => Value::F32(v.clone()),
            },
            ColumnData::F64(v) => match v {
                None => Value::Null,
                Some(v) => Value::F64(v.clone()),
            },
            ColumnData::Bit(v) => match v {
                None => Value::Null,
                Some(v) => Value::Bool(v.clone()),
            },
            ColumnData::String(v) => match v {
                None => Value::Null,
                Some(v) => Value::String(v.to_string()),
            },
            ColumnData::Guid(v) => match v {
                None => Value::Null,
                Some(v) => Value::String(v.to_string()).into_ext("Uuid"),
            },
            ColumnData::Binary(v) => match v {
                None => Value::Null,
                Some(v) => Value::Binary(v.to_vec()),
            },
            ColumnData::Numeric(v) => match v {
                None => Value::Null,
                Some(_) => {
                    let v: tiberius::Result<Option<BigDecimal>> = tiberius::FromSql::from_sql(row);
                    match v {
                        Ok(v) => match v {
                            None => Value::Null,
                            Some(v) => Value::String(v.to_string()).into_ext("Decimal"),
                        },
                        Err(e) => {
                            return Err(Error::from(e.to_string()));
                        }
                    }
                }
            },
            ColumnData::Xml(v) => match v {
                None => Value::Null,
                Some(v) => Value::String(v.to_string()).into_ext("Xml"),
            },
            ColumnData::DateTime(v) => match v {
                None => Value::Null,
                Some(_) => {
                    let v: tiberius::Result<Option<NaiveDateTime>> =
                        tiberius::FromSql::from_sql(row);
                    match v {
                        Ok(v) => match v {
                            None => Value::Null,
                            Some(v) => value!(DateTime(
                                <fastdate::DateTime as DateTimeFromNativeDatetime>::from(v)
                            )),
                        },
                        Err(e) => {
                            return Err(Error::from(e.to_string()));
                        }
                    }
                }
            },
            ColumnData::SmallDateTime(m) => match m {
                None => Value::Null,
                Some(_) => {
                    let v: tiberius::Result<Option<NaiveDateTime>> =
                        tiberius::FromSql::from_sql(row);
                    match v {
                        Ok(v) => match v {
                            None => Value::Null,
                            Some(v) => value!(DateTime(
                                <fastdate::DateTime as DateTimeFromNativeDatetime>::from(v)
                            )),
                        },
                        Err(e) => {
                            return Err(Error::from(e.to_string()));
                        }
                    }
                }
            },
            ColumnData::Time(v) => match v {
                None => Value::Null,
                Some(_) => {
                    let v: tiberius::Result<Option<chrono::NaiveTime>> =
                        tiberius::FromSql::from_sql(row);
                    match v {
                        Ok(v) => match v {
                            None => Value::Null,
                            Some(v) => Value::String(v.to_string()).into_ext("Time"),
                        },
                        Err(e) => {
                            return Err(Error::from(e.to_string()));
                        }
                    }
                }
            },
            ColumnData::Date(v) => match v {
                None => Value::Null,
                Some(_) => {
                    let v: tiberius::Result<Option<chrono::NaiveDate>> =
                        tiberius::FromSql::from_sql(row);
                    match v {
                        Ok(v) => match v {
                            None => Value::Null,
                            Some(v) => Value::String(v.to_string()).into_ext("Date"),
                        },
                        Err(e) => {
                            return Err(Error::from(e.to_string()));
                        }
                    }
                }
            },
            ColumnData::DateTime2(v) => match v {
                None => Value::Null,
                Some(_) => {
                    let v: tiberius::Result<Option<NaiveDateTime>> =
                        tiberius::FromSql::from_sql(row);
                    match v {
                        Ok(v) => match v {
                            None => Value::Null,
                            Some(v) => value!(DateTime(
                                <fastdate::DateTime as DateTimeFromNativeDatetime>::from(v)
                            )),
                        },
                        Err(e) => {
                            return Err(Error::from(e.to_string()));
                        }
                    }
                }
            },
            ColumnData::DateTimeOffset(v) => match v {
                None => Value::Null,
                Some(_) => {
                    let v: tiberius::Result<Option<chrono::DateTime<FixedOffset>>> =
                        tiberius::FromSql::from_sql(row);
                    match v {
                        Ok(v) => match v {
                            None => Value::Null,
                            Some(v) => {
                                let dt = DateTime(fastdate::DateTime::from_timestamp_nano(
                                    v.timestamp_nanos_opt()
                                        .expect("value can not be represented in a timestamp with nanosecond precision.") as i128 - (v.offset().utc_minus_local() * 60) as i128).set_offset(v.offset().utc_minus_local() * 60));
                                value!(dt)
                            }
                        },
                        Err(e) => {
                            return Err(Error::from(e.to_string()));
                        }
                    }
                }
            },
        })
    }
}

pub trait DateTimeFromNativeDatetime {
    fn from(arg: chrono::NaiveDateTime) -> Self;
}

pub trait DateTimeFromDateTimeFixedOffset {
    fn from(arg: chrono::DateTime<FixedOffset>) -> Self;
}

impl DateTimeFromNativeDatetime for fastdate::DateTime {
    fn from(arg: NaiveDateTime) -> Self {
        fastdate::DateTime::from_timestamp_nano(
            arg.and_utc()
                .timestamp_nanos_opt()
                .expect("value can not be represented in a timestamp with nanosecond precision.")
                as i128,
        )
        .set_offset(offset_sec())
        .add_sub_sec(-offset_sec() as i64)
    }
}

impl DateTimeFromDateTimeFixedOffset for fastdate::DateTime {
    fn from(arg: chrono::DateTime<FixedOffset>) -> Self {
        fastdate::DateTime::from_timestamp_nano(
            arg.timestamp_nanos_opt()
                .expect("value can not be represented in a timestamp with nanosecond precision.")
                as i128,
        )
        .set_offset(arg.offset().local_minus_utc())
    }
}

#[cfg(test)]
mod test {
    use crate::decode::{DateTimeFromDateTimeFixedOffset, DateTimeFromNativeDatetime};
    use chrono::{FixedOffset, NaiveDateTime};
    use fastdate::DateTime;

    #[test]
    fn test_decode_time_zone() {
        let offset = FixedOffset::east_opt(8 * 60 * 60).unwrap();
        let dt: chrono::DateTime<FixedOffset> = chrono::DateTime::from_naive_utc_and_offset(
            NaiveDateTime::from_timestamp_opt(1697801035, 0).unwrap(),
            offset,
        );
        println!("{}", dt.to_string());
        let de = <DateTime as DateTimeFromDateTimeFixedOffset>::from(dt);
        println!("{}", de.to_string());
        assert_eq!(
            dt.to_string().replacen(" ", "T", 1).replace(" ", ""),
            de.display(true)
        );
    }

    #[test]
    fn test_decode_zone_native() {
        let dt = NaiveDateTime::from_timestamp_opt(1698039464, 0).unwrap();
        println!("{}", dt.to_string());
        let de = <DateTime as DateTimeFromNativeDatetime>::from(dt);
        println!("{}", de.to_string());
        assert_eq!(dt.to_string(), de.display_stand());
    }
}