-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(mssql): support DateTime2 type via chrono
- Loading branch information
Showing
8 changed files
with
153 additions
and
4 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
use byteorder::{ByteOrder, LittleEndian}; | ||
use chrono::{Datelike, NaiveTime, Timelike}; | ||
|
||
use crate::decode::Decode; | ||
use crate::encode::{Encode, IsNull}; | ||
use crate::error::BoxDynError; | ||
use crate::mssql::{Mssql, MssqlTypeInfo, MssqlValueRef}; | ||
use crate::mssql::protocol::type_info::{DataType, TypeInfo}; | ||
use crate::types::Type; | ||
|
||
/// Provides conversion of chrono::DateTime (UTC) to MS SQL DateTime2N | ||
/// | ||
/// Note that MS SQL has a number of DateTime-related types and conversion | ||
/// might not work. | ||
/// During encoding, values are always encoded with the best possible | ||
/// precision, which uses 5 digits for nanoseconds. | ||
impl Type<Mssql> for chrono::DateTime<chrono::Utc> { | ||
fn type_info() -> MssqlTypeInfo { | ||
MssqlTypeInfo(TypeInfo { | ||
scale: 7, | ||
ty: DataType::DateTime2N, | ||
size: 8, | ||
collation: None, | ||
precision: 0 | ||
}) | ||
} | ||
|
||
fn compatible(ty: &MssqlTypeInfo) -> bool { | ||
matches!(ty.0.ty, DataType::DateTime2N) | ||
} | ||
} | ||
|
||
/// Split the time into days from Gregorian calendar, seconds and nanoseconds | ||
/// as required for DateTime2 | ||
fn split_time(date_time: &chrono::DateTime<chrono::Utc>) -> (i32, u32, u32) { | ||
let mut days = date_time.num_days_from_ce() - 1; | ||
let mut seconds = date_time.num_seconds_from_midnight(); | ||
let mut ns = date_time.nanosecond(); | ||
|
||
// this date format cannot encode anything outside of 0000-01-01 to 9999-12-31 | ||
// so it's best to do some bounds-checking | ||
if days < 0 { | ||
days = 0; | ||
seconds = 0; | ||
ns = 0; | ||
} else if days > 3652058 { | ||
// corresponds to 9999-12-31, the highest plausible value for YYYY-MM-DD | ||
days = 3652058; | ||
seconds = 59 + 59*60 + 23*3600; | ||
ns = 999999900 | ||
} | ||
(days, seconds, ns) | ||
} | ||
|
||
/// Encodes DateTime objects for transfer over the wire | ||
impl Encode<'_, Mssql> for chrono::DateTime<chrono::Utc> { | ||
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull { | ||
let (days, seconds, ns) = split_time(self); | ||
|
||
// always use full scale=7, using 5 bytes | ||
let mut date = [0u8; 8]; | ||
let ns_total = (seconds as i64) * 1_000_000_000 + ns as i64; | ||
let t = ns_total / 100; | ||
for i in 0..5 { | ||
date[i] = (t >> i*8) as u8; | ||
} | ||
LittleEndian::write_i24(&mut date[5..8], days); | ||
buf.extend_from_slice(&date); | ||
IsNull::No | ||
} | ||
} | ||
|
||
/// Determines seconds since midnight and nanoseconds since the last second | ||
fn decode_time(scale: u8, data: &[u8]) -> (u32, u32) { | ||
let mut acc = 0u64; | ||
for i in (0..data.len()).rev() { | ||
acc <<= 8; | ||
acc |= data[i] as u64; | ||
} | ||
for _i in 0..(7 - scale) { | ||
acc *= 10; | ||
} | ||
let nsbig = acc * 100; | ||
let seconds = nsbig / 1_000_000_000; | ||
let ns = nsbig % 1_000_000_000; | ||
(seconds as u32, ns as u32) | ||
} | ||
|
||
/// Decodes DateTime2N values received from the server | ||
impl Decode<'_, Mssql> for chrono::DateTime<chrono::Utc> { | ||
fn decode(value: MssqlValueRef<'_>) -> Result<Self, BoxDynError> { | ||
let bytes = value.as_bytes()?; | ||
let timesize = bytes.len() - 3; | ||
|
||
let days_from_ce = LittleEndian::read_i24(&bytes[timesize..]); | ||
let day = chrono::NaiveDate::from_num_days_from_ce(days_from_ce + 1); | ||
|
||
let (seconds, nanoseconds) = decode_time(value.type_info.0.scale, &bytes[0..timesize]); | ||
let time = NaiveTime::from_num_seconds_from_midnight(seconds, nanoseconds); | ||
|
||
Ok(chrono::DateTime::from_utc(day.and_time(time), chrono::Utc)) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use chrono::{DateTime, Timelike}; | ||
|
||
use crate::decode::Decode; | ||
use crate::encode::{Encode, IsNull}; | ||
use crate::mssql::{MssqlTypeInfo, MssqlValueRef}; | ||
use crate::mssql::protocol::type_info::{DataType, TypeInfo}; | ||
|
||
#[test] | ||
fn test_decode_from_encode() { | ||
let now = chrono::Utc::now(); | ||
let mut buf: Vec<u8> = Vec::new(); | ||
assert!(matches!(now.encode_by_ref(&mut buf), IsNull::No)); | ||
|
||
let decoded: DateTime<chrono::Utc> = chrono::DateTime::decode(MssqlValueRef { | ||
data: Some(&bytes::Bytes::from(buf)), | ||
type_info: MssqlTypeInfo(TypeInfo { | ||
ty: DataType::DateTime2N, | ||
size: 8, | ||
precision: 0, | ||
scale: 7, | ||
collation: None}) | ||
}).unwrap(); | ||
|
||
let now_truncated_ns = now.with_nanosecond((now.nanosecond()/ 10000) * 10000).unwrap(); | ||
assert_eq!(now_truncated_ns, decoded); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters