clean up Time impl, impl for text modes

This commit is contained in:
Ryan Leckey 2020-03-21 02:34:21 -07:00
parent 4a98a51a19
commit db543f8391
11 changed files with 424 additions and 457 deletions

131
Cargo.lock generated
View File

@ -162,6 +162,12 @@ dependencies = [
"libc",
]
[[package]]
name = "base-x"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b20b618342cf9891c292c4f5ac2cde7287cc5c87e87e9c769d617793607dec1"
[[package]]
name = "base64"
version = "0.10.1"
@ -246,6 +252,12 @@ dependencies = [
"slab",
]
[[package]]
name = "bumpalo"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f359dc14ff8911330a51ef78022d376f25ed00248912803b58f00cb1c27f742"
[[package]]
name = "byte-tools"
version = "0.3.1"
@ -436,6 +448,12 @@ dependencies = [
"generic-array",
]
[[package]]
name = "discard"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
[[package]]
name = "dotenv"
version = "0.15.0"
@ -1586,6 +1604,12 @@ dependencies = [
"opaque-debug",
]
[[package]]
name = "sha1"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
[[package]]
name = "sha2"
version = "0.8.1"
@ -1672,6 +1696,7 @@ dependencies = [
"sqlx-core 0.3.0-alpha.1",
"sqlx-macros 0.3.0-alpha.1",
"sqlx-test",
"time 0.2.9",
"tokio 0.2.13",
"trybuild",
]
@ -1821,6 +1846,55 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4edf667ea8f60afc06d6aeec079d20d5800351109addec1faea678a8663da4e1"
[[package]]
name = "stdweb"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5"
dependencies = [
"discard",
"rustc_version",
"stdweb-derive",
"stdweb-internal-macros",
"stdweb-internal-runtime",
"wasm-bindgen",
]
[[package]]
name = "stdweb-derive"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef"
dependencies = [
"proc-macro2",
"quote",
"serde",
"serde_derive",
"syn",
]
[[package]]
name = "stdweb-internal-macros"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11"
dependencies = [
"base-x",
"proc-macro2",
"quote",
"serde",
"serde_derive",
"serde_json",
"sha1",
"syn",
]
[[package]]
name = "stdweb-internal-runtime"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
[[package]]
name = "string"
version = "0.2.1"
@ -1987,9 +2061,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6329a7835505d46f5f3a9a2c237f8d6bf5ca6f0015decb3698ba57fcdbb609ba"
dependencies = [
"cfg-if",
"libc",
"rustversion",
"standback",
"stdweb",
"time-macros",
"winapi 0.3.8",
]
[[package]]
@ -2322,6 +2399,60 @@ version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "wasm-bindgen"
version = "0.2.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3557c397ab5a8e347d434782bcd31fc1483d927a6826804cec05cc792ee2519d"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0da9c9a19850d3af6df1cb9574970b566d617ecfaf36eb0b706b6f3ef9bd2f8"
dependencies = [
"bumpalo",
"lazy_static",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f6fde1d36e75a714b5fe0cffbb78978f222ea6baebb726af13c78869fdb4205"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25bda4168030a6412ea8a047e27238cadf56f0e53516e1e83fec0a8b7c786f6d"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc9f36ad51f25b0219a3d4d13b90eb44cd075dff8b6280cca015775d7acaddd8"
[[package]]
name = "winapi"
version = "0.2.8"

View File

@ -59,6 +59,7 @@ sqlx-macros = { version = "0.3.0-alpha.1", path = "sqlx-macros", default-feature
[dev-dependencies]
anyhow = "1.0.26"
time_ = { version = "0.2.9", package = "time" }
futures = "0.3.4"
env_logger = "0.7.1"
async-std = { version = "1.5.0", features = [ "attributes" ] }

View File

@ -1,17 +1,19 @@
use std::borrow::Cow;
use std::convert::TryFrom;
use std::convert::TryInto;
use byteorder::{ByteOrder, LittleEndian};
use time::{Date, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset};
use crate::decode::{Decode, DecodeError};
use crate::decode::Decode;
use crate::encode::Encode;
use crate::io::{Buf, BufMut};
use crate::mysql::protocol::TypeId;
use crate::mysql::types::MySqlTypeInfo;
use crate::mysql::MySql;
use crate::types::HasSqlType;
use crate::mysql::{MySql, MySqlValue};
use crate::types::Type;
impl HasSqlType<OffsetDateTime> for MySql {
impl Type<MySql> for OffsetDateTime {
fn type_info() -> MySqlTypeInfo {
MySqlTypeInfo::new(TypeId::TIMESTAMP)
}
@ -26,15 +28,15 @@ impl Encode<MySql> for OffsetDateTime {
}
}
impl Decode<MySql> for OffsetDateTime {
fn decode(buf: &[u8]) -> Result<Self, DecodeError> {
let primitive: PrimitiveDateTime = Decode::<MySql>::decode(buf)?;
impl<'de> Decode<'de, MySql> for OffsetDateTime {
fn decode(value: Option<MySqlValue<'de>>) -> crate::Result<MySql, Self> {
let primitive: PrimitiveDateTime = Decode::<MySql>::decode(value)?;
Ok(primitive.assume_utc())
}
}
impl HasSqlType<Time> for MySql {
impl Type<MySql> for Time {
fn type_info() -> MySqlTypeInfo {
MySqlTypeInfo::new(TypeId::TIME)
}
@ -66,24 +68,44 @@ impl Encode<MySql> for Time {
}
}
impl Decode<MySql> for Time {
fn decode(mut buf: &[u8]) -> Result<Self, DecodeError> {
// data length, expecting 8 or 12 (fractional seconds)
let len = buf.get_u8()?;
impl<'de> Decode<'de, MySql> for Time {
fn decode(value: Option<MySqlValue<'de>>) -> crate::Result<MySql, Self> {
match value.try_into()? {
MySqlValue::Binary(mut buf) => {
// data length, expecting 8 or 12 (fractional seconds)
let len = buf.get_u8()?;
// is negative : int<1>
let is_negative = buf.get_u8()?;
assert_eq!(is_negative, 0, "Negative dates/times are not supported");
// is negative : int<1>
let is_negative = buf.get_u8()?;
assert_eq!(is_negative, 0, "Negative dates/times are not supported");
// "date on 4 bytes little-endian format" (?)
// https://mariadb.com/kb/en/resultset-row/#timestamp-binary-encoding
buf.advance(4);
// "date on 4 bytes little-endian format" (?)
// https://mariadb.com/kb/en/resultset-row/#timestamp-binary-encoding
buf.advance(4);
decode_time(len - 5, buf)
decode_time(len - 5, buf)
}
MySqlValue::Text(buf) => {
let s = from_utf8(buf).map_err(crate::Error::decode)?;
// If there are less than 9 digits after the decimal point
// We need to zero-pad
// TODO: Ask [time] to add a parse % for less-than-fixed-9 nanos
let s = if s.len() < 20 {
Cow::Owned(format!("{:0<19}", s))
} else {
Cow::Borrowed(s)
};
Time::parse(&*s, "%H:%M:%S.%N").map_err(crate::Error::decode)
}
}
}
}
impl HasSqlType<Date> for MySql {
impl Type<MySql> for Date {
fn type_info() -> MySqlTypeInfo {
MySqlTypeInfo::new(TypeId::DATE)
}
@ -101,13 +123,19 @@ impl Encode<MySql> for Date {
}
}
impl Decode<MySql> for Date {
fn decode(buf: &[u8]) -> Result<Self, DecodeError> {
decode_date(&buf[1..])
impl<'de> Decode<'de, MySql> for Date {
fn decode(value: Option<MySqlValue<'de>>) -> crate::Result<MySql, Self> {
match value.try_into()? {
MySqlValue::Binary(buf) => decode_date(&buf[1..]),
MySqlValue::Text(buf) => {
let s = from_utf8(buf).map_err(crate::Error::decode)?;
Date::parse(s, "%Y-%m-%d").map_err(crate::Error::decode)
}
}
}
}
impl HasSqlType<PrimitiveDateTime> for MySql {
impl Type<MySql> for PrimitiveDateTime {
fn type_info() -> MySqlTypeInfo {
MySqlTypeInfo::new(TypeId::DATETIME)
}
@ -142,18 +170,42 @@ impl Encode<MySql> for PrimitiveDateTime {
}
}
impl Decode<MySql> for PrimitiveDateTime {
fn decode(buf: &[u8]) -> Result<Self, DecodeError> {
let len = buf[0];
let date = decode_date(&buf[1..])?;
impl<'de> Decode<'de, MySql> for PrimitiveDateTime {
fn decode(value: Option<MySqlValue<'de>>) -> crate::Result<MySql, Self> {
match value.try_into()? {
MySqlValue::Binary(buf) => {
let len = buf[0];
let date = decode_date(&buf[1..])?;
let dt = if len > 4 {
date.with_time(decode_time(len - 4, &buf[5..])?)
} else {
date.midnight()
};
let dt = if len > 4 {
date.with_time(decode_time(len - 4, &buf[5..])?)
} else {
date.midnight()
};
Ok(dt)
Ok(dt)
}
MySqlValue::Text(buf) => {
let s = from_utf8(buf).map_err(crate::Error::decode)?;
// If there are less than 9 digits after the decimal point
// We need to zero-pad
// TODO: Ask [time] to add a parse % for less-than-fixed-9 nanos
let s = if s.len() < 31 {
if s.contains('.') {
Cow::Owned(format!("{:0<30}", s))
} else {
Cow::Owned(format!("{}.000000000", s))
}
} else {
Cow::Borrowed(s)
};
PrimitiveDateTime::parse(&*s, "%Y-%m-%d %H:%M:%S.%N").map_err(crate::Error::decode)
}
}
}
}
@ -167,13 +219,13 @@ fn encode_date(date: &Date, buf: &mut Vec<u8>) {
buf.push(date.day());
}
fn decode_date(buf: &[u8]) -> Result<Date, DecodeError> {
fn decode_date(buf: &[u8]) -> crate::Result<MySql, Date> {
Date::try_from_ymd(
LittleEndian::read_u16(buf) as i32,
buf[2] as u8,
buf[3] as u8,
)
.map_err(|e| DecodeError::Message(Box::new(format!("Error while decoding Date: {}", e))))
.map_err(|e| decode_err!("Error while decoding Date: {}", e))
}
fn encode_time(time: &Time, include_micros: bool, buf: &mut Vec<u8>) {
@ -186,7 +238,7 @@ fn encode_time(time: &Time, include_micros: bool, buf: &mut Vec<u8>) {
}
}
fn decode_time(len: u8, mut buf: &[u8]) -> Result<Time, DecodeError> {
fn decode_time(len: u8, mut buf: &[u8]) -> crate::Result<MySql, Time> {
let hour = buf.get_u8()?;
let minute = buf.get_u8()?;
let seconds = buf.get_u8()?;
@ -199,9 +251,10 @@ fn decode_time(len: u8, mut buf: &[u8]) -> Result<Time, DecodeError> {
};
Time::try_from_hms_micro(hour, minute, seconds, micros as u32)
.map_err(|e| DecodeError::Message(Box::new(format!("Time out of range for MySQL: {}", e))))
.map_err(|e| decode_err!("Time out of range for MySQL: {}", e))
}
use std::str::from_utf8;
#[cfg(test)]
use time::{date, time};

View File

@ -48,7 +48,7 @@ impl TryFrom<u8> for Message {
b'I' => Message::EmptyQueryResponse,
id => {
return Err(protocol_err!("unknown message: {:?}", id).into());
return Err(protocol_err!("unknown message: {:?}", id as char).into());
}
})
}

View File

@ -1,62 +1,89 @@
use std::borrow::Cow;
use std::convert::TryInto;
use std::mem;
use byteorder::BigEndian;
use time::{date, offset, Date, NumericalDuration, OffsetDateTime, PrimitiveDateTime, Time};
use crate::decode::{Decode, DecodeError};
use crate::decode::Decode;
use crate::encode::Encode;
use crate::io::Buf;
use crate::postgres::protocol::TypeId;
use crate::postgres::types::PgTypeInfo;
use crate::postgres::Postgres;
use crate::types::HasSqlType;
use crate::postgres::{PgValue, Postgres};
use crate::types::Type;
const POSTGRES_EPOCH: PrimitiveDateTime = date!(2000 - 1 - 1).midnight();
impl HasSqlType<Time> for Postgres {
impl Type<Postgres> for Time {
fn type_info() -> PgTypeInfo {
PgTypeInfo::new(TypeId::TIME)
PgTypeInfo::new(TypeId::TIME, "TIME")
}
}
impl HasSqlType<Date> for Postgres {
impl Type<Postgres> for Date {
fn type_info() -> PgTypeInfo {
PgTypeInfo::new(TypeId::DATE)
PgTypeInfo::new(TypeId::DATE, "DATE")
}
}
impl HasSqlType<PrimitiveDateTime> for Postgres {
impl Type<Postgres> for PrimitiveDateTime {
fn type_info() -> PgTypeInfo {
PgTypeInfo::new(TypeId::TIMESTAMP)
PgTypeInfo::new(TypeId::TIMESTAMP, "TIMESTAMP")
}
}
impl HasSqlType<OffsetDateTime> for Postgres {
impl Type<Postgres> for OffsetDateTime {
fn type_info() -> PgTypeInfo {
PgTypeInfo::new(TypeId::TIMESTAMPTZ)
PgTypeInfo::new(TypeId::TIMESTAMPTZ, "TIMESTAMPTZ")
}
}
impl HasSqlType<[Time]> for Postgres {
impl Type<Postgres> for [Time] {
fn type_info() -> PgTypeInfo {
PgTypeInfo::new(TypeId::ARRAY_TIME)
PgTypeInfo::new(TypeId::ARRAY_TIME, "TIME[]")
}
}
impl HasSqlType<[Date]> for Postgres {
impl Type<Postgres> for [Date] {
fn type_info() -> PgTypeInfo {
PgTypeInfo::new(TypeId::ARRAY_DATE)
PgTypeInfo::new(TypeId::ARRAY_DATE, "DATE[]")
}
}
impl HasSqlType<[PrimitiveDateTime]> for Postgres {
impl Type<Postgres> for [PrimitiveDateTime] {
fn type_info() -> PgTypeInfo {
PgTypeInfo::new(TypeId::ARRAY_TIMESTAMP)
PgTypeInfo::new(TypeId::ARRAY_TIMESTAMP, "TIMESTAMP[]")
}
}
impl HasSqlType<[OffsetDateTime]> for Postgres {
impl Type<Postgres> for [OffsetDateTime] {
fn type_info() -> PgTypeInfo {
PgTypeInfo::new(TypeId::ARRAY_TIMESTAMPTZ)
PgTypeInfo::new(TypeId::ARRAY_TIMESTAMPTZ, "TIMESTAMPTZ[]")
}
}
impl Type<Postgres> for Vec<Time> {
fn type_info() -> PgTypeInfo {
PgTypeInfo::new(TypeId::ARRAY_TIME, "TIME[]")
}
}
impl Type<Postgres> for Vec<Date> {
fn type_info() -> PgTypeInfo {
PgTypeInfo::new(TypeId::ARRAY_DATE, "DATE[]")
}
}
impl Type<Postgres> for Vec<PrimitiveDateTime> {
fn type_info() -> PgTypeInfo {
PgTypeInfo::new(TypeId::ARRAY_TIMESTAMP, "TIMESTAMP[]")
}
}
impl Type<Postgres> for Vec<OffsetDateTime> {
fn type_info() -> PgTypeInfo {
PgTypeInfo::new(TypeId::ARRAY_TIMESTAMPTZ, "TIMESTAMPTZ[]")
}
}
@ -67,7 +94,7 @@ fn microseconds_since_midnight(time: Time) -> i64 {
+ time.microsecond() as i64
}
fn from_microseconds_since_midnight(mut microsecond: u64) -> Result<Time, DecodeError> {
fn from_microseconds_since_midnight(mut microsecond: u64) -> crate::Result<Postgres, Time> {
#![allow(clippy::cast_possible_truncation)]
microsecond %= 86_400 * 1_000_000;
@ -78,14 +105,32 @@ fn from_microseconds_since_midnight(mut microsecond: u64) -> Result<Time, Decode
(microsecond / 1_000_000 % 60) as u8,
(microsecond % 1_000_000) as u32,
)
.map_err(|e| DecodeError::Message(Box::new(format!("Time out of range for Postgres: {}", e))))
.map_err(|e| decode_err!("Time out of range for Postgres: {}", e))
}
impl Decode<Postgres> for Time {
fn decode(raw: &[u8]) -> Result<Self, DecodeError> {
let micros: i64 = Decode::<Postgres>::decode(raw)?;
impl<'de> Decode<'de, Postgres> for Time {
fn decode(value: Option<PgValue<'de>>) -> crate::Result<Postgres, Self> {
match value.try_into()? {
PgValue::Binary(mut buf) => {
let micros: i64 = buf.get_i64::<BigEndian>()?;
from_microseconds_since_midnight(micros as u64)
from_microseconds_since_midnight(micros as u64)
}
PgValue::Text(s) => {
// If there are less than 9 digits after the decimal point
// We need to zero-pad
// TODO: Ask [time] to add a parse % for less-than-fixed-9 nanos
let s = if s.len() < 20 {
Cow::Owned(format!("{:0<19}", s))
} else {
Cow::Borrowed(s)
};
Time::parse(&*s, "%H:%M:%S.%N").map_err(crate::Error::decode)
}
}
}
}
@ -101,11 +146,17 @@ impl Encode<Postgres> for Time {
}
}
impl Decode<Postgres> for Date {
fn decode(raw: &[u8]) -> Result<Self, DecodeError> {
let n: i32 = Decode::<Postgres>::decode(raw)?;
impl<'de> Decode<'de, Postgres> for Date {
fn decode(value: Option<PgValue<'de>>) -> crate::Result<Postgres, Self> {
match value.try_into()? {
PgValue::Binary(mut buf) => {
let n: i32 = buf.get_i32::<BigEndian>()?;
Ok(date!(2000 - 1 - 1) + n.days())
Ok(date!(2000 - 1 - 1) + n.days())
}
PgValue::Text(s) => Date::parse(s, "%Y-%m-%d").map_err(crate::Error::decode),
}
}
}
@ -125,11 +176,44 @@ impl Encode<Postgres> for Date {
}
}
impl Decode<Postgres> for PrimitiveDateTime {
fn decode(raw: &[u8]) -> Result<Self, DecodeError> {
let n: i64 = Decode::<Postgres>::decode(raw)?;
impl<'de> Decode<'de, Postgres> for PrimitiveDateTime {
fn decode(value: Option<PgValue<'de>>) -> crate::Result<Postgres, Self> {
match value.try_into()? {
PgValue::Binary(mut buf) => {
let n: i64 = buf.get_i64::<BigEndian>()?;
Ok(POSTGRES_EPOCH + n.microseconds())
Ok(POSTGRES_EPOCH + n.microseconds())
}
// TODO: Try and fix duplication between here and MySQL
PgValue::Text(s) => {
// If there are less than 9 digits after the decimal point
// We need to zero-pad
// TODO: Ask [time] to add a parse % for less-than-fixed-9 nanos
let s = if let Some(plus) = s.rfind('+') {
let mut big = String::from(&s[..plus]);
while big.len() < 31 {
big.push('0');
}
big.push_str(&s[plus..]);
Cow::Owned(big)
} else if s.len() < 31 {
if s.contains('.') {
Cow::Owned(format!("{:0<30}", s))
} else {
Cow::Owned(format!("{}.000000000", s))
}
} else {
Cow::Borrowed(s)
};
PrimitiveDateTime::parse(&*s, "%Y-%m-%d %H:%M:%S.%N").map_err(crate::Error::decode)
}
}
}
}
@ -148,10 +232,11 @@ impl Encode<Postgres> for PrimitiveDateTime {
}
}
impl Decode<Postgres> for OffsetDateTime {
fn decode(raw: &[u8]) -> Result<Self, DecodeError> {
let date_time: PrimitiveDateTime = Decode::<Postgres>::decode(raw)?;
Ok(date_time.assume_utc())
impl<'de> Decode<'de, Postgres> for OffsetDateTime {
fn decode(value: Option<PgValue<'de>>) -> crate::Result<Postgres, Self> {
let primitive: PrimitiveDateTime = Decode::<Postgres>::decode(value)?;
Ok(primitive.assume_utc())
}
}

View File

@ -1,88 +0,0 @@
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?)
}
#[cfg(all(feature = "chrono", not(feature = "time")))]
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
#[cfg_attr(feature = "runtime-tokio", tokio::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' = ?) as _1, CAST(? AS DATE) as _2",
value,
value
)
.fetch_one(&mut conn)
.await?;
assert!(row._1 != 0);
assert_eq!(value, row._2);
Ok(())
}
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
#[cfg_attr(feature = "runtime-tokio", tokio::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(())
}
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
#[cfg_attr(feature = "runtime-tokio", tokio::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(())
}
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
#[cfg_attr(feature = "runtime-tokio", tokio::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(())
}

View File

@ -1,92 +0,0 @@
use sqlx::types::time::{Date, OffsetDateTime, Time, UtcOffset};
use sqlx::{mysql::MySqlConnection, Connection, Row};
async fn connect() -> anyhow::Result<MySqlConnection> {
Ok(MySqlConnection::open(dotenv::var("DATABASE_URL")?).await?)
}
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
async fn mysql_timers_date() -> anyhow::Result<()> {
let mut conn = connect().await?;
// TODO: maybe use macro here? but is it OK to include `time` as test dependency?
let value = Date::try_from_ymd(2019, 1, 2).unwrap();
let row = sqlx::query!(
"SELECT (DATE '2019-01-02' = ?) as _1, CAST(? AS DATE) as _2",
value,
value
)
.fetch_one(&mut conn)
.await?;
assert!(row._1 != 0);
assert_eq!(value, row._2);
Ok(())
}
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
async fn mysql_timers_date_time() -> anyhow::Result<()> {
let mut conn = connect().await?;
let value = Date::try_from_ymd(2019, 1, 2)
.unwrap()
.try_with_hms(5, 10, 20)
.unwrap();
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(())
}
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
async fn mysql_timers_time() -> anyhow::Result<()> {
let mut conn = connect().await?;
let value = Time::try_from_hms_micro(5, 10, 20, 115100).unwrap();
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(())
}
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
async fn mysql_timers_timestamp() -> anyhow::Result<()> {
let mut conn = connect().await?;
let value = Date::try_from_ymd(2019, 1, 2)
.unwrap()
.try_with_hms_micro(5, 10, 20, 115100)
.unwrap()
.assume_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(())
}

View File

@ -1,3 +1,5 @@
extern crate time_ as time;
use sqlx::MySql;
use sqlx_test::test_type;
@ -74,7 +76,7 @@ mod chrono {
"'2019-01-02 05:10:20'" == NaiveDate::from_ymd(2019, 1, 2).and_hms(5, 10, 20)
));
test_type!(chrono_date_time_tz(
test_type!(chrono_timestamp(
MySql,
DateTime::<Utc>,
"TIMESTAMP '2019-01-02 05:10:20.115100'"
@ -84,3 +86,39 @@ mod chrono {
)
));
}
#[cfg(feature = "time")]
mod time_tests {
use super::*;
use sqlx::types::time::{Date, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset};
use time::{date, time};
test_type!(time_date(
MySql,
Date,
"DATE '2001-01-05'" == date!(2001 - 1 - 5),
"DATE '2050-11-23'" == date!(2050 - 11 - 23)
));
test_type!(time_time(
MySql,
Time,
"TIME '05:10:20.115100'" == time!(5:10:20.115100)
));
test_type!(time_date_time(
MySql,
PrimitiveDateTime,
"'2019-01-02 05:10:20'" == date!(2019 - 1 - 2).with_time(time!(5:10:20)),
"'2019-01-02 05:10:20.115100'" == date!(2019 - 1 - 2).with_time(time!(5:10:20.115100))
));
test_type!(time_timestamp(
MySql,
OffsetDateTime,
"TIMESTAMP '2019-01-02 05:10:20.115100'"
== date!(2019 - 1 - 2)
.with_time(time!(5:10:20.115100))
.assume_utc()
));
}

View File

@ -1,91 +0,0 @@
use sqlx::types::chrono::{DateTime, NaiveDate, Utc};
use sqlx::{Connection, PgConnection, Row};
#[cfg(all(feature = "chrono", not(feature = "time")))]
use sqlx::types::chrono::NaiveTime;
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 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' = $1, $1")
.bind(&value)
.fetch_one(&mut conn)
.await?;
assert!(row.get::<bool, _>(0));
assert_eq!(value, row.get(1));
Ok(())
}
#[cfg(all(feature = "chrono", not(feature = "time")))]
#[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 AS equality, TIME '05:10:20.115100' AS time",
value,
)
.fetch_one(&mut conn)
.await?;
assert!(row.equality);
assert_eq!(value, row.time);
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

@ -1,109 +0,0 @@
use sqlx::types::time::{Date, OffsetDateTime, Time, UtcOffset};
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_timers_date() -> anyhow::Result<()> {
let mut conn = connect().await?;
let value = Date::try_from_ymd(2019, 1, 2).unwrap();
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 mysql_timers_date_time() -> anyhow::Result<()> {
let mut conn = connect().await?;
let value = Date::try_from_ymd(2019, 1, 2)
.unwrap()
.try_with_hms(5, 10, 20)
.unwrap();
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_timers_time() -> anyhow::Result<()> {
let mut conn = connect().await?;
let value = Time::try_from_hms_micro(5, 10, 20, 115100).unwrap();
let row = sqlx::query!(
"SELECT TIME '05:10:20.115100' = $1 AS equality, TIME '05:10:20.115100' AS time",
value
)
.fetch_one(&mut conn)
.await?;
assert!(row.equality);
assert_eq!(value, row.time);
Ok(())
}
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
async fn postgres_timers_timestamp_tz() -> anyhow::Result<()> {
let mut conn = connect().await?;
let value = Date::try_from_ymd(2019, 1, 2)
.unwrap()
.try_with_hms_micro(5, 10, 20, 115100)
.unwrap()
.assume_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: OffsetDateTime = row.get(1);
assert_eq!(value, out);
let value = Date::try_from_ymd(2019, 1, 2)
.unwrap()
.try_with_hms_micro(5, 10, 20, 115100)
.unwrap()
.assume_offset(UtcOffset::east_hours(3));
let row = sqlx::query(
"SELECT TIMESTAMPTZ '2019-01-02 02:10:20.115100' = $1, TIMESTAMPTZ '2019-01-02 02:10:20.115100'",
)
.bind(&value)
.fetch_one(&mut conn)
.await?;
assert!(row.get::<bool, _>(0));
let out: OffsetDateTime = row.get(1);
assert_eq!(value, out);
Ok(())
}

View File

@ -1,3 +1,5 @@
extern crate time_ as time;
use std::sync::atomic::{AtomicU32, Ordering};
use sqlx::decode::Decode;
@ -200,6 +202,43 @@ mod chrono {
));
}
#[cfg(feature = "time")]
mod time_tests {
use super::*;
use sqlx::types::time::{Date, OffsetDateTime, PrimitiveDateTime, Time};
use time::{date, time};
test_type!(time_date(
Postgres,
Date,
"DATE '2001-01-05'" == date!(2001 - 1 - 5),
"DATE '2050-11-23'" == date!(2050 - 11 - 23)
));
test_type!(time_time(
Postgres,
Time,
"TIME '05:10:20.115100'" == time!(5:10:20.115100)
));
test_type!(time_date_time(
Postgres,
PrimitiveDateTime,
"TIMESTAMP '2019-01-02 05:10:20'" == date!(2019 - 1 - 2).with_time(time!(5:10:20)),
"TIMESTAMP '2019-01-02 05:10:20.115100'"
== date!(2019 - 1 - 2).with_time(time!(5:10:20.115100))
));
test_type!(time_timestamp(
Postgres,
OffsetDateTime,
"TIMESTAMPTZ '2019-01-02 05:10:20.115100'"
== date!(2019 - 1 - 2)
.with_time(time!(5:10:20.115100))
.assume_utc()
));
}
// This is trying to break my complete lack of understanding of null bitmaps for array/record
// decoding. The docs in pg are either wrong or I'm reading the wrong docs.
test_type!(lots_of_nulls_vec(Postgres, Vec<Option<bool>>,