From 62a6f63df616edd7627e0799b67d86a8495ef0b5 Mon Sep 17 00:00:00 2001 From: Dean Shaff Date: Tue, 10 Dec 2024 14:59:28 +0100 Subject: [PATCH 1/9] start supporting chrono --- Cargo.toml | 21 +++++++++++++++++---- src/serde.rs | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 58867a5..52c1919 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,17 +69,29 @@ watch = ["dep:sha-1", "dep:serde_json", "serde/derive"] uuid = ["dep:uuid"] time = ["dep:time"] lz4 = ["dep:lz4_flex", "dep:cityhash-rs"] - +chrono = ["dep:chrono"] ## TLS native-tls = ["dep:hyper-tls"] # ext: native-tls-alpn # ext: native-tls-vendored rustls-tls = ["rustls-tls-aws-lc", "rustls-tls-webpki-roots"] -rustls-tls-aws-lc = ["dep:rustls", "dep:hyper-rustls", "hyper-rustls?/aws-lc-rs"] +rustls-tls-aws-lc = [ + "dep:rustls", + "dep:hyper-rustls", + "hyper-rustls?/aws-lc-rs", +] rustls-tls-ring = ["dep:rustls", "dep:hyper-rustls", "hyper-rustls?/ring"] -rustls-tls-webpki-roots = ["dep:rustls", "dep:hyper-rustls", "hyper-rustls?/webpki-tokio"] -rustls-tls-native-roots = ["dep:rustls", "dep:hyper-rustls", "hyper-rustls?/native-tokio"] +rustls-tls-webpki-roots = [ + "dep:rustls", + "dep:hyper-rustls", + "hyper-rustls?/webpki-tokio", +] +rustls-tls-native-roots = [ + "dep:rustls", + "dep:hyper-rustls", + "hyper-rustls?/native-tokio", +] [dependencies] clickhouse-derive = { version = "0.2.0", path = "derive" } @@ -110,6 +122,7 @@ lz4_flex = { version = "0.11.3", default-features = false, features = [ cityhash-rs = { version = "=1.0.1", optional = true } # exact version for safety uuid = { version = "1", optional = true } time = { version = "0.3", optional = true } +chrono = { version = "0.4", optional = true, features = ["serde"] } bstr = { version = "1.11.0", default-features = false } quanta = { version = "0.12", optional = true } replace_with = { version = "0.1.7" } diff --git a/src/serde.rs b/src/serde.rs index 81e1307..9a75c1d 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -106,6 +106,45 @@ pub mod uuid { } } +#[cfg(feature = "chrono")] +pub mod chrono { + use super::*; + use ::chrono::{DateTime, Utc}; + use serde::{de::Error as _, ser::Error as _}; + + pub mod datetime { + use super::*; + + type DateTimeUtc = DateTime; + + option!( + DateTimeUtc, + "Ser/de `Option>` to/from `Nullable(DateTime)`." + ); + + pub fn serialize(dt: &DateTime, serializer: S) -> Result + where + S: Serializer, + { + let ts = dt.timestamp(); + + u32::try_from(ts) + .map_err(|_| S::Error::custom(format!("{dt} cannot be represented as DateTime")))? + .serialize(serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let ts: u32 = Deserialize::deserialize(deserializer)?; + DateTime::::from_timestamp(i64::from(ts), 0).ok_or_else(|| { + D::Error::custom(format!("{ts} cannot be converted to DateTime")) + }) + } + } +} + /// Ser/de [`::time::OffsetDateTime`] and [`::time::Date`]. #[cfg(feature = "time")] pub mod time { From 89fe4f9ebe61bee508318ec092b535dcc22664bc Mon Sep 17 00:00:00 2001 From: Dean Shaff Date: Wed, 11 Dec 2024 09:39:46 +0100 Subject: [PATCH 2/9] try again --- src/serde.rs | 203 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) diff --git a/src/serde.rs b/src/serde.rs index 9a75c1d..4aa4d5f 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -143,6 +143,209 @@ pub mod chrono { }) } } + + /// Contains modules to ser/de `DateTime` to/from `DateTime64(_)`. + pub mod datetime64 { + use super::*; + type DateTimeUtc = DateTime; + + /// Ser/de `DateTime` to/from `DateTime64(0)` (seconds). + pub mod secs { + use super::*; + + option!( + DateTimeUtc, + "Ser/de `Option` to/from `Nullable(DateTime64(0))`." + ); + + pub fn serialize(dt: &DateTime, serializer: S) -> Result + where + S: Serializer, + { + let ts = dt.timestamp(); + ts.serialize(serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let ts: i64 = Deserialize::deserialize(deserializer)?; + DateTime::::from_timestamp(ts, 0).ok_or_else(|| { + D::Error::custom(format!("Can't create DateTime from {ts}")) + }) + } + } + + /// Ser/de `DateTime` to/from `DateTime64(3)` (milliseconds). + pub mod millis { + use super::*; + + option!( + DateTimeUtc, + "Ser/de `Option>` to/from `Nullable(DateTime64(3))`." + ); + + pub fn serialize(dt: &DateTime, serializer: S) -> Result + where + S: Serializer, + { + let ts = dt.timestamp_millis(); + ts.serialize(serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let ts: i64 = Deserialize::deserialize(deserializer)?; + DateTime::::from_timestamp_millis(ts).ok_or_else(|| { + D::Error::custom(format!("Can't create DateTime from {ts}")) + }) + } + } + + /// Ser/de `DateTime` to/from `DateTime64(6)` (microseconds). + pub mod micros { + use super::*; + + option!( + DateTimeUtc, + "Ser/de `Option>` to/from `Nullable(DateTime64(6))`." + ); + + pub fn serialize(dt: &DateTime, serializer: S) -> Result + where + S: Serializer, + { + let ts = dt.timestamp_micros(); + ts.serialize(serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let ts: i64 = Deserialize::deserialize(deserializer)?; + DateTime::::from_timestamp_micros(ts).ok_or_else(|| { + D::Error::custom(format!("Can't create DateTime from {ts}")) + }) + } + } + + /// Ser/de `DateTime` to/from `DateTime64(9)` (nanoseconds). + pub mod nanos { + use super::*; + + option!( + DateTimeUtc, + "Ser/de `Option>` to/from `Nullable(DateTime64(9))`." + ); + + pub fn serialize(dt: &DateTime, serializer: S) -> Result + where + S: Serializer, + { + let ts = dt.timestamp_nanos_opt().ok_or_else(|| { + S::Error::custom(format!("Can't create a timestamp from {dt}")) + })?; + ts.serialize(serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let ts: i64 = Deserialize::deserialize(deserializer)?; + Ok(DateTime::::from_timestamp_nanos(ts)) + } + } + } + + /// Ser/de `time::Date` to/from `Date`. + pub mod date { + use super::*; + use ::chrono::{Duration, NaiveDate}; + + option!( + NaiveDate, + "Ser/de `Option` to/from `Nullable(Date)`." + ); + + const ORIGIN: Option = NaiveDate::from_ymd_opt(1970, 0, 0); + + pub fn serialize(date: &NaiveDate, serializer: S) -> Result + where + S: Serializer, + { + let origin = ORIGIN.unwrap(); + if *date < origin { + let msg = format!("{date} cannot be represented as Date"); + return Err(S::Error::custom(msg)); + } + + let elapsed = *date - origin; // cannot underflow: checked above + let days = elapsed.num_days(); + + u16::try_from(days) + .map_err(|_| S::Error::custom(format!("{date} cannot be represented as Date")))? + .serialize(serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let days: u16 = Deserialize::deserialize(deserializer)?; + Ok(ORIGIN.unwrap() + Duration::days(i64::from(days))) // cannot overflow: always < `Date::MAX` + } + } + + /// Ser/de `time::Date` to/from `Date32`. + pub mod date32 { + use ::chrono::{Duration, NaiveDate}; + + use super::*; + + option!( + NaiveDate, + "Ser/de `Option` to/from `Nullable(Date32)`." + ); + + const ORIGIN: Option = NaiveDate::from_yo_opt(1970, 0); + + // NOTE: actually, it's 1925 and 2283 with a tail for versions before 22.8-lts. + const MIN: Option = NaiveDate::from_yo_opt(1900, 0); + const MAX: Option = NaiveDate::from_yo_opt(2299, 364); + + pub fn serialize(date: &NaiveDate, serializer: S) -> Result + where + S: Serializer, + { + if *date < MIN.unwrap() || *date > MAX.unwrap() { + let msg = format!("{date} cannot be represented as Date"); + return Err(S::Error::custom(msg)); + } + + let elapsed = *date - ORIGIN.unwrap(); // cannot underflow: checked above + let days = elapsed.num_days(); + + i32::try_from(days) + .map_err(|_| S::Error::custom(format!("{date} cannot be represented as Date32")))? + .serialize(serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let days: i32 = Deserialize::deserialize(deserializer)?; + + // It shouldn't overflow, because clamped by CH and < `Date::MAX`. + // TODO: ensure CH clamps when an invalid value is inserted in binary format. + Ok(ORIGIN.unwrap() + Duration::days(i64::from(days))) + } + } } /// Ser/de [`::time::OffsetDateTime`] and [`::time::Date`]. From d609be5fbc36f4294d727c325fc4c6cf44e7a805 Mon Sep 17 00:00:00 2001 From: Dean Shaff Date: Wed, 11 Dec 2024 10:06:16 +0100 Subject: [PATCH 3/9] add tests --- Cargo.toml | 2 +- src/serde.rs | 115 +++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 112 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 52c1919..0baf6e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,7 +69,7 @@ watch = ["dep:sha-1", "dep:serde_json", "serde/derive"] uuid = ["dep:uuid"] time = ["dep:time"] lz4 = ["dep:lz4_flex", "dep:cityhash-rs"] -chrono = ["dep:chrono"] +chrono = ["dep:chrono", "dep:serde_json"] ## TLS native-tls = ["dep:hyper-tls"] # ext: native-tls-alpn diff --git a/src/serde.rs b/src/serde.rs index 4aa4d5f..3c661f3 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -142,6 +142,28 @@ pub mod chrono { D::Error::custom(format!("{ts} cannot be converted to DateTime")) }) } + + #[cfg(test)] + mod tests { + use ::chrono::{DateTime, Utc}; + use serde::{Deserialize, Serialize}; + + #[derive(Serialize, Deserialize)] + struct WithDateTime { + #[serde(with = "super::datetime")] + dt: DateTime, + } + + #[test] + fn test_serde() { + let dt = WithDateTime { dt: Utc::now() }; + let serde_dt: WithDateTime = + serde_json::from_str(&serde_json::to_string(&dt).unwrap()).unwrap(); + // we have to compare `timestamp` because we lose information when serializing + // and deserializing using clickhouse `DateTime` format + assert!(dt.dt.timestamp() == serde_dt.dt.timestamp()); + } + } } /// Contains modules to ser/de `DateTime` to/from `DateTime64(_)`. @@ -260,6 +282,41 @@ pub mod chrono { Ok(DateTime::::from_timestamp_nanos(ts)) } } + + #[cfg(test)] + mod tests { + use ::chrono::{DateTime, Utc}; + use serde::{Deserialize, Serialize}; + + #[derive(Serialize, Deserialize)] + struct WithDateTime { + #[serde(with = "super::datetime64::nanos")] + dt_nanos: DateTime, + #[serde(with = "super::datetime64::micros")] + dt_micros: DateTime, + #[serde(with = "super::datetime64::millis")] + dt_millis: DateTime, + #[serde(with = "super::datetime64::secs")] + dt_secs: DateTime, + } + + #[test] + fn test_serde() { + let now = Utc::now(); + let dt = WithDateTime { + dt_nanos: now.clone(), + dt_micros: now.clone(), + dt_millis: now.clone(), + dt_secs: now.clone(), + }; + let serde_dt: WithDateTime = + serde_json::from_str(&serde_json::to_string(&dt).unwrap()).unwrap(); + assert!(dt.dt_nanos == serde_dt.dt_nanos); + assert!(dt.dt_micros == serde_dt.dt_micros); + assert!(dt.dt_millis.timestamp_millis() == serde_dt.dt_millis.timestamp_millis()); + assert!(dt.dt_secs.timestamp() == serde_dt.dt_secs.timestamp()); + } + } } /// Ser/de `time::Date` to/from `Date`. @@ -272,7 +329,7 @@ pub mod chrono { "Ser/de `Option` to/from `Nullable(Date)`." ); - const ORIGIN: Option = NaiveDate::from_ymd_opt(1970, 0, 0); + const ORIGIN: Option = NaiveDate::from_yo_opt(1970, 1); pub fn serialize(date: &NaiveDate, serializer: S) -> Result where @@ -299,6 +356,27 @@ pub mod chrono { let days: u16 = Deserialize::deserialize(deserializer)?; Ok(ORIGIN.unwrap() + Duration::days(i64::from(days))) // cannot overflow: always < `Date::MAX` } + + #[cfg(test)] + mod tests { + use ::chrono::{NaiveDate, Utc}; + use serde::{Deserialize, Serialize}; + + #[derive(Serialize, Deserialize)] + struct WithDate { + #[serde(with = "super::date")] + d: NaiveDate, + } + + #[test] + fn test_serde() { + let now = Utc::now().date_naive(); + let dt = WithDate { d: now.clone() }; + let serde_dt: WithDate = + serde_json::from_str(&serde_json::to_string(&dt).unwrap()).unwrap(); + assert!(dt.d == serde_dt.d); + } + } } /// Ser/de `time::Date` to/from `Date32`. @@ -312,11 +390,11 @@ pub mod chrono { "Ser/de `Option` to/from `Nullable(Date32)`." ); - const ORIGIN: Option = NaiveDate::from_yo_opt(1970, 0); + const ORIGIN: Option = NaiveDate::from_yo_opt(1970, 1); // NOTE: actually, it's 1925 and 2283 with a tail for versions before 22.8-lts. - const MIN: Option = NaiveDate::from_yo_opt(1900, 0); - const MAX: Option = NaiveDate::from_yo_opt(2299, 364); + const MIN: Option = NaiveDate::from_yo_opt(1900, 1); + const MAX: Option = NaiveDate::from_yo_opt(2299, 365); pub fn serialize(date: &NaiveDate, serializer: S) -> Result where @@ -345,6 +423,35 @@ pub mod chrono { // TODO: ensure CH clamps when an invalid value is inserted in binary format. Ok(ORIGIN.unwrap() + Duration::days(i64::from(days))) } + #[cfg(test)] + mod tests { + use ::chrono::{NaiveDate, Utc}; + use serde::{Deserialize, Serialize}; + + #[derive(Serialize, Deserialize)] + struct WithDate { + #[serde(with = "super::date32")] + d_32: NaiveDate, + } + + #[test] + fn test_serde() { + let now = Utc::now().date_naive(); + let dt = WithDate { d_32: now.clone() }; + let serde_dt: WithDate = + serde_json::from_str(&serde_json::to_string(&dt).unwrap()).unwrap(); + assert!(dt.d_32 == serde_dt.d_32); + } + + #[test] + fn test_serde_invalid() { + let french_revolution = NaiveDate::from_ymd_opt(1789, 7, 14).unwrap(); + let dt = WithDate { + d_32: french_revolution.clone(), + }; + assert!(serde_json::to_string(&dt).is_err()); + } + } } } From e153043493fd0ffba80f4f320a58f2820a5c8515 Mon Sep 17 00:00:00 2001 From: Dean Shaff Date: Wed, 11 Dec 2024 10:12:41 +0100 Subject: [PATCH 4/9] update readme --- README.md | 48 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d6f58e3..cea6655 100644 --- a/README.md +++ b/README.md @@ -224,6 +224,7 @@ See [examples](https://github.com/ClickHouse/clickhouse-rs/tree/main/examples). * `watch` — enables `client.watch` functionality. See the corresponding section for details. * `uuid` — adds `serde::uuid` to work with [uuid](https://docs.rs/uuid) crate. * `time` — adds `serde::time` to work with [time](https://docs.rs/time) crate. +* `chrono` — adds `serde::chrono` to work with [chrono](https://docs.rs/chrono) crate. ### TLS By default, TLS is disabled and one or more following features must be enabled to use HTTPS urls: @@ -328,7 +329,7 @@ How to choose between all these features? Here are some considerations: } ``` -* `Date` maps to/from `u16` or a newtype around it and represents a number of days elapsed since `1970-01-01`. Also, [`time::Date`](https://docs.rs/time/latest/time/struct.Date.html) is supported by using `serde::time::date`, that requires the `time` feature. +* `Date` maps to/from `u16` or a newtype around it and represents a number of days elapsed since `1970-01-01`. Also, [`time::Date`](https://docs.rs/time/latest/time/struct.Date.html) is supported by using `serde::time::date`, that requires the `time` feature. `chrono::NaiveDate` is supported by using `serde::chrono::date`, requiring the `chrono` feature.
Example @@ -339,9 +340,17 @@ How to choose between all these features? Here are some considerations: #[serde(with = "clickhouse::serde::time::date")] date: Date, } + + // or if you want to use `chrono`: + #[derive(Row, Serialize, Deserialize)] + struct MyRow { + days: u16, + #[serde(with = "clickhouse::serde::chrono::date")] + date: NaiveDate, + } ```
-* `Date32` maps to/from `i32` or a newtype around it and represents a number of days elapsed since `1970-01-01`. Also, [`time::Date`](https://docs.rs/time/latest/time/struct.Date.html) is supported by using `serde::time::date32`, that requires the `time` feature. +* `Date32` maps to/from `i32` or a newtype around it and represents a number of days elapsed since `1970-01-01`. Also, [`time::Date`](https://docs.rs/time/latest/time/struct.Date.html) is supported by using `serde::time::date32`, that requires the `time` feature. `chrono::NaiveDate` is supported by using `serde::chrono::date`, requiring the `chrono` feature.
Example @@ -352,9 +361,18 @@ How to choose between all these features? Here are some considerations: #[serde(with = "clickhouse::serde::time::date32")] date: Date, } + + // or if you want to use `chrono`: + #[derive(Row, Serialize, Deserialize)] + struct MyRow { + days: u16, + #[serde(with = "clickhouse::serde::chrono::date32")] + date: NaiveDate, + } + ```
-* `DateTime` maps to/from `u32` or a newtype around it and represents a number of seconds elapsed since UNIX epoch. Also, [`time::OffsetDateTime`](https://docs.rs/time/latest/time/struct.OffsetDateTime.html) is supported by using `serde::time::datetime`, that requires the `time` feature. +* `DateTime` maps to/from `u32` or a newtype around it and represents a number of seconds elapsed since UNIX epoch. Also, [`time::OffsetDateTime`](https://docs.rs/time/latest/time/struct.OffsetDateTime.html) is supported by using `serde::time::datetime`, that requires the `time` feature. `chrono::DateTime` is supported by using `serde::chrono::datetime`, which requires the `chrono` feature.
Example @@ -365,9 +383,16 @@ How to choose between all these features? Here are some considerations: #[serde(with = "clickhouse::serde::time::datetime")] dt: OffsetDateTime, } + // or if you prefer chrono: + #[derive(Row, Serialize, Deserialize)] + struct MyRow { + ts: u32, + #[serde(with = "clickhouse::serde::chrono::datetime")] + dt: DateTime, + } ```
-* `DateTime64(_)` maps to/from `i32` or a newtype around it and represents a time elapsed since UNIX epoch. Also, [`time::OffsetDateTime`](https://docs.rs/time/latest/time/struct.OffsetDateTime.html) is supported by using `serde::time::datetime64::*`, that requires the `time` feature. +* `DateTime64(_)` maps to/from `i32` or a newtype around it and represents a time elapsed since UNIX epoch. Also, [`time::OffsetDateTime`](https://docs.rs/time/latest/time/struct.OffsetDateTime.html) is supported by using `serde::time::datetime64::*`, that requires the `time` feature. `chrono::DateTime` is supported by using `serde::chrono::datetime`, which requires the `chrono` feature.
Example @@ -384,6 +409,21 @@ How to choose between all these features? Here are some considerations: #[serde(with = "clickhouse::serde::time::datetime64::nanos")] dt64ns: OffsetDateTime, // `DateTime64(9)` } + + // or if you prefer chrono: + #[derive(Row, Serialize, Deserialize)] + struct MyRow { + ts: u32, + #[serde(with = "clickhouse::serde::chrono::datetime64::secs")] + dt64s: DateTime, // `DateTime64(0)` + #[serde(with = "clickhouse::serde::chrono::datetime64::millis")] + dt64ms: DateTime, // `DateTime64(3)` + #[serde(with = "clickhouse::serde::chrono::datetime64::micros")] + dt64us: DateTime, // `DateTime64(6)` + #[serde(with = "clickhouse::serde::chrono::datetime64::nanos")] + dt64ns: DateTime, // `DateTime64(9)` + } + ```
* `Tuple(A, B, ...)` maps to/from `(A, B, ...)` or a newtype around it. From db02cf74433cfc05d6613c2be38adea698745974 Mon Sep 17 00:00:00 2001 From: Dean Shaff Date: Wed, 11 Dec 2024 10:16:17 +0100 Subject: [PATCH 5/9] update examples --- examples/data_types_derive_simple.rs | 43 +++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/examples/data_types_derive_simple.rs b/examples/data_types_derive_simple.rs index 1163746..55e0639 100644 --- a/examples/data_types_derive_simple.rs +++ b/examples/data_types_derive_simple.rs @@ -1,14 +1,14 @@ use std::str::FromStr; -use fixnum::typenum::{U12, U4, U8}; -use fixnum::FixedPoint; -use rand::distributions::Alphanumeric; -use rand::seq::SliceRandom; -use rand::Rng; +use chrono::{DateTime, NaiveDate, Utc}; +use fixnum::{ + typenum::{U12, U4, U8}, + FixedPoint, +}; +use rand::{distributions::Alphanumeric, seq::SliceRandom, Rng}; use time::{Date, Month, OffsetDateTime, Time}; -use clickhouse::sql::Identifier; -use clickhouse::{error::Result, Client}; +use clickhouse::{error::Result, sql::Identifier, Client}; // This example covers derivation of _simpler_ ClickHouse data types. // See also: https://clickhouse.com/docs/en/sql-reference/data-types @@ -138,6 +138,25 @@ pub struct Row { pub datetime64_9: OffsetDateTime, #[serde(with = "clickhouse::serde::time::datetime64::nanos")] pub datetime64_9_tz: OffsetDateTime, + + #[serde(with = "clickhouse::serde::chrono::date")] + pub chrono_date: NaiveDate, + #[serde(with = "clickhouse::serde::chrono::date32")] + pub chrono_date32: NaiveDate, + #[serde(with = "clickhouse::serde::chrono::datetime")] + pub chrono_datetime: DateTime, + #[serde(with = "clickhouse::serde::chrono::datetime")] + pub chrono_datetime_tz: DateTime, + #[serde(with = "clickhouse::serde::chrono::datetime64::secs")] + pub chrono_datetime64_0: DateTime, + #[serde(with = "clickhouse::serde::chrono::datetime64::millis")] + pub chrono_datetime64_3: DateTime, + #[serde(with = "clickhouse::serde::chrono::datetime64::micros")] + pub chrono_datetime64_6: DateTime, + #[serde(with = "clickhouse::serde::chrono::datetime64::nanos")] + pub chrono_datetime64_9: DateTime, + #[serde(with = "clickhouse::serde::chrono::datetime64::nanos")] + pub chrono_datetime64_9_tz: DateTime, } // See ClickHouse decimal sizes: https://clickhouse.com/docs/en/sql-reference/data-types/decimal @@ -215,6 +234,16 @@ impl Row { datetime64_6: max_datetime64(), datetime64_9: max_datetime64_nanos(), datetime64_9_tz: max_datetime64_nanos(), + + chrono_date: NaiveDate::from_ymd_opt(2149, 6, 6).unwrap(), + chrono_date32: NaiveDate::from_ymd_opt(2299, 12, 31).unwrap(), + chrono_datetime: Utc::now(), + chrono_datetime_tz: Utc::now(), + chrono_datetime64_0: Utc::now(), + chrono_datetime64_3: Utc::now(), + chrono_datetime64_6: Utc::now(), + chrono_datetime64_9: Utc::now(), + chrono_datetime64_9_tz: Utc::now(), } } } From bbfeb39764a40d086c6d774e7f170e0ca8edd084 Mon Sep 17 00:00:00 2001 From: Dean Shaff Date: Wed, 11 Dec 2024 18:20:54 +0100 Subject: [PATCH 6/9] fix clippy issue --- Cargo.toml | 2 +- src/serde.rs | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0baf6e6..d7449f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,7 +51,7 @@ required-features = ["rustls-tls"] [[example]] name = "data_types_derive_simple" -required-features = ["time", "uuid"] +required-features = ["time", "uuid", "chrono"] [[example]] name = "data_types_variant" diff --git a/src/serde.rs b/src/serde.rs index 3c661f3..2b10bb3 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -304,10 +304,10 @@ pub mod chrono { fn test_serde() { let now = Utc::now(); let dt = WithDateTime { - dt_nanos: now.clone(), - dt_micros: now.clone(), - dt_millis: now.clone(), - dt_secs: now.clone(), + dt_nanos: now, + dt_micros: now, + dt_millis: now, + dt_secs: now, }; let serde_dt: WithDateTime = serde_json::from_str(&serde_json::to_string(&dt).unwrap()).unwrap(); @@ -371,7 +371,7 @@ pub mod chrono { #[test] fn test_serde() { let now = Utc::now().date_naive(); - let dt = WithDate { d: now.clone() }; + let dt = WithDate { d: now }; let serde_dt: WithDate = serde_json::from_str(&serde_json::to_string(&dt).unwrap()).unwrap(); assert!(dt.d == serde_dt.d); @@ -437,7 +437,7 @@ pub mod chrono { #[test] fn test_serde() { let now = Utc::now().date_naive(); - let dt = WithDate { d_32: now.clone() }; + let dt = WithDate { d_32: now }; let serde_dt: WithDate = serde_json::from_str(&serde_json::to_string(&dt).unwrap()).unwrap(); assert!(dt.d_32 == serde_dt.d_32); @@ -447,7 +447,7 @@ pub mod chrono { fn test_serde_invalid() { let french_revolution = NaiveDate::from_ymd_opt(1789, 7, 14).unwrap(); let dt = WithDate { - d_32: french_revolution.clone(), + d_32: french_revolution, }; assert!(serde_json::to_string(&dt).is_err()); } From bfe8179b7f81d275c678088c25082a2a45858165 Mon Sep 17 00:00:00 2001 From: Dean Shaff Date: Mon, 6 Jan 2025 14:17:16 +0100 Subject: [PATCH 7/9] fix issues highlighted in PR --- Cargo.toml | 2 +- README.md | 39 ++-- examples/data_types_derive_simple.rs | 36 ++-- src/serde.rs | 113 +----------- tests/it/chrono.rs | 262 +++++++++++++++++++++++++++ tests/it/main.rs | 1 + 6 files changed, 304 insertions(+), 149 deletions(-) create mode 100644 tests/it/chrono.rs diff --git a/Cargo.toml b/Cargo.toml index d7449f5..1bf6b88 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,7 +69,7 @@ watch = ["dep:sha-1", "dep:serde_json", "serde/derive"] uuid = ["dep:uuid"] time = ["dep:time"] lz4 = ["dep:lz4_flex", "dep:cityhash-rs"] -chrono = ["dep:chrono", "dep:serde_json"] +chrono = ["dep:chrono"] ## TLS native-tls = ["dep:hyper-tls"] # ext: native-tls-alpn diff --git a/README.md b/README.md index cea6655..11e0da0 100644 --- a/README.md +++ b/README.md @@ -329,7 +329,9 @@ How to choose between all these features? Here are some considerations: } ``` -* `Date` maps to/from `u16` or a newtype around it and represents a number of days elapsed since `1970-01-01`. Also, [`time::Date`](https://docs.rs/time/latest/time/struct.Date.html) is supported by using `serde::time::date`, that requires the `time` feature. `chrono::NaiveDate` is supported by using `serde::chrono::date`, requiring the `chrono` feature. +* `Date` maps to/from `u16` or a newtype around it and represents a number of days elapsed since `1970-01-01`. The following external types are supported: + * [`time::Date`](https://docs.rs/time/latest/time/struct.Date.html) is supported by using `serde::time::date`, requiring the `time` feature. + * [`chrono::NaiveDate`](https://docs.rs/chrono/latest/chrono/struct.NaiveDate.html) is supported by using `serde::chrono::date`, requiring the `chrono` feature.
Example @@ -350,7 +352,9 @@ How to choose between all these features? Here are some considerations: } ```
-* `Date32` maps to/from `i32` or a newtype around it and represents a number of days elapsed since `1970-01-01`. Also, [`time::Date`](https://docs.rs/time/latest/time/struct.Date.html) is supported by using `serde::time::date32`, that requires the `time` feature. `chrono::NaiveDate` is supported by using `serde::chrono::date`, requiring the `chrono` feature. +* `Date32` maps to/from `i32` or a newtype around it and represents a number of days elapsed since `1970-01-01`. The following external types are supported: + * [`time::Date`](https://docs.rs/time/latest/time/struct.Date.html) is supported by using `serde::time::date32`, requiring the `time` feature. + * [`chrono::NaiveDate`](https://docs.rs/chrono/latest/chrono/struct.NaiveDate.html) is supported by using `serde::chrono::date32`, requiring the `chrono` feature.
Example @@ -372,7 +376,9 @@ How to choose between all these features? Here are some considerations: ```
-* `DateTime` maps to/from `u32` or a newtype around it and represents a number of seconds elapsed since UNIX epoch. Also, [`time::OffsetDateTime`](https://docs.rs/time/latest/time/struct.OffsetDateTime.html) is supported by using `serde::time::datetime`, that requires the `time` feature. `chrono::DateTime` is supported by using `serde::chrono::datetime`, which requires the `chrono` feature. +* `DateTime` maps to/from `u32` or a newtype around it and represents a number of seconds elapsed since UNIX epoch. The following external types are supported: + * [`time::OffsetDateTime`](https://docs.rs/time/latest/time/struct.OffsetDateTime.html) is supported by using `serde::time::datetime`, requiring the `time` feature. + * [`chrono::DateTime`](https://docs.rs/chrono/latest/chrono/struct.DateTime.html) is supported by using `serde::chrono::datetime`, requiring the `chrono` feature.
Example @@ -382,17 +388,14 @@ How to choose between all these features? Here are some considerations: ts: u32, #[serde(with = "clickhouse::serde::time::datetime")] dt: OffsetDateTime, - } - // or if you prefer chrono: - #[derive(Row, Serialize, Deserialize)] - struct MyRow { - ts: u32, #[serde(with = "clickhouse::serde::chrono::datetime")] - dt: DateTime, + dt_chrono: DateTime, } ```
-* `DateTime64(_)` maps to/from `i32` or a newtype around it and represents a time elapsed since UNIX epoch. Also, [`time::OffsetDateTime`](https://docs.rs/time/latest/time/struct.OffsetDateTime.html) is supported by using `serde::time::datetime64::*`, that requires the `time` feature. `chrono::DateTime` is supported by using `serde::chrono::datetime`, which requires the `chrono` feature. +* `DateTime64(_)` maps to/from `i32` or a newtype around it and represents a time elapsed since UNIX epoch. The following external types are supported: + * [`time::OffsetDateTime`](https://docs.rs/time/latest/time/struct.OffsetDateTime.html) is supported by using `serde::time::datetime64::*`, requiring the `time` feature. + * [`chrono::DateTime`](https://docs.rs/chrono/latest/chrono/struct.DateTime.html) is supported by using `serde::chrono::datetime64::*`, requiring the `chrono` feature.
Example @@ -408,22 +411,18 @@ How to choose between all these features? Here are some considerations: dt64us: OffsetDateTime, // `DateTime64(6)` #[serde(with = "clickhouse::serde::time::datetime64::nanos")] dt64ns: OffsetDateTime, // `DateTime64(9)` - } - - // or if you prefer chrono: - #[derive(Row, Serialize, Deserialize)] - struct MyRow { - ts: u32, + // if you prefer using chrono: #[serde(with = "clickhouse::serde::chrono::datetime64::secs")] - dt64s: DateTime, // `DateTime64(0)` + dt64s_chrono: DateTime, // `DateTime64(0)` #[serde(with = "clickhouse::serde::chrono::datetime64::millis")] - dt64ms: DateTime, // `DateTime64(3)` + dt64ms_chrono: DateTime, // `DateTime64(3)` #[serde(with = "clickhouse::serde::chrono::datetime64::micros")] - dt64us: DateTime, // `DateTime64(6)` + dt64us_chrono: DateTime, // `DateTime64(6)` #[serde(with = "clickhouse::serde::chrono::datetime64::nanos")] - dt64ns: DateTime, // `DateTime64(9)` + dt64ns_chrono: DateTime, // `DateTime64(9)` } + ```
* `Tuple(A, B, ...)` maps to/from `(A, B, ...)` or a newtype around it. diff --git a/examples/data_types_derive_simple.rs b/examples/data_types_derive_simple.rs index 55e0639..d633a63 100644 --- a/examples/data_types_derive_simple.rs +++ b/examples/data_types_derive_simple.rs @@ -121,23 +121,23 @@ pub struct Row { pub decimal64_18_8: Decimal64, pub decimal128_38_12: Decimal128, #[serde(with = "clickhouse::serde::time::date")] - pub date: Date, + pub time_date: Date, #[serde(with = "clickhouse::serde::time::date32")] - pub date32: Date, + pub time_date32: Date, #[serde(with = "clickhouse::serde::time::datetime")] - pub datetime: OffsetDateTime, + pub time_datetime: OffsetDateTime, #[serde(with = "clickhouse::serde::time::datetime")] - pub datetime_tz: OffsetDateTime, + pub time_datetime_tz: OffsetDateTime, #[serde(with = "clickhouse::serde::time::datetime64::secs")] - pub datetime64_0: OffsetDateTime, + pub time_datetime64_0: OffsetDateTime, #[serde(with = "clickhouse::serde::time::datetime64::millis")] - pub datetime64_3: OffsetDateTime, + pub time_datetime64_3: OffsetDateTime, #[serde(with = "clickhouse::serde::time::datetime64::micros")] - pub datetime64_6: OffsetDateTime, + pub time_datetime64_6: OffsetDateTime, #[serde(with = "clickhouse::serde::time::datetime64::nanos")] - pub datetime64_9: OffsetDateTime, + pub time_datetime64_9: OffsetDateTime, #[serde(with = "clickhouse::serde::time::datetime64::nanos")] - pub datetime64_9_tz: OffsetDateTime, + pub time_datetime64_9_tz: OffsetDateTime, #[serde(with = "clickhouse::serde::chrono::date")] pub chrono_date: NaiveDate, @@ -225,15 +225,15 @@ impl Row { // See // - https://clickhouse.com/docs/en/sql-reference/data-types/date // - https://clickhouse.com/docs/en/sql-reference/data-types/date32 - date: Date::from_calendar_date(2149, Month::June, 6).unwrap(), - date32: Date::from_calendar_date(2299, Month::December, 31).unwrap(), - datetime: max_datetime(), - datetime_tz: max_datetime(), - datetime64_0: max_datetime64(), - datetime64_3: max_datetime64(), - datetime64_6: max_datetime64(), - datetime64_9: max_datetime64_nanos(), - datetime64_9_tz: max_datetime64_nanos(), + time_date: Date::from_calendar_date(2149, Month::June, 6).unwrap(), + time_date32: Date::from_calendar_date(2299, Month::December, 31).unwrap(), + time_datetime: max_datetime(), + time_datetime_tz: max_datetime(), + time_datetime64_0: max_datetime64(), + time_datetime64_3: max_datetime64(), + time_datetime64_6: max_datetime64(), + time_datetime64_9: max_datetime64_nanos(), + time_datetime64_9_tz: max_datetime64_nanos(), chrono_date: NaiveDate::from_ymd_opt(2149, 6, 6).unwrap(), chrono_date32: NaiveDate::from_ymd_opt(2299, 12, 31).unwrap(), diff --git a/src/serde.rs b/src/serde.rs index 2b10bb3..68ef883 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -142,28 +142,6 @@ pub mod chrono { D::Error::custom(format!("{ts} cannot be converted to DateTime")) }) } - - #[cfg(test)] - mod tests { - use ::chrono::{DateTime, Utc}; - use serde::{Deserialize, Serialize}; - - #[derive(Serialize, Deserialize)] - struct WithDateTime { - #[serde(with = "super::datetime")] - dt: DateTime, - } - - #[test] - fn test_serde() { - let dt = WithDateTime { dt: Utc::now() }; - let serde_dt: WithDateTime = - serde_json::from_str(&serde_json::to_string(&dt).unwrap()).unwrap(); - // we have to compare `timestamp` because we lose information when serializing - // and deserializing using clickhouse `DateTime` format - assert!(dt.dt.timestamp() == serde_dt.dt.timestamp()); - } - } } /// Contains modules to ser/de `DateTime` to/from `DateTime64(_)`. @@ -269,7 +247,7 @@ pub mod chrono { S: Serializer, { let ts = dt.timestamp_nanos_opt().ok_or_else(|| { - S::Error::custom(format!("Can't create a timestamp from {dt}")) + S::Error::custom(format!("{dt} cannot be represented as DateTime64")) })?; ts.serialize(serializer) } @@ -282,41 +260,6 @@ pub mod chrono { Ok(DateTime::::from_timestamp_nanos(ts)) } } - - #[cfg(test)] - mod tests { - use ::chrono::{DateTime, Utc}; - use serde::{Deserialize, Serialize}; - - #[derive(Serialize, Deserialize)] - struct WithDateTime { - #[serde(with = "super::datetime64::nanos")] - dt_nanos: DateTime, - #[serde(with = "super::datetime64::micros")] - dt_micros: DateTime, - #[serde(with = "super::datetime64::millis")] - dt_millis: DateTime, - #[serde(with = "super::datetime64::secs")] - dt_secs: DateTime, - } - - #[test] - fn test_serde() { - let now = Utc::now(); - let dt = WithDateTime { - dt_nanos: now, - dt_micros: now, - dt_millis: now, - dt_secs: now, - }; - let serde_dt: WithDateTime = - serde_json::from_str(&serde_json::to_string(&dt).unwrap()).unwrap(); - assert!(dt.dt_nanos == serde_dt.dt_nanos); - assert!(dt.dt_micros == serde_dt.dt_micros); - assert!(dt.dt_millis.timestamp_millis() == serde_dt.dt_millis.timestamp_millis()); - assert!(dt.dt_secs.timestamp() == serde_dt.dt_secs.timestamp()); - } - } } /// Ser/de `time::Date` to/from `Date`. @@ -326,7 +269,7 @@ pub mod chrono { option!( NaiveDate, - "Ser/de `Option` to/from `Nullable(Date)`." + "Ser/de `Option` to/from `Nullable(Date)`." ); const ORIGIN: Option = NaiveDate::from_yo_opt(1970, 1); @@ -356,27 +299,6 @@ pub mod chrono { let days: u16 = Deserialize::deserialize(deserializer)?; Ok(ORIGIN.unwrap() + Duration::days(i64::from(days))) // cannot overflow: always < `Date::MAX` } - - #[cfg(test)] - mod tests { - use ::chrono::{NaiveDate, Utc}; - use serde::{Deserialize, Serialize}; - - #[derive(Serialize, Deserialize)] - struct WithDate { - #[serde(with = "super::date")] - d: NaiveDate, - } - - #[test] - fn test_serde() { - let now = Utc::now().date_naive(); - let dt = WithDate { d: now }; - let serde_dt: WithDate = - serde_json::from_str(&serde_json::to_string(&dt).unwrap()).unwrap(); - assert!(dt.d == serde_dt.d); - } - } } /// Ser/de `time::Date` to/from `Date32`. @@ -387,7 +309,7 @@ pub mod chrono { option!( NaiveDate, - "Ser/de `Option` to/from `Nullable(Date32)`." + "Ser/de `Option` to/from `Nullable(Date32)`." ); const ORIGIN: Option = NaiveDate::from_yo_opt(1970, 1); @@ -423,35 +345,6 @@ pub mod chrono { // TODO: ensure CH clamps when an invalid value is inserted in binary format. Ok(ORIGIN.unwrap() + Duration::days(i64::from(days))) } - #[cfg(test)] - mod tests { - use ::chrono::{NaiveDate, Utc}; - use serde::{Deserialize, Serialize}; - - #[derive(Serialize, Deserialize)] - struct WithDate { - #[serde(with = "super::date32")] - d_32: NaiveDate, - } - - #[test] - fn test_serde() { - let now = Utc::now().date_naive(); - let dt = WithDate { d_32: now }; - let serde_dt: WithDate = - serde_json::from_str(&serde_json::to_string(&dt).unwrap()).unwrap(); - assert!(dt.d_32 == serde_dt.d_32); - } - - #[test] - fn test_serde_invalid() { - let french_revolution = NaiveDate::from_ymd_opt(1789, 7, 14).unwrap(); - let dt = WithDate { - d_32: french_revolution, - }; - assert!(serde_json::to_string(&dt).is_err()); - } - } } } diff --git a/tests/it/chrono.rs b/tests/it/chrono.rs new file mode 100644 index 0000000..58a4a8b --- /dev/null +++ b/tests/it/chrono.rs @@ -0,0 +1,262 @@ +#![cfg(feature = "chrono")] + +use std::ops::RangeBounds; + +use chrono::{DateTime, Datelike, NaiveDate, Utc}; +use rand::{ + distributions::{Distribution, Standard}, + Rng, +}; +use serde::{Deserialize, Serialize}; + +use clickhouse::Row; + +#[tokio::test] +async fn datetime() { + let client = prepare_database!(); + + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Row)] + struct MyRow { + #[serde(with = "clickhouse::serde::chrono::datetime")] + dt: DateTime, + #[serde(with = "clickhouse::serde::chrono::datetime::option")] + dt_opt: Option>, + #[serde(with = "clickhouse::serde::chrono::datetime64::secs")] + dt64s: DateTime, + #[serde(with = "clickhouse::serde::chrono::datetime64::secs::option")] + dt64s_opt: Option>, + #[serde(with = "clickhouse::serde::chrono::datetime64::millis")] + dt64ms: DateTime, + #[serde(with = "clickhouse::serde::chrono::datetime64::millis::option")] + dt64ms_opt: Option>, + #[serde(with = "clickhouse::serde::chrono::datetime64::micros")] + dt64us: DateTime, + #[serde(with = "clickhouse::serde::chrono::datetime64::micros::option")] + dt64us_opt: Option>, + #[serde(with = "clickhouse::serde::chrono::datetime64::nanos")] + dt64ns: DateTime, + #[serde(with = "clickhouse::serde::chrono::datetime64::nanos::option")] + dt64ns_opt: Option>, + } + + #[derive(Debug, Deserialize, Row)] + struct MyRowStr { + dt: String, + dt64s: String, + dt64ms: String, + dt64us: String, + dt64ns: String, + } + + client + .query( + " + CREATE TABLE test( + dt DateTime, + dt_opt Nullable(DateTime), + dt64s DateTime64(0), + dt64s_opt Nullable(DateTime64(0)), + dt64ms DateTime64(3), + dt64ms_opt Nullable(DateTime64(3)), + dt64us DateTime64(6), + dt64us_opt Nullable(DateTime64(6)), + dt64ns DateTime64(9), + dt64ns_opt Nullable(DateTime64(9)) + ) + ENGINE = MergeTree ORDER BY dt + ", + ) + .execute() + .await + .unwrap(); + let d = NaiveDate::from_ymd_opt(2022, 11, 13).unwrap(); + let dt_s = d.and_hms_opt(15, 27, 42).unwrap().and_utc(); + let dt_ms = d.and_hms_milli_opt(15, 27, 42, 123).unwrap().and_utc(); + let dt_us = d.and_hms_micro_opt(15, 27, 42, 123456).unwrap().and_utc(); + let dt_ns = d.and_hms_nano_opt(15, 27, 42, 123456789).unwrap().and_utc(); + + let original_row = MyRow { + dt: dt_s, + dt_opt: Some(dt_s), + dt64s: dt_s, + dt64s_opt: Some(dt_s), + dt64ms: dt_ms, + dt64ms_opt: Some(dt_ms), + dt64us: dt_us, + dt64us_opt: Some(dt_us), + dt64ns: dt_ns, + dt64ns_opt: Some(dt_ns), + }; + + let mut insert = client.insert("test").unwrap(); + insert.write(&original_row).await.unwrap(); + insert.end().await.unwrap(); + + let row = client + .query("SELECT ?fields FROM test") + .fetch_one::() + .await + .unwrap(); + + let row_str = client + .query( + " + SELECT toString(dt), + toString(dt64s), + toString(dt64ms), + toString(dt64us), + toString(dt64ns) + FROM test + ", + ) + .fetch_one::() + .await + .unwrap(); + + assert_eq!(row, original_row); + assert_eq!(row_str.dt, &original_row.dt.to_string()[..19]); + assert_eq!(row_str.dt64s, &original_row.dt64s.to_string()[..19]); + assert_eq!(row_str.dt64ms, &original_row.dt64ms.to_string()[..23]); + assert_eq!(row_str.dt64us, &original_row.dt64us.to_string()[..26]); + assert_eq!(row_str.dt64ns, &original_row.dt64ns.to_string()[..29]); +} + +#[tokio::test] +async fn date() { + let client = prepare_database!(); + + #[derive(Debug, Serialize, Deserialize, Row)] + struct MyRow { + #[serde(with = "clickhouse::serde::chrono::date")] + date: NaiveDate, + #[serde(with = "clickhouse::serde::chrono::date::option")] + date_opt: Option, + } + + client + .query( + " + CREATE TABLE test( + date Date, + date_opt Nullable(Date) + ) ENGINE = MergeTree ORDER BY date + ", + ) + .execute() + .await + .unwrap(); + + let mut insert = client.insert("test").unwrap(); + + let dates = generate_dates(1970..2149, 100); + for &date in &dates { + let original_row = MyRow { + date, + date_opt: Some(date), + }; + + insert.write(&original_row).await.unwrap(); + } + insert.end().await.unwrap(); + + let actual = client + .query("SELECT ?fields, toString(date) FROM test ORDER BY date") + .fetch_all::<(MyRow, String)>() + .await + .unwrap(); + + assert_eq!(actual.len(), dates.len()); + + for ((row, date_str), expected) in actual.iter().zip(dates) { + assert_eq!(row.date, expected); + assert_eq!(row.date_opt, Some(expected)); + assert_eq!(date_str, &expected.to_string()); + } +} + +#[tokio::test] +async fn date32() { + let client = prepare_database!(); + + #[derive(Debug, Serialize, Deserialize, Row)] + struct MyRow { + #[serde(with = "clickhouse::serde::chrono::date32")] + date: NaiveDate, + #[serde(with = "clickhouse::serde::chrono::date32::option")] + date_opt: Option, + } + + client + .query( + " + CREATE TABLE test( + date Date32, + date_opt Nullable(Date32) + ) ENGINE = MergeTree ORDER BY date + ", + ) + .execute() + .await + .unwrap(); + + let mut insert = client.insert("test").unwrap(); + + let dates = generate_dates(1925..2283, 100); // TODO: 1900..=2299 for newer versions. + for &date in &dates { + let original_row = MyRow { + date, + date_opt: Some(date), + }; + + insert.write(&original_row).await.unwrap(); + } + insert.end().await.unwrap(); + + let actual = client + .query("SELECT ?fields, toString(date) FROM test ORDER BY date") + .fetch_all::<(MyRow, String)>() + .await + .unwrap(); + + assert_eq!(actual.len(), dates.len()); + + for ((row, date_str), expected) in actual.iter().zip(dates) { + assert_eq!(row.date, expected); + assert_eq!(row.date_opt, Some(expected)); + assert_eq!(date_str, &expected.to_string()); + } +} + +// Distribution isn't implemented for `chrono` types, but we can lift the implementation from the `time` crate: https://docs.rs/time/latest/src/time/rand.rs.html#14-20 +struct NaiveDateWrapper(NaiveDate); + +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> NaiveDateWrapper { + NaiveDateWrapper( + NaiveDate::from_num_days_from_ce_opt( + rng.gen_range( + NaiveDate::MIN.num_days_from_ce()..=NaiveDate::MAX.num_days_from_ce(), + ), + ) + .unwrap(), + ) + } +} + +fn generate_dates(years: impl RangeBounds, count: usize) -> Vec { + let mut rng = rand::thread_rng(); + let mut dates: Vec<_> = (&mut rng) + .sample_iter(Standard) + .filter_map(|date: NaiveDateWrapper| { + if years.contains(&date.0.year()) { + Some(date.0) + } else { + None + } + }) + .take(count) + .collect(); + + dates.sort_unstable(); + dates +} diff --git a/tests/it/main.rs b/tests/it/main.rs index 87995b4..c35a6cc 100644 --- a/tests/it/main.rs +++ b/tests/it/main.rs @@ -54,6 +54,7 @@ async fn flush_query_log(client: &Client) { client.query("SYSTEM FLUSH LOGS").execute().await.unwrap(); } +mod chrono; mod compression; mod cursor_error; mod cursor_stats; From 3432afc9d297f25278f932edda32a626c8886763 Mon Sep 17 00:00:00 2001 From: Dean Shaff Date: Wed, 8 Jan 2025 12:56:44 +0100 Subject: [PATCH 8/9] add chrono to tests --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fe6ab78..26819c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: - run: rustup show active-toolchain -v - run: cargo build - run: cargo build --no-default-features - - run: cargo build --features uuid,time + - run: cargo build --features uuid,time,chrono - run: cargo build --all-features rustfmt: From 6f328f7c4398325895f7849f6bf5c393a9493aee Mon Sep 17 00:00:00 2001 From: Dean Shaff Date: Mon, 20 Jan 2025 16:42:32 +0100 Subject: [PATCH 9/9] fix readme --- README.md | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 11e0da0..bab0983 100644 --- a/README.md +++ b/README.md @@ -341,15 +341,11 @@ How to choose between all these features? Here are some considerations: days: u16, #[serde(with = "clickhouse::serde::time::date")] date: Date, - } - - // or if you want to use `chrono`: - #[derive(Row, Serialize, Deserialize)] - struct MyRow { - days: u16, + // if you prefer using chrono: #[serde(with = "clickhouse::serde::chrono::date")] - date: NaiveDate, + date_chrono: NaiveDate, } + ``` * `Date32` maps to/from `i32` or a newtype around it and represents a number of days elapsed since `1970-01-01`. The following external types are supported: @@ -364,14 +360,10 @@ How to choose between all these features? Here are some considerations: days: i32, #[serde(with = "clickhouse::serde::time::date32")] date: Date, - } - - // or if you want to use `chrono`: - #[derive(Row, Serialize, Deserialize)] - struct MyRow { - days: u16, + // if you prefer using chrono: #[serde(with = "clickhouse::serde::chrono::date32")] - date: NaiveDate, + date_chrono: NaiveDate, + } ``` @@ -388,6 +380,7 @@ How to choose between all these features? Here are some considerations: ts: u32, #[serde(with = "clickhouse::serde::time::datetime")] dt: OffsetDateTime, + // if you prefer using chrono: #[serde(with = "clickhouse::serde::chrono::datetime")] dt_chrono: DateTime, } @@ -411,7 +404,7 @@ How to choose between all these features? Here are some considerations: dt64us: OffsetDateTime, // `DateTime64(6)` #[serde(with = "clickhouse::serde::time::datetime64::nanos")] dt64ns: OffsetDateTime, // `DateTime64(9)` - // if you prefer using chrono: + // if you prefer using chrono: #[serde(with = "clickhouse::serde::chrono::datetime64::secs")] dt64s_chrono: DateTime, // `DateTime64(0)` #[serde(with = "clickhouse::serde::chrono::datetime64::millis")]