Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow converting from DateTime2 to Datetimen in 'tds73' #298

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

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