[MySQL] Add an integration tests for chrono + MySQL and fix issues

This commit is contained in:
Ryan Leckey
2019-12-30 00:32:59 -08:00
parent e161787952
commit 2a42ff9f0d
4 changed files with 242 additions and 65 deletions

View File

@@ -1,78 +1,82 @@
use byteorder::{ByteOrder, LittleEndian};
use chrono::{Datelike, NaiveDate, NaiveDateTime, Timelike};
use std::convert::TryFrom;
use byteorder::{BigEndian, ByteOrder, LittleEndian};
use chrono::{DateTime, Datelike, NaiveDate, NaiveDateTime, NaiveTime, Timelike, Utc};
use crate::decode::{Decode, DecodeError};
use crate::encode::Encode;
use crate::io::{Buf, BufMut};
use crate::mysql::protocol::Type;
use crate::mysql::types::MySqlTypeMetadata;
use crate::mysql::MySql;
use crate::types::HasSqlType;
use std::convert::TryFrom;
impl HasSqlType<NaiveDateTime> for MySql {
impl HasSqlType<DateTime<Utc>> for MySql {
fn metadata() -> Self::TypeMetadata {
MySqlTypeMetadata::new(Type::DATETIME)
MySqlTypeMetadata::new(Type::TIMESTAMP)
}
}
impl Encode<MySql> for NaiveDateTime {
impl Encode<MySql> for DateTime<Utc> {
fn encode(&self, buf: &mut Vec<u8>) {
// subtract the length byte
let length = Encode::<MySql>::size_hint(self) - 1;
Encode::<MySql>::encode(&self.naive_utc(), buf);
}
}
buf.push(length as u8);
impl Decode<MySql> for DateTime<Utc> {
fn decode(buf: &[u8]) -> Result<Self, DecodeError> {
let naive: NaiveDateTime = Decode::<MySql>::decode(buf)?;
encode_date(self.date(), buf);
Ok(DateTime::from_utc(naive, Utc))
}
}
if length >= 7 {
buf.push(self.hour() as u8);
buf.push(self.minute() as u8);
buf.push(self.second() as u8);
}
impl HasSqlType<NaiveTime> for MySql {
fn metadata() -> Self::TypeMetadata {
MySqlTypeMetadata::new(Type::TIME)
}
}
if length == 11 {
buf.extend_from_slice(&self.timestamp_subsec_micros().to_le_bytes());
}
impl Encode<MySql> for NaiveTime {
fn encode(&self, buf: &mut Vec<u8>) {
let len = Encode::<MySql>::size_hint(self) - 1;
buf.push(len as u8);
// NaiveTime is not negative
buf.push(0);
// "date on 4 bytes little-endian format" (?)
// https://mariadb.com/kb/en/resultset-row/#teimstamp-binary-encoding
buf.advance(4);
encode_time(self, len > 9, buf);
}
fn size_hint(&self) -> usize {
match (
self.hour(),
self.minute(),
self.second(),
self.timestamp_subsec_micros(),
) {
// include the length byte
(0, 0, 0, 0) => 5,
(_, _, _, 0) => 8,
(_, _, _, _) => 12,
if self.nanosecond() == 0 {
// if micro_seconds is 0, length is 8 and micro_seconds is not sent
9
} else {
// otherwise length is 12
13
}
}
}
impl Decode<MySql> for NaiveDateTime {
fn decode(raw: &[u8]) -> Result<Self, DecodeError> {
let len = raw[0];
impl Decode<MySql> for NaiveTime {
fn decode(mut buf: &[u8]) -> Result<Self, DecodeError> {
// data length, expecting 8 or 12 (fractional seconds)
let len = buf.get_u8()?;
// TODO: Make an error
assert_ne!(len, 0, "MySQL zero-dates are not supported");
// is negative : int<1>
let is_negative = buf.get_u8()?;
assert_eq!(is_negative, 0, "Negative dates/times are not supported");
let date = decode_date(&raw[1..]);
// "date on 4 bytes little-endian format" (?)
// https://mariadb.com/kb/en/resultset-row/#timestamp-binary-encoding
buf.advance(4);
Ok(if len >= 7 {
date.and_hms_micro(
raw[5] as u32,
raw[6] as u32,
raw[7] as u32,
if len == 11 {
LittleEndian::read_u32(&raw[8..])
} else {
0
},
)
} else {
date.and_hms(0, 0, 0)
})
decode_time(len - 5, buf)
}
}
@@ -86,7 +90,7 @@ impl Encode<MySql> for NaiveDate {
fn encode(&self, buf: &mut Vec<u8>) {
buf.push(4);
encode_date(*self, buf);
encode_date(self, buf);
}
fn size_hint(&self) -> usize {
@@ -95,15 +99,67 @@ impl Encode<MySql> for NaiveDate {
}
impl Decode<MySql> for NaiveDate {
fn decode(raw: &[u8]) -> Result<Self, DecodeError> {
// TODO: Return error
assert_eq!(raw[0], 4, "expected only 4 bytes");
Ok(decode_date(&raw[1..]))
fn decode(buf: &[u8]) -> Result<Self, DecodeError> {
Ok(decode_date(&buf[1..]))
}
}
fn encode_date(date: NaiveDate, buf: &mut Vec<u8>) {
impl HasSqlType<NaiveDateTime> for MySql {
fn metadata() -> Self::TypeMetadata {
MySqlTypeMetadata::new(Type::DATETIME)
}
}
impl Encode<MySql> for NaiveDateTime {
fn encode(&self, buf: &mut Vec<u8>) {
let len = Encode::<MySql>::size_hint(self) - 1;
buf.push(len as u8);
encode_date(&self.date(), buf);
if len > 4 {
encode_time(&self.time(), len > 8, buf);
}
}
fn size_hint(&self) -> usize {
// to save space the packet can be compressed:
match (
self.hour(),
self.minute(),
self.second(),
self.timestamp_subsec_nanos(),
) {
// if hour, minutes, seconds and micro_seconds are all 0,
// length is 4 and no other field is sent
(0, 0, 0, 0) => 5,
// if micro_seconds is 0, length is 7
// and micro_seconds is not sent
(_, _, _, 0) => 8,
// otherwise length is 11
(_, _, _, _) => 12,
}
}
}
impl Decode<MySql> for NaiveDateTime {
fn decode(buf: &[u8]) -> Result<Self, DecodeError> {
let len = buf[0];
let date = decode_date(&buf[1..]);
let dt = if len > 4 {
date.and_time(decode_time(len - 4, &buf[5..])?)
} else {
date.and_hms(0, 0, 0)
};
Ok(dt)
}
}
fn encode_date(date: &NaiveDate, buf: &mut Vec<u8>) {
// MySQL supports years from 1000 - 9999
let year = u16::try_from(date.year())
.unwrap_or_else(|_| panic!("NaiveDateTime out of range for Mysql: {}", date));
@@ -113,14 +169,44 @@ fn encode_date(date: NaiveDate, buf: &mut Vec<u8>) {
buf.push(date.day() as u8);
}
fn decode_date(raw: &[u8]) -> NaiveDate {
fn decode_date(buf: &[u8]) -> NaiveDate {
NaiveDate::from_ymd(
LittleEndian::read_u16(raw) as i32,
raw[2] as u32,
raw[3] as u32,
LittleEndian::read_u16(buf) as i32,
buf[2] as u32,
buf[3] as u32,
)
}
fn encode_time(time: &NaiveTime, include_micros: bool, buf: &mut Vec<u8>) {
buf.push(time.hour() as u8);
buf.push(time.minute() as u8);
buf.push(time.second() as u8);
if include_micros {
buf.put_u32::<LittleEndian>((time.nanosecond() / 1000) as u32);
}
}
fn decode_time(len: u8, mut buf: &[u8]) -> Result<NaiveTime, DecodeError> {
let hour = buf.get_u8()?;
let minute = buf.get_u8()?;
let seconds = buf.get_u8()?;
let micros = if len > 3 {
// microseconds : int<EOF>
buf.get_uint::<LittleEndian>(buf.len())?
} else {
0
};
Ok(NaiveTime::from_hms_micro(
hour as u32,
minute as u32,
seconds as u32,
micros as u32,
))
}
#[test]
fn test_encode_date_time() {
let mut buf = Vec::new();