mirror of
https://github.com/launchbadge/sqlx.git
synced 2026-03-27 04:20:47 +00:00
[MySQL] Add an integration tests for chrono + MySQL and fix issues
This commit is contained in:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user