postgres: implement text mode for chrono and clean up type tests

This commit is contained in:
Ryan Leckey 2020-03-01 23:47:25 -08:00
parent 7fbc26de05
commit f337f1c602
7 changed files with 136 additions and 125 deletions

View File

@ -86,10 +86,6 @@ required-features = [ "postgres" ]
name = "postgres-types" name = "postgres-types"
required-features = [ "postgres" ] required-features = [ "postgres" ]
[[test]]
name = "postgres-types-chrono"
required-features = [ "postgres", "chrono" ]
[[test]] [[test]]
name = "mysql-types" name = "mysql-types"
required-features = [ "mysql" ] required-features = [ "mysql" ]

View File

@ -21,7 +21,7 @@ fn test_ssl_request() {
use crate::io::Buf; use crate::io::Buf;
let mut buf = Vec::new(); let mut buf = Vec::new();
SslRequest::encode(&mut buf); SslRequest.encode(&mut buf);
assert_eq!(&buf, b"\x00\x00\x00\x08\x04\xd2\x16/"); assert_eq!(&buf, b"\x00\x00\x00\x08\x04\xd2\x16/");
} }

View File

@ -1,6 +1,7 @@
use std::convert::TryInto; use std::convert::TryInto;
use std::mem; use std::mem;
use byteorder::{NetworkEndian, ReadBytesExt};
use chrono::{DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc}; use chrono::{DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
use crate::decode::Decode; use crate::decode::Decode;
@ -10,6 +11,7 @@ use crate::postgres::row::PgValue;
use crate::postgres::types::PgTypeInfo; use crate::postgres::types::PgTypeInfo;
use crate::postgres::Postgres; use crate::postgres::Postgres;
use crate::types::Type; use crate::types::Type;
use crate::Error;
impl Type<Postgres> for NaiveTime { impl Type<Postgres> for NaiveTime {
fn type_info() -> PgTypeInfo { fn type_info() -> PgTypeInfo {
@ -67,10 +69,16 @@ where
impl<'de> Decode<'de, Postgres> for NaiveTime { impl<'de> Decode<'de, Postgres> for NaiveTime {
fn decode(value: Option<PgValue<'de>>) -> crate::Result<Self> { fn decode(value: Option<PgValue<'de>>) -> crate::Result<Self> {
let micros: i64 = Decode::<Postgres>::decode(value)?; match value.try_into()? {
PgValue::Binary(mut buf) => {
let micros = buf.read_i64::<NetworkEndian>().map_err(Error::decode)?;
Ok(NaiveTime::from_hms(0, 0, 0) + Duration::microseconds(micros)) Ok(NaiveTime::from_hms(0, 0, 0) + Duration::microseconds(micros))
} }
PgValue::Text(s) => NaiveTime::parse_from_str(s, "%H:%M:%S%.f").map_err(Error::decode),
}
}
} }
impl Encode<Postgres> for NaiveTime { impl Encode<Postgres> for NaiveTime {
@ -89,10 +97,16 @@ impl Encode<Postgres> for NaiveTime {
impl<'de> Decode<'de, Postgres> for NaiveDate { impl<'de> Decode<'de, Postgres> for NaiveDate {
fn decode(value: Option<PgValue<'de>>) -> crate::Result<Self> { fn decode(value: Option<PgValue<'de>>) -> crate::Result<Self> {
let days: i32 = Decode::<Postgres>::decode(value)?; match value.try_into()? {
PgValue::Binary(mut buf) => {
let days: i32 = buf.read_i32::<NetworkEndian>().map_err(Error::decode)?;
Ok(NaiveDate::from_ymd(2000, 1, 1) + Duration::days(days as i64)) Ok(NaiveDate::from_ymd(2000, 1, 1) + Duration::days(days as i64))
} }
PgValue::Text(s) => NaiveDate::parse_from_str(s, "%Y-%m-%d").map_err(Error::decode),
}
}
} }
impl Encode<Postgres> for NaiveDate { impl Encode<Postgres> for NaiveDate {
@ -114,7 +128,9 @@ impl Encode<Postgres> for NaiveDate {
impl<'de> Decode<'de, Postgres> for NaiveDateTime { impl<'de> Decode<'de, Postgres> for NaiveDateTime {
fn decode(value: Option<PgValue<'de>>) -> crate::Result<Self> { fn decode(value: Option<PgValue<'de>>) -> crate::Result<Self> {
let micros: i64 = Decode::<Postgres>::decode(value)?; match value.try_into()? {
PgValue::Binary(mut buf) => {
let micros = buf.read_i64::<NetworkEndian>().map_err(Error::decode)?;
postgres_epoch() postgres_epoch()
.naive_utc() .naive_utc()
@ -129,6 +145,23 @@ impl<'de> Decode<'de, Postgres> for NaiveDateTime {
) )
}) })
} }
PgValue::Text(s) => {
NaiveDateTime::parse_from_str(
s,
if s.contains('+') {
// Contains a time-zone specifier
// This is given for timestamptz for some reason
// Postgres already guarantees this to always be UTC
"%Y-%m-%d %H:%M:%S%.f%#z"
} else {
"%Y-%m-%d %H:%M:%S%.f"
},
)
.map_err(Error::decode)
}
}
}
} }
impl Encode<Postgres> for NaiveDateTime { impl Encode<Postgres> for NaiveDateTime {
@ -205,15 +238,15 @@ fn test_encode_datetime() {
#[test] #[test]
fn test_decode_datetime() { fn test_decode_datetime() {
let buf = [0u8; 8]; let buf = [0u8; 8];
let date: NaiveDateTime = Decode::<Postgres>::decode(&buf).unwrap(); let date: NaiveDateTime = Decode::<Postgres>::decode(Some(PgValue::Binary(&buf))).unwrap();
assert_eq!(date.to_string(), "2000-01-01 00:00:00"); assert_eq!(date.to_string(), "2000-01-01 00:00:00");
let buf = 3_600_000_000i64.to_be_bytes(); let buf = 3_600_000_000i64.to_be_bytes();
let date: NaiveDateTime = Decode::<Postgres>::decode(&buf).unwrap(); let date: NaiveDateTime = Decode::<Postgres>::decode(Some(PgValue::Binary(&buf))).unwrap();
assert_eq!(date.to_string(), "2000-01-01 01:00:00"); assert_eq!(date.to_string(), "2000-01-01 01:00:00");
let buf = 629_377_265_000_000i64.to_be_bytes(); let buf = 629_377_265_000_000i64.to_be_bytes();
let date: NaiveDateTime = Decode::<Postgres>::decode(&buf).unwrap(); let date: NaiveDateTime = Decode::<Postgres>::decode(Some(PgValue::Binary(&buf))).unwrap();
assert_eq!(date.to_string(), "2019-12-11 11:01:05"); assert_eq!(date.to_string(), "2019-12-11 11:01:05");
} }
@ -241,14 +274,14 @@ fn test_encode_date() {
#[test] #[test]
fn test_decode_date() { fn test_decode_date() {
let buf = [0; 4]; let buf = [0; 4];
let date: NaiveDate = Decode::<Postgres>::decode(&buf).unwrap(); let date: NaiveDate = Decode::<Postgres>::decode(Some(PgValue::Binary(&buf))).unwrap();
assert_eq!(date.to_string(), "2000-01-01"); assert_eq!(date.to_string(), "2000-01-01");
let buf = 366i32.to_be_bytes(); let buf = 366i32.to_be_bytes();
let date: NaiveDate = Decode::<Postgres>::decode(&buf).unwrap(); let date: NaiveDate = Decode::<Postgres>::decode(Some(PgValue::Binary(&buf))).unwrap();
assert_eq!(date.to_string(), "2001-01-01"); assert_eq!(date.to_string(), "2001-01-01");
let buf = 7284i32.to_be_bytes(); let buf = 7284i32.to_be_bytes();
let date: NaiveDate = Decode::<Postgres>::decode(&buf).unwrap(); let date: NaiveDate = Decode::<Postgres>::decode(Some(PgValue::Binary(&buf))).unwrap();
assert_eq!(date.to_string(), "2019-12-11"); assert_eq!(date.to_string(), "2019-12-11");
} }

View File

@ -21,7 +21,7 @@ where
macro_rules! test_type { macro_rules! test_type {
($name:ident($db:ident, $ty:ty, $($text:literal == $value:expr),+)) => { ($name:ident($db:ident, $ty:ty, $($text:literal == $value:expr),+)) => {
$crate::test_prepared_type!($name($db, $ty, $($text == $value),+)); $crate::test_prepared_type!($name($db, $ty, $($text == $value),+));
// $crate::test_unprepared_type!($name($db, $ty, $($text == $value),+)); $crate::test_unprepared_type!($name($db, $ty, $($text == $value),+));
} }
} }
@ -33,6 +33,8 @@ macro_rules! test_unprepared_type {
#[cfg_attr(feature = "runtime-async-std", async_std::test)] #[cfg_attr(feature = "runtime-async-std", async_std::test)]
#[cfg_attr(feature = "runtime-tokio", tokio::test)] #[cfg_attr(feature = "runtime-tokio", tokio::test)]
async fn [< test_unprepared_type_ $name >] () -> anyhow::Result<()> { async fn [< test_unprepared_type_ $name >] () -> anyhow::Result<()> {
use sqlx::prelude::*;
let mut conn = sqlx_test::new::<$db>().await?; let mut conn = sqlx_test::new::<$db>().await?;
$( $(

View File

@ -59,6 +59,13 @@ pub mod decode {
} }
pub mod prelude { pub mod prelude {
pub use super::Connect as _;
pub use super::Connection as _;
pub use super::Cursor as _;
pub use super::Executor as _;
pub use super::FromRow as _;
pub use super::Row as _;
#[cfg(feature = "postgres")] #[cfg(feature = "postgres")]
pub use super::postgres::PgQueryAs as _; pub use super::postgres::PgQueryAs as _;
} }

View File

@ -1,85 +0,0 @@
use sqlx::types::chrono::{DateTime, NaiveDate, NaiveTime, Utc};
use sqlx::{Connection, PgConnection, Row};
async fn connect() -> anyhow::Result<PgConnection> {
Ok(PgConnection::open(dotenv::var("DATABASE_URL")?).await?)
}
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
async fn postgres_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' = $1, $1")
.bind(&value)
.fetch_one(&mut conn)
.await?;
assert!(row.get::<bool, _>(0));
assert_eq!(value, row.get(1));
Ok(())
}
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
async fn postgres_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' = $1, $1")
.bind(&value)
.fetch_one(&mut conn)
.await?;
assert!(row.get::<bool, _>(0));
assert_eq!(value, row.get(1));
Ok(())
}
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
async fn postgres_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' = $1, TIME '05:10:20.115100'")
.bind(&value)
.fetch_one(&mut conn)
.await?;
assert!(row.get::<bool, _>(0));
assert_eq!(value, row.get(1));
Ok(())
}
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
async fn postgres_chrono_timestamp_tz() -> 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 TIMESTAMPTZ '2019-01-02 05:10:20.115100' = $1, TIMESTAMPTZ '2019-01-02 05:10:20.115100'",
)
.bind(&value)
.fetch_one(&mut conn)
.await?;
assert!(row.get::<bool, _>(0));
let out: DateTime<Utc> = row.get(1);
assert_eq!(value, out);
Ok(())
}

View File

@ -26,25 +26,83 @@ test_type!(string(
"''" == "" "''" == ""
)); ));
// TODO: BYTEA test_type!(bytea(
// TODO: UUID Postgres,
// TODO: CHRONO Vec<u8>,
"E'\\\\xDEADBEEF'::bytea"
== vec![0xDE_u8, 0xAD, 0xBE, 0xEF],
"E'\\\\x'::bytea"
== Vec::<u8>::new(),
"E'\\\\x0000000052'::bytea"
== vec![0_u8, 0, 0, 0, 0x52]
));
#[cfg(feature = "uuid")]
test_type!(uuid(
Postgres,
sqlx::types::Uuid,
"'b731678f-636f-4135-bc6f-19440c13bd19'::uuid"
== sqlx::types::Uuid::parse_str("b731678f-636f-4135-bc6f-19440c13bd19").unwrap(),
"'00000000-0000-0000-0000-000000000000'::uuid"
== sqlx::types::Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap()
));
#[cfg(feature = "chrono")]
mod chrono {
use super::*;
use sqlx::types::chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Utc};
test_type!(chrono_date(
Postgres,
NaiveDate,
"DATE '2001-01-05'" == NaiveDate::from_ymd(2001, 1, 5),
"DATE '2050-11-23'" == NaiveDate::from_ymd(2050, 11, 23)
));
test_type!(chrono_time(
Postgres,
NaiveTime,
"TIME '05:10:20.115100'" == NaiveTime::from_hms_micro(5, 10, 20, 115100)
));
test_type!(chrono_date_time(
Postgres,
NaiveDateTime,
"'2019-01-02 05:10:20'" == NaiveDate::from_ymd(2019, 1, 2).and_hms(5, 10, 20)
));
test_type!(chrono_date_time_tz(
Postgres,
DateTime::<Utc>,
"TIMESTAMPTZ '2019-01-02 05:10:20.115100'"
== DateTime::<Utc>::from_utc(
NaiveDate::from_ymd(2019, 1, 2).and_hms_micro(5, 10, 20, 115100),
Utc,
)
));
}
// #[cfg_attr(feature = "runtime-async-std", async_std::test)] // #[cfg_attr(feature = "runtime-async-std", async_std::test)]
// #[cfg_attr(feature = "runtime-tokio", tokio::test)] // #[cfg_attr(feature = "runtime-tokio", tokio::test)]
// async fn postgres_bytes() -> anyhow::Result<()> { // async fn postgres_chrono_timestamp_tz() -> anyhow::Result<()> {
// let mut conn = connect().await?; // let mut conn = connect().await?;
// //
// let value = b"Hello, World"; // let value = DateTime::<Utc>::from_utc(
// NaiveDate::from_ymd(2019, 1, 2).and_hms_micro(5, 10, 20, 115100),
// Utc,
// );
// //
// let rec: (bool, Vec<u8>) = sqlx::query("SELECT E'\\\\x48656c6c6f2c20576f726c64' = $1, $1") // let row = sqlx::query(
// .bind(&value[..]) // "SELECT TIMESTAMPTZ '2019-01-02 05:10:20.115100' = $1, TIMESTAMPTZ '2019-01-02 05:10:20.115100'",
// .map(|row: PgRow| Ok((row.get(0)?, row.get(1)?))) // )
// .bind(&value)
// .fetch_one(&mut conn) // .fetch_one(&mut conn)
// .await?; // .await?;
// //
// assert!(rec.0); // assert!(row.get::<bool, _>(0));
// assert_eq!(&value[..], &*rec.1); //
// let out: DateTime<Utc> = row.get(1);
// assert_eq!(value, out);
// //
// Ok(()) // Ok(())
// } // }