Skip to content

Conversation

@Geo-W
Copy link

@Geo-W Geo-W commented Jun 2, 2023

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.

(ColumnData::DateTime2(Some(dt)), None) => {
let len = dt.time().len()? + 3;
dst.extend_from_slice(&[VarLenType::Datetime2 as u8, dt.time().scale(), len]);
dt.encode(&mut *dst)?;
}

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

@NTmatter
Copy link

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(())
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants