tiberius icon indicating copy to clipboard operation
tiberius copied to clipboard

allow converting from DateTime2 to Datetimen in 'tds73'

Open Geo-W opened this issue 2 years ago • 1 comments

This pr tries to address the following problem: When inserting NaiveDateTime into ms datetime columns with bulk_insert and tds73 is turned on by default, it would raise
Err` value: BulkInput("invalid data type, expecting Some(VarLenSized(VarLenContext { type: Datetimen, len: 8, collation: None })) but found DateTime2(Some(DateTime2 { date: Date(693604), time: Time { increments: 330110000000, scale: 7 } }))

cannot just disable tds73 feature as date/time might in the same table with datetime simultaneously and into_sql/to_sql is impl for date/time in without tds73 only.

normal inserting with execute does not get this issue as it calls DateTime2 to None when converting. https://github.com/prisma/tiberius/blob/98943b21b5f7a5135d3ae4bd51acbf268bb99189/src/tds/codec/column_data.rs#L615-L619

Don't know whether it is a good solution. Or maybe any other impl can solve this issue is appreciated. Thank you so much~

Geo-W avatar Jun 02 '23 15:06 Geo-W

I'm seeing a similar issue, but with a regular DATETIME column. If I were to make a feature request, I'd ask that the legacy DateTime conversion functionality be made available for manual use.

As a workaround, I've copied/modified the legacy PrimitiveDateTime handling from tds/time.rs (gated behind #[cfg(not(feature = "tds73"))]), spitting out a ColumnData::DateTime variant that can be fed into bulk insert's TokenRow.

It's a little extra work, but it prevents a mandatory conversion to DateTime2:

pub fn naive_dt_to_datetime1<'a>(dt: NaiveDateTime) -> ColumnData<'a> {
    fn to_days(date: NaiveDate, start_year: i32) -> i64 {
        (date - NaiveDate::from_ymd_opt(start_year, 1, 1).unwrap()).num_days()
    }

    fn to_sec_fragments(from: NaiveTime) -> i64 {
        let nanos: i64 = (from - NaiveTime::from_hms_opt(0, 0, 0).unwrap())
            .num_nanoseconds()
            .unwrap();

        nanos * 300 / (1e9 as i64)
    }

    let date = dt.date();
    let time = dt.time();

    let days = to_days(date, 1900) as i32;
    let seconds_fragments = to_sec_fragments(time);

    let dt = tiberius::time::DateTime::new(days, seconds_fragments as u32);
    ColumnData::DateTime(Some(dt))
}

For some example usage:

/// Convert my domain object into a row usable by bulk insert
impl<'a> IntoRow<'a> for EventRow {
    fn into_row(self) -> TokenRow<'a> {
        let mut row = TokenRow::new();

        row.push(naive_dt_to_datetime1(self.timestamp.naive_utc()));
        row.push(self.event_id.into_sql());

        row
    }
}

/// Perform bulk insert
async fn do_inserts(db: &Client, events: &Vec<EventRow>) -> anyhow::Result<()>{
    let mut bulk = db.bulk_insert("TABLE_NAME").await?;
    for evt in events {
        let row = evt.into_row();
        bulk.send(row).await?;
    }
    _ = bulk.finalize().await?;
   Ok(())
}

NTmatter avatar Sep 24 '24 22:09 NTmatter