From f337f1c60219b5afae3d8c08e16b6fb388d7f055 Mon Sep 17 00:00:00 2001 From: Ryan Leckey Date: Sun, 1 Mar 2020 23:47:25 -0800 Subject: [PATCH] postgres: implement text mode for chrono and clean up type tests --- Cargo.toml | 4 - .../src/postgres/protocol/ssl_request.rs | 2 +- sqlx-core/src/postgres/types/chrono.rs | 77 ++++++++++++----- sqlx-test/src/lib.rs | 4 +- src/lib.rs | 7 ++ tests/postgres-types-chrono.rs | 85 ------------------- tests/postgres-types.rs | 82 +++++++++++++++--- 7 files changed, 136 insertions(+), 125 deletions(-) delete mode 100644 tests/postgres-types-chrono.rs diff --git a/Cargo.toml b/Cargo.toml index 617e07dd..10899394 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" ] diff --git a/sqlx-core/src/postgres/protocol/ssl_request.rs b/sqlx-core/src/postgres/protocol/ssl_request.rs index d3497a4d..7a767996 100644 --- a/sqlx-core/src/postgres/protocol/ssl_request.rs +++ b/sqlx-core/src/postgres/protocol/ssl_request.rs @@ -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/"); } diff --git a/sqlx-core/src/postgres/types/chrono.rs b/sqlx-core/src/postgres/types/chrono.rs index 9764691a..dd3663c0 100644 --- a/sqlx-core/src/postgres/types/chrono.rs +++ b/sqlx-core/src/postgres/types/chrono.rs @@ -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 for NaiveTime { fn type_info() -> PgTypeInfo { @@ -67,9 +69,15 @@ where impl<'de> Decode<'de, Postgres> for NaiveTime { fn decode(value: Option>) -> crate::Result { - let micros: i64 = Decode::::decode(value)?; + match value.try_into()? { + PgValue::Binary(mut buf) => { + let micros = buf.read_i64::().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 for NaiveTime { impl<'de> Decode<'de, Postgres> for NaiveDate { fn decode(value: Option>) -> crate::Result { - let days: i32 = Decode::::decode(value)?; + match value.try_into()? { + PgValue::Binary(mut buf) => { + let days: i32 = buf.read_i32::().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 for NaiveDate { impl<'de> Decode<'de, Postgres> for NaiveDateTime { fn decode(value: Option>) -> crate::Result { - let micros: i64 = Decode::::decode(value)?; + match value.try_into()? { + PgValue::Binary(mut buf) => { + let micros = buf.read_i64::().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::::decode(&buf).unwrap(); + let date: NaiveDateTime = Decode::::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::::decode(&buf).unwrap(); + let date: NaiveDateTime = Decode::::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::::decode(&buf).unwrap(); + let date: NaiveDateTime = Decode::::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::::decode(&buf).unwrap(); + let date: NaiveDate = Decode::::decode(Some(PgValue::Binary(&buf))).unwrap(); assert_eq!(date.to_string(), "2000-01-01"); let buf = 366i32.to_be_bytes(); - let date: NaiveDate = Decode::::decode(&buf).unwrap(); + let date: NaiveDate = Decode::::decode(Some(PgValue::Binary(&buf))).unwrap(); assert_eq!(date.to_string(), "2001-01-01"); let buf = 7284i32.to_be_bytes(); - let date: NaiveDate = Decode::::decode(&buf).unwrap(); + let date: NaiveDate = Decode::::decode(Some(PgValue::Binary(&buf))).unwrap(); assert_eq!(date.to_string(), "2019-12-11"); } diff --git a/sqlx-test/src/lib.rs b/sqlx-test/src/lib.rs index ea23ccf7..9bc3763d 100644 --- a/sqlx-test/src/lib.rs +++ b/sqlx-test/src/lib.rs @@ -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?; $( diff --git a/src/lib.rs b/src/lib.rs index 0fdc87c5..51b57ba3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 _; } diff --git a/tests/postgres-types-chrono.rs b/tests/postgres-types-chrono.rs deleted file mode 100644 index 5aa1c2ec..00000000 --- a/tests/postgres-types-chrono.rs +++ /dev/null @@ -1,85 +0,0 @@ -use sqlx::types::chrono::{DateTime, NaiveDate, NaiveTime, Utc}; -use sqlx::{Connection, PgConnection, Row}; - -async fn connect() -> anyhow::Result { - 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::(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::(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::(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::::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::(0)); - - let out: DateTime = row.get(1); - assert_eq!(value, out); - - Ok(()) -} diff --git a/tests/postgres-types.rs b/tests/postgres-types.rs index 47c12349..2daf85f0 100644 --- a/tests/postgres-types.rs +++ b/tests/postgres-types.rs @@ -26,25 +26,83 @@ test_type!(string( "''" == "" )); -// TODO: BYTEA -// TODO: UUID -// TODO: CHRONO +test_type!(bytea( + Postgres, + Vec, + "E'\\\\xDEADBEEF'::bytea" + == vec![0xDE_u8, 0xAD, 0xBE, 0xEF], + "E'\\\\x'::bytea" + == Vec::::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::, + "TIMESTAMPTZ '2019-01-02 05:10:20.115100'" + == DateTime::::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::::from_utc( +// NaiveDate::from_ymd(2019, 1, 2).and_hms_micro(5, 10, 20, 115100), +// Utc, +// ); // -// let rec: (bool, Vec) = 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::(0)); +// +// let out: DateTime = row.get(1); +// assert_eq!(value, out); // // Ok(()) // }