Skip to content

Commit fe35c09

Browse files
Add support for Time and Time64 types (#258)
Co-authored-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>
1 parent 7d70b73 commit fe35c09

File tree

9 files changed

+1249
-2
lines changed

9 files changed

+1249
-2
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ required-features = ["time", "uuid", "chrono"]
7878
name = "data_types_variant"
7979
required-features = ["time"]
8080

81+
[[example]]
82+
name = "time_types_example"
83+
required-features = ["time", "chrono"]
84+
8185
[profile.release]
8286
debug = true
8387

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,38 @@ How to choose between all these features? Here are some considerations:
400400
}
401401
402402
403+
```
404+
</details>
405+
* `Time` maps to/from i32 or a newtype around it. The Time data type is used to store a time value independent of any calendar date. It is ideal for representing daily schedules, event times, or any situation where only the time component (hours, minutes, seconds) is important.
406+
* [`time:Duration`](https://docs.rs/time/latest/time/struct.Duration.html) is is supported by using `serde::time::*`, requiring the `time` feature.
407+
* [`chrono::Duration`](https://docs.rs/chrono/latest/chrono/type.Duration.html) is supported by using `serde::chrono::*`, which is an alias to `TimeDelta`, requiring the `chrono` feature
408+
<details>
409+
<summary>Example</summary>
410+
411+
```rust,ignore
412+
#[derive(Row, Serialize, Deserialize)]
413+
struct MyRow {
414+
#[serde(with = "clickhouse::serde::chrono::time64::secs")]
415+
t0: chrono::Duration,
416+
#[serde(with = "clickhouse::serde::chrono::time64::secs::option")]
417+
t0_opt: Option<chrono::Duration>,
418+
}
419+
420+
```
421+
</details>
422+
* `Time64(_)` maps to/from i64 or a newtype around it. The Time data type is used to store a time value independent of any calendar date. It is ideal for representing daily schedules, event times, or any situation where only the time component (hours, minutes, seconds) is important.
423+
* [`time:Duration`](https://docs.rs/time/latest/time/struct.Duration.html) is is supported by using `serde::time::*`, requiring the `time` feature.
424+
* [`chrono::Duration`](https://docs.rs/chrono/latest/chrono/type.Duration.html) is supported by using `serde::chrono::*`, requiring the `chrono` feature
425+
<details>
426+
<summary>Example</summary>
427+
428+
```rust,ignore
429+
#[derive(Row, Serialize, Deserialize)]
430+
struct MyRow {
431+
#[serde(with = "clickhouse::serde::time::time")]
432+
t0: Time,
433+
}
434+
403435
```
404436
</details>
405437
* `Tuple(A, B, ...)` maps to/from `(A, B, ...)` or a newtype around it.

examples/time_types_example.rs

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
use chrono::Duration;
2+
use clickhouse::Client;
3+
use serde::{Deserialize, Serialize};
4+
5+
#[derive(Debug, Serialize, Deserialize, clickhouse::Row)]
6+
struct TimeExample {
7+
#[serde(with = "clickhouse::serde::time::time")]
8+
time_field: time::Duration,
9+
10+
#[serde(with = "clickhouse::serde::time::time::option")]
11+
time_optional: Option<time::Duration>,
12+
13+
#[serde(with = "clickhouse::serde::time::time64::secs")]
14+
time64_seconds: time::Duration,
15+
16+
#[serde(with = "clickhouse::serde::time::time64::millis")]
17+
time64_millis: time::Duration,
18+
19+
#[serde(with = "clickhouse::serde::time::time64::micros")]
20+
time64_micros: time::Duration,
21+
22+
#[serde(with = "clickhouse::serde::time::time64::nanos")]
23+
time64_nanos: time::Duration,
24+
}
25+
26+
#[derive(Debug, Serialize, Deserialize, clickhouse::Row)]
27+
struct TimeExampleChrono {
28+
#[serde(with = "clickhouse::serde::chrono::time")]
29+
time_field: Duration,
30+
31+
#[serde(with = "clickhouse::serde::chrono::time::option")]
32+
time_optional: Option<Duration>,
33+
34+
#[serde(with = "clickhouse::serde::chrono::time64::secs")]
35+
time64_seconds: Duration,
36+
37+
#[serde(with = "clickhouse::serde::chrono::time64::millis")]
38+
time64_millis: Duration,
39+
40+
#[serde(with = "clickhouse::serde::chrono::time64::micros")]
41+
time64_micros: Duration,
42+
43+
#[serde(with = "clickhouse::serde::chrono::time64::nanos")]
44+
time64_nanos: Duration,
45+
}
46+
47+
#[tokio::main]
48+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
49+
let client = Client::default();
50+
51+
let create_table_sql = r#"
52+
CREATE TABLE IF NOT EXISTS time_example (
53+
time_field Time,
54+
time_optional Nullable(Time),
55+
time64_seconds Time64(0),
56+
time64_millis Time64(3),
57+
time64_micros Time64(6),
58+
time64_nanos Time64(9)
59+
) ENGINE = MergeTree()
60+
ORDER BY time_field
61+
"#;
62+
63+
client.query(create_table_sql).execute().await?;
64+
65+
// Insert data using time crate
66+
let time_example = TimeExample {
67+
time_field: time::Duration::seconds(12 * 3600 + 34 * 60 + 56),
68+
time_optional: Some(time::Duration::seconds(23 * 3600 + 59 * 60 + 59)),
69+
time64_seconds: time::Duration::seconds(3600 + 2 * 60 + 3),
70+
time64_millis: time::Duration::seconds(4 * 3600 + 5 * 60 + 6)
71+
+ time::Duration::milliseconds(123),
72+
time64_micros: time::Duration::seconds(7 * 3600 + 8 * 60 + 9)
73+
+ time::Duration::microseconds(456_789),
74+
time64_nanos: time::Duration::seconds(10 * 3600 + 11 * 60 + 12)
75+
+ time::Duration::nanoseconds(123_456_789),
76+
};
77+
78+
let mut insert = client.insert::<TimeExample>("time_example")?;
79+
insert.write(&time_example).await?;
80+
insert.end().await?;
81+
82+
// Insert data using chrono crate
83+
let time_example_chrono = TimeExampleChrono {
84+
time_field: Duration::seconds(13 * 3600 + 45 * 60),
85+
time_optional: Some(Duration::seconds(1)),
86+
time64_seconds: Duration::seconds(2 * 3600 + 3 * 60 + 4),
87+
time64_millis: Duration::seconds(5 * 3600 + 6 * 60 + 7) + Duration::milliseconds(456),
88+
time64_micros: Duration::seconds(8 * 3600 + 9 * 60 + 10) + Duration::microseconds(789_012),
89+
time64_nanos: Duration::seconds(11 * 3600 + 12 * 60 + 13)
90+
+ Duration::nanoseconds(987_654_321),
91+
};
92+
93+
let mut insert = client.insert::<TimeExampleChrono>("time_example")?;
94+
insert.write(&time_example_chrono).await?;
95+
insert.end().await?;
96+
97+
// Insert chrono edge cases
98+
let edge_cases = vec![
99+
Duration::seconds(-999 * 3600 - 59 * 60 - 59), // Min
100+
Duration::zero(), // Midnight
101+
Duration::seconds(999 * 3600 + 59 * 60 + 59), // Max
102+
];
103+
104+
for (i, edge) in edge_cases.into_iter().enumerate() {
105+
let data = TimeExampleChrono {
106+
time_field: edge,
107+
time_optional: Some(edge),
108+
time64_seconds: edge,
109+
time64_millis: edge,
110+
time64_micros: edge,
111+
time64_nanos: edge,
112+
};
113+
let mut insert = client.insert::<TimeExampleChrono>("time_example")?;
114+
insert.write(&data).await?;
115+
insert.end().await?;
116+
println!("Inserted edge case #{i}: {edge:?}");
117+
}
118+
119+
// Query the data
120+
let rows: Vec<TimeExample> = client
121+
.query("SELECT * FROM time_example ORDER BY time_field")
122+
.fetch_all()
123+
.await?;
124+
for time_example in rows {
125+
println!("Time example: {time_example:?}");
126+
}
127+
128+
println!("Time and Time64 types example completed successfully!");
129+
130+
Ok(())
131+
}

src/rowbinary/tests.rs

Lines changed: 158 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ struct Timestamp32(u32);
77
#[derive(Debug, PartialEq, Serialize, Deserialize)]
88
struct Timestamp64(u64);
99

10+
#[derive(Debug, PartialEq, Serialize, Deserialize)]
11+
struct Time32(i32);
12+
13+
#[derive(Debug, PartialEq, Serialize, Deserialize)]
14+
struct Time64(i64);
15+
1016
#[derive(Debug, PartialEq, Serialize, Deserialize)]
1117
struct FixedPoint64(i64);
1218

@@ -25,6 +31,8 @@ struct Sample<'a> {
2531
float64: f64,
2632
datetime: Timestamp32,
2733
datetime64: Timestamp64,
34+
time32: Time32,
35+
time64: Time64,
2836
decimal64: FixedPoint64,
2937
decimal128: FixedPoint128,
3038
string: &'a str,
@@ -51,6 +59,8 @@ impl Row for Sample<'_> {
5159
"float64",
5260
"datetime",
5361
"datetime64",
62+
"time32",
63+
"time64",
5464
"decimal64",
5565
"decimal128",
5666
"string",
@@ -61,7 +71,7 @@ impl Row for Sample<'_> {
6171
"array",
6272
"boolean",
6373
];
64-
const COLUMN_COUNT: usize = 19;
74+
const COLUMN_COUNT: usize = 21;
6575
const KIND: crate::row::RowKind = crate::row::RowKind::Struct;
6676

6777
type Value<'a> = Sample<'a>;
@@ -79,6 +89,8 @@ fn sample() -> Sample<'static> {
7989
float64: 42.42,
8090
datetime: Timestamp32(2_301_990_162),
8191
datetime64: Timestamp64(2_301_990_162_123),
92+
time32: Time32(42_000), // 11:40:00 (42,000 seconds since midnight)
93+
time64: Time64(42_000_000_000), // 11:40:00.000000000 (42,000,000,000 nanoseconds since midnight)
8294
decimal64: FixedPoint64(4242 * 10_000_000),
8395
decimal128: FixedPoint128(4242 * 10_000_000),
8496
string: "01234",
@@ -115,6 +127,10 @@ fn sample_serialized() -> Vec<u8> {
115127
// [DateTime64(3)] 2042-12-12 12:42:42'123
116128
// (ts: 2301990162123)
117129
0xcb, 0x4e, 0x4e, 0xf9, 0x17, 0x02, 0x00, 0x00, //
130+
// [Time32] 11:40:00 (42,000 seconds since midnight)
131+
0x10, 0xa4, 0x00, 0x00, //
132+
// [Time64] 11:40:00.000000000 (42,000,000,000 nanoseconds since midnight)
133+
0x00, 0x24, 0x65, 0xc7, 0x09, 0x00, 0x00, 0x00, //
118134
// [Decimal64(9)] 42.420000000
119135
0x00, 0xd5, 0x6d, 0xe0, 0x09, 0x00, 0x00, 0x00, //
120136
// [Decimal128(9)] 42.420000000
@@ -160,3 +176,144 @@ fn it_deserializes() {
160176
assert_eq!(actual, sample());
161177
}
162178
}
179+
180+
#[test]
181+
fn it_serializes_time64() {
182+
let value = 42_000_000_000;
183+
let time64 = Time64(value);
184+
println!("Time64 value: {}", time64.0);
185+
let mut actual = Vec::new();
186+
super::serialize_into(&mut actual, &time64).unwrap();
187+
188+
// Expected: 42000000000 in little-endian
189+
let expected = value.to_le_bytes();
190+
191+
assert_eq!(actual, expected, "Time64 serialization mismatch");
192+
}
193+
194+
#[test]
195+
fn it_deserializes_time64() {
196+
let value_bytes = 42_000_000_000_i64.to_le_bytes();
197+
let time64 = { Time64(i64::from_le_bytes(value_bytes)) };
198+
assert_eq!(time64.0, 42_000_000_000, "Time deserialization mismatch");
199+
}
200+
201+
#[test]
202+
fn it_serializes_time32() {
203+
let value = 42_000;
204+
let time32 = Time32(value);
205+
let mut actual = Vec::new();
206+
super::serialize_into(&mut actual, &time32).unwrap();
207+
let expected = value.to_le_bytes();
208+
assert_eq!(actual, expected, "Time32 serialization mismatch");
209+
}
210+
211+
#[test]
212+
fn it_deserializes_time32() {
213+
let value_bytes = 42_000_i32.to_le_bytes();
214+
let time64 = { Time32(i32::from_le_bytes(value_bytes)) };
215+
assert_eq!(time64.0, 42_000, "Time deserialization mismatch");
216+
}
217+
218+
#[test]
219+
fn it_serializes_option_time32_some() {
220+
let value = 42_000;
221+
let time: Option<Time32> = Some(Time32(value));
222+
let mut actual = Vec::new();
223+
super::serialize_into(&mut actual, &time).unwrap();
224+
225+
// Nullable encoding: 0x00 = not null, followed by value
226+
let mut expected = vec![0x00];
227+
// extend after not null
228+
expected.extend_from_slice(&value.to_le_bytes());
229+
230+
assert_eq!(
231+
actual, expected,
232+
"Option<Time32> (Some) serialization mismatch"
233+
);
234+
}
235+
236+
#[cfg(feature = "chrono")]
237+
#[test]
238+
fn it_serializes_time32_overflow_fails() {
239+
use crate::serde::chrono::time;
240+
use chrono::Duration;
241+
242+
// Duration that exceeds i32::MAX
243+
let value = Duration::seconds(i64::from(i32::MAX) + 1);
244+
245+
// Use a dummy serializer just to trigger the error
246+
let result = time::serialize(&value, serde_json::value::Serializer);
247+
248+
assert!(result.is_err(), "Expected error due to overflow");
249+
250+
let err = result.unwrap_err().to_string();
251+
assert!(
252+
err.contains("cannot be represented as Time"),
253+
"Unexpected error message: {err}"
254+
);
255+
}
256+
257+
#[cfg(feature = "time")]
258+
#[test]
259+
fn it_time_serializes_time64_millis_overflow_fails() {
260+
use crate::serde::time::time64::millis;
261+
use time::Duration;
262+
263+
let value = Duration::milliseconds(i64::MAX) + Duration::milliseconds(1);
264+
265+
let result = millis::serialize(&value, serde_json::value::Serializer);
266+
267+
assert!(
268+
result.is_err(),
269+
"Expected error due to milliseconds overflow"
270+
);
271+
272+
let err = result.unwrap_err().to_string();
273+
assert!(
274+
err.contains("milliseconds too large for i64"),
275+
"Unexpected error message: {err}"
276+
);
277+
}
278+
279+
#[cfg(feature = "time")]
280+
#[test]
281+
fn it_time_serializes_time64_micros_overflow_fails() {
282+
use crate::serde::time::time64::micros;
283+
use time::Duration;
284+
285+
let value = Duration::microseconds(i64::MAX) + Duration::microseconds(1);
286+
let result = micros::serialize(&value, serde_json::value::Serializer);
287+
288+
assert!(
289+
result.is_err(),
290+
"Expected error due to microseconds overflow"
291+
);
292+
293+
let err = result.unwrap_err().to_string();
294+
assert!(
295+
err.contains("microseconds too large for i64"),
296+
"Unexpected error message: {err}"
297+
);
298+
}
299+
300+
#[cfg(feature = "time")]
301+
#[test]
302+
fn it_time_serializes_time64_nanos_overflow_fails() {
303+
use crate::serde::time::time64::nanos;
304+
use time::Duration;
305+
306+
let value = Duration::nanoseconds(i64::MAX) + Duration::nanoseconds(1);
307+
let result = nanos::serialize(&value, serde_json::value::Serializer);
308+
309+
assert!(
310+
result.is_err(),
311+
"Expected error due to nanoseconds overflow"
312+
);
313+
314+
let err = result.unwrap_err().to_string();
315+
assert!(
316+
err.contains("nanoseconds too large for i64"),
317+
"Unexpected error message: {err}"
318+
);
319+
}

0 commit comments

Comments
 (0)