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"
required-features = [ "postgres" ]
[[test]]
name = "postgres-types-chrono"
required-features = [ "postgres", "chrono" ]
[[test]]
name = "mysql-types"
required-features = [ "mysql" ]

View file

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

View file

@ -1,6 +1,7 @@
use std::convert::TryInto;
use std::mem;
use byteorder::{NetworkEndian, ReadBytesExt};
use chrono::{DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
use crate::decode::Decode;
@ -10,6 +11,7 @@ use crate::postgres::row::PgValue;
use crate::postgres::types::PgTypeInfo;
use crate::postgres::Postgres;
use crate::types::Type;
use crate::Error;
impl Type<Postgres> for NaiveTime {
fn type_info() -> PgTypeInfo {
@ -67,9 +69,15 @@ where
impl<'de> Decode<'de, Postgres> for NaiveTime {
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),
}
}
}
@ -89,9 +97,15 @@ impl Encode<Postgres> for NaiveTime {
impl<'de> Decode<'de, Postgres> for NaiveDate {
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),
}
}
}
@ -114,20 +128,39 @@ impl Encode<Postgres> for NaiveDate {
impl<'de> Decode<'de, Postgres> for NaiveDateTime {
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()
.naive_utc()
.checked_add_signed(Duration::microseconds(micros))
.ok_or_else(|| {
crate::Error::Decode(
format!(
"Postgres timestamp out of range for NaiveDateTime: {:?}",
micros
)
.into(),
postgres_epoch()
.naive_utc()
.checked_add_signed(Duration::microseconds(micros))
.ok_or_else(|| {
crate::Error::Decode(
format!(
"Postgres timestamp out of range for NaiveDateTime: {:?}",
micros
)
.into(),
)
})
}
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)
}
}
}
}
@ -205,15 +238,15 @@ fn test_encode_datetime() {
#[test]
fn test_decode_datetime() {
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");
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");
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");
}
@ -241,14 +274,14 @@ fn test_encode_date() {
#[test]
fn test_decode_date() {
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");
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");
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");
}

View file

@ -21,7 +21,7 @@ where
macro_rules! test_type {
($name:ident($db:ident, $ty:ty, $($text:literal == $value:expr),+)) => {
$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-tokio", tokio::test)]
async fn [< test_unprepared_type_ $name >] () -> anyhow::Result<()> {
use sqlx::prelude::*;
let mut conn = sqlx_test::new::<$db>().await?;
$(

View file

@ -59,6 +59,13 @@ pub mod decode {
}
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")]
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
// TODO: UUID
// TODO: CHRONO
test_type!(bytea(
Postgres,
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-tokio", tokio::test)]
// async fn postgres_bytes() -> anyhow::Result<()> {
// async fn postgres_chrono_timestamp_tz() -> anyhow::Result<()> {
// 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")
// .bind(&value[..])
// .map(|row: PgRow| Ok((row.get(0)?, row.get(1)?)))
// .fetch_one(&mut conn)
// .await?;
// 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!(rec.0);
// assert_eq!(&value[..], &*rec.1);
// assert!(row.get::<bool, _>(0));
//
// let out: DateTime<Utc> = row.get(1);
// assert_eq!(value, out);
//
// Ok(())
// }