mirror of
https://github.com/launchbadge/sqlx.git
synced 2025-10-03 07:45:30 +00:00
[MySQL] Add an integration tests for chrono + MySQL and fix issues
This commit is contained in:
parent
e161787952
commit
2a42ff9f0d
@ -64,3 +64,7 @@ required-features = [ "postgres" ]
|
|||||||
[[test]]
|
[[test]]
|
||||||
name = "mysql-types"
|
name = "mysql-types"
|
||||||
required-features = [ "mysql" ]
|
required-features = [ "mysql" ]
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "mysql-types-chrono"
|
||||||
|
required-features = [ "mysql", "chrono" ]
|
||||||
|
@ -1,78 +1,82 @@
|
|||||||
use byteorder::{ByteOrder, LittleEndian};
|
use std::convert::TryFrom;
|
||||||
use chrono::{Datelike, NaiveDate, NaiveDateTime, Timelike};
|
|
||||||
|
use byteorder::{BigEndian, ByteOrder, LittleEndian};
|
||||||
|
use chrono::{DateTime, Datelike, NaiveDate, NaiveDateTime, NaiveTime, Timelike, Utc};
|
||||||
|
|
||||||
use crate::decode::{Decode, DecodeError};
|
use crate::decode::{Decode, DecodeError};
|
||||||
use crate::encode::Encode;
|
use crate::encode::Encode;
|
||||||
|
use crate::io::{Buf, BufMut};
|
||||||
use crate::mysql::protocol::Type;
|
use crate::mysql::protocol::Type;
|
||||||
use crate::mysql::types::MySqlTypeMetadata;
|
use crate::mysql::types::MySqlTypeMetadata;
|
||||||
use crate::mysql::MySql;
|
use crate::mysql::MySql;
|
||||||
use crate::types::HasSqlType;
|
use crate::types::HasSqlType;
|
||||||
use std::convert::TryFrom;
|
|
||||||
|
|
||||||
impl HasSqlType<NaiveDateTime> for MySql {
|
impl HasSqlType<DateTime<Utc>> for MySql {
|
||||||
fn metadata() -> Self::TypeMetadata {
|
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>) {
|
fn encode(&self, buf: &mut Vec<u8>) {
|
||||||
// subtract the length byte
|
Encode::<MySql>::encode(&self.naive_utc(), buf);
|
||||||
let length = Encode::<MySql>::size_hint(self) - 1;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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 {
|
impl HasSqlType<NaiveTime> for MySql {
|
||||||
buf.push(self.hour() as u8);
|
fn metadata() -> Self::TypeMetadata {
|
||||||
buf.push(self.minute() as u8);
|
MySqlTypeMetadata::new(Type::TIME)
|
||||||
buf.push(self.second() as u8);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if length == 11 {
|
impl Encode<MySql> for NaiveTime {
|
||||||
buf.extend_from_slice(&self.timestamp_subsec_micros().to_le_bytes());
|
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 {
|
fn size_hint(&self) -> usize {
|
||||||
match (
|
if self.nanosecond() == 0 {
|
||||||
self.hour(),
|
// if micro_seconds is 0, length is 8 and micro_seconds is not sent
|
||||||
self.minute(),
|
9
|
||||||
self.second(),
|
} else {
|
||||||
self.timestamp_subsec_micros(),
|
// otherwise length is 12
|
||||||
) {
|
13
|
||||||
// include the length byte
|
|
||||||
(0, 0, 0, 0) => 5,
|
|
||||||
(_, _, _, 0) => 8,
|
|
||||||
(_, _, _, _) => 12,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Decode<MySql> for NaiveDateTime {
|
impl Decode<MySql> for NaiveTime {
|
||||||
fn decode(raw: &[u8]) -> Result<Self, DecodeError> {
|
fn decode(mut buf: &[u8]) -> Result<Self, DecodeError> {
|
||||||
let len = raw[0];
|
// data length, expecting 8 or 12 (fractional seconds)
|
||||||
|
let len = buf.get_u8()?;
|
||||||
|
|
||||||
// TODO: Make an error
|
// is negative : int<1>
|
||||||
assert_ne!(len, 0, "MySQL zero-dates are not supported");
|
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 {
|
decode_time(len - 5, buf)
|
||||||
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)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,7 +90,7 @@ impl Encode<MySql> for NaiveDate {
|
|||||||
fn encode(&self, buf: &mut Vec<u8>) {
|
fn encode(&self, buf: &mut Vec<u8>) {
|
||||||
buf.push(4);
|
buf.push(4);
|
||||||
|
|
||||||
encode_date(*self, buf);
|
encode_date(self, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size_hint(&self) -> usize {
|
fn size_hint(&self) -> usize {
|
||||||
@ -95,15 +99,67 @@ impl Encode<MySql> for NaiveDate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Decode<MySql> for NaiveDate {
|
impl Decode<MySql> for NaiveDate {
|
||||||
fn decode(raw: &[u8]) -> Result<Self, DecodeError> {
|
fn decode(buf: &[u8]) -> Result<Self, DecodeError> {
|
||||||
// TODO: Return error
|
Ok(decode_date(&buf[1..]))
|
||||||
assert_eq!(raw[0], 4, "expected only 4 bytes");
|
|
||||||
|
|
||||||
Ok(decode_date(&raw[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
|
// MySQL supports years from 1000 - 9999
|
||||||
let year = u16::try_from(date.year())
|
let year = u16::try_from(date.year())
|
||||||
.unwrap_or_else(|_| panic!("NaiveDateTime out of range for Mysql: {}", date));
|
.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);
|
buf.push(date.day() as u8);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode_date(raw: &[u8]) -> NaiveDate {
|
fn decode_date(buf: &[u8]) -> NaiveDate {
|
||||||
NaiveDate::from_ymd(
|
NaiveDate::from_ymd(
|
||||||
LittleEndian::read_u16(raw) as i32,
|
LittleEndian::read_u16(buf) as i32,
|
||||||
raw[2] as u32,
|
buf[2] as u32,
|
||||||
raw[3] 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]
|
#[test]
|
||||||
fn test_encode_date_time() {
|
fn test_encode_date_time() {
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
|
81
tests/mysql-types-chrono.rs
Normal file
81
tests/mysql-types-chrono.rs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
use sqlx::types::chrono::{DateTime, NaiveDate, NaiveTime, Utc};
|
||||||
|
use sqlx::{mysql::MySqlConnection, Connection, Row};
|
||||||
|
|
||||||
|
async fn connect() -> anyhow::Result<MySqlConnection> {
|
||||||
|
Ok(MySqlConnection::open(dotenv::var("DATABASE_URL")?).await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn mysql_chrono_date() -> anyhow::Result<()> {
|
||||||
|
let mut conn = connect().await?;
|
||||||
|
|
||||||
|
let value = NaiveDate::from_ymd(2019, 1, 2);
|
||||||
|
|
||||||
|
let row = sqlx::query("SELECT DATE '2019-01-02' = ?, ?")
|
||||||
|
.bind(&value)
|
||||||
|
.bind(&value)
|
||||||
|
.fetch_one(&mut conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
assert!(row.get::<bool, _>(0));
|
||||||
|
assert_eq!(value, row.get(1));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn mysql_chrono_date_time() -> anyhow::Result<()> {
|
||||||
|
let mut conn = connect().await?;
|
||||||
|
|
||||||
|
let value = NaiveDate::from_ymd(2019, 1, 2).and_hms(5, 10, 20);
|
||||||
|
|
||||||
|
let row = sqlx::query("SELECT '2019-01-02 05:10:20' = ?, ?")
|
||||||
|
.bind(&value)
|
||||||
|
.bind(&value)
|
||||||
|
.fetch_one(&mut conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
assert!(row.get::<bool, _>(0));
|
||||||
|
assert_eq!(value, row.get(1));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn mysql_chrono_time() -> anyhow::Result<()> {
|
||||||
|
let mut conn = connect().await?;
|
||||||
|
|
||||||
|
let value = NaiveTime::from_hms_micro(5, 10, 20, 115100);
|
||||||
|
|
||||||
|
let row = sqlx::query("SELECT TIME '05:10:20.115100' = ?, TIME '05:10:20.115100'")
|
||||||
|
.bind(&value)
|
||||||
|
.fetch_one(&mut conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
assert!(row.get::<bool, _>(0));
|
||||||
|
assert_eq!(value, row.get(1));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn mysql_chrono_timestamp() -> anyhow::Result<()> {
|
||||||
|
let mut conn = connect().await?;
|
||||||
|
|
||||||
|
let value = DateTime::<Utc>::from_utc(
|
||||||
|
NaiveDate::from_ymd(2019, 1, 2).and_hms_micro(5, 10, 20, 115100),
|
||||||
|
Utc,
|
||||||
|
);
|
||||||
|
|
||||||
|
let row = sqlx::query(
|
||||||
|
"SELECT TIMESTAMP '2019-01-02 05:10:20.115100' = ?, TIMESTAMP '2019-01-02 05:10:20.115100'",
|
||||||
|
)
|
||||||
|
.bind(&value)
|
||||||
|
.fetch_one(&mut conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
assert!(row.get::<bool, _>(0));
|
||||||
|
assert_eq!(value, row.get(1));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -1,13 +1,14 @@
|
|||||||
use sqlx::{mysql::MySqlConnection, Connection as _, Row};
|
use sqlx::{mysql::MySqlConnection, Connection, Row};
|
||||||
|
|
||||||
|
async fn connect() -> anyhow::Result<MySqlConnection> {
|
||||||
|
Ok(MySqlConnection::open(dotenv::var("DATABASE_URL")?).await?)
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! test {
|
macro_rules! test {
|
||||||
($name:ident: $ty:ty: $($text:literal == $value:expr),+) => {
|
($name:ident: $ty:ty: $($text:literal == $value:expr),+) => {
|
||||||
#[async_std::test]
|
#[async_std::test]
|
||||||
async fn $name () -> sqlx::Result<()> {
|
async fn $name () -> anyhow::Result<()> {
|
||||||
let mut conn =
|
let mut conn = connect().await?;
|
||||||
MySqlConnection::open(
|
|
||||||
&dotenv::var("DATABASE_URL").expect("DATABASE_URL must be set")
|
|
||||||
).await?;
|
|
||||||
|
|
||||||
$(
|
$(
|
||||||
let row = sqlx::query(&format!("SELECT {} = ?, ? as _1", $text))
|
let row = sqlx::query(&format!("SELECT {} = ?, ? as _1", $text))
|
||||||
@ -29,12 +30,17 @@ macro_rules! test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test!(mysql_bool: bool: "false" == false, "true" == true);
|
test!(mysql_bool: bool: "false" == false, "true" == true);
|
||||||
|
|
||||||
test!(mysql_tiny_unsigned: u8: "253" == 253_u8);
|
test!(mysql_tiny_unsigned: u8: "253" == 253_u8);
|
||||||
test!(mysql_tiny: i8: "5" == 5_i8);
|
test!(mysql_tiny: i8: "5" == 5_i8);
|
||||||
|
|
||||||
test!(mysql_medium_unsigned: u16: "21415" == 21415_u16);
|
test!(mysql_medium_unsigned: u16: "21415" == 21415_u16);
|
||||||
test!(mysql_short: i16: "21415" == 21415_i16);
|
test!(mysql_short: i16: "21415" == 21415_i16);
|
||||||
|
|
||||||
test!(mysql_long_unsigned: u32: "2141512" == 2141512_u32);
|
test!(mysql_long_unsigned: u32: "2141512" == 2141512_u32);
|
||||||
test!(mysql_long: i32: "2141512" == 2141512_i32);
|
test!(mysql_long: i32: "2141512" == 2141512_i32);
|
||||||
|
|
||||||
test!(mysql_longlong_unsigned: u64: "2141512" == 2141512_u64);
|
test!(mysql_longlong_unsigned: u64: "2141512" == 2141512_u64);
|
||||||
test!(mysql_longlong: i64: "2141512" == 2141512_i64);
|
test!(mysql_longlong: i64: "2141512" == 2141512_i64);
|
||||||
|
|
||||||
test!(mysql_string: String: "'helloworld'" == "helloworld");
|
test!(mysql_string: String: "'helloworld'" == "helloworld");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user