Skip to content

add support for time and time64 #252

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ required-features = ["time", "uuid", "chrono"]
name = "data_types_variant"
required-features = ["time"]

[[example]]
name = "time_types_example"
required-features = ["time", "chrono"]

[profile.release]
debug = true

Expand Down
110 changes: 110 additions & 0 deletions examples/time_types_example.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
use clickhouse::Client;
use serde::{Deserialize, Serialize};
use time::Time;

#[derive(Debug, Serialize, Deserialize, clickhouse::Row)]
struct TimeExample {
#[serde(with = "clickhouse::serde::time::time")]
time_field: Time,

#[serde(with = "clickhouse::serde::time::time::option")]
time_optional: Option<Time>,

#[serde(with = "clickhouse::serde::time::time64::secs")]
time64_seconds: Time,

#[serde(with = "clickhouse::serde::time::time64::millis")]
time64_millis: Time,

#[serde(with = "clickhouse::serde::time::time64::micros")]
time64_micros: Time,

#[serde(with = "clickhouse::serde::time::time64::nanos")]
time64_nanos: Time,
}

#[derive(Debug, Serialize, Deserialize, clickhouse::Row)]
struct TimeExampleChrono {
#[serde(with = "clickhouse::serde::chrono::time")]
time_field: chrono::NaiveTime,

#[serde(with = "clickhouse::serde::chrono::time::option")]
time_optional: Option<chrono::NaiveTime>,

#[serde(with = "clickhouse::serde::chrono::time64::secs")]
time64_seconds: chrono::NaiveTime,

#[serde(with = "clickhouse::serde::chrono::time64::millis")]
time64_millis: chrono::NaiveTime,

#[serde(with = "clickhouse::serde::chrono::time64::micros")]
time64_micros: chrono::NaiveTime,

#[serde(with = "clickhouse::serde::chrono::time64::nanos")]
time64_nanos: chrono::NaiveTime,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a connection pool
let client = Client::default();

// Create a client
// let client = pool.get_handle()?; // This line is removed as per the edit hint

// Create table with Time and Time64 columns
let create_table_sql = r#"
CREATE TABLE IF NOT EXISTS time_example (
time_field Time,
time_optional Nullable(Time),
time64_seconds Time64(0),
time64_millis Time64(3),
time64_micros Time64(6),
time64_nanos Time64(9)
) ENGINE = MergeTree()
ORDER BY time_field
"#;

client.query(create_table_sql).execute().await?;

// Insert data using time crate
let time_example = TimeExample {
time_field: Time::from_hms(12, 34, 56).unwrap(),
time_optional: Some(Time::from_hms(23, 59, 59).unwrap()),
time64_seconds: Time::from_hms(1, 2, 3).unwrap(),
time64_millis: Time::from_hms_milli(4, 5, 6, 123).unwrap(),
time64_micros: Time::from_hms_micro(7, 8, 9, 456789).unwrap(),
time64_nanos: Time::from_hms_nano(10, 11, 12, 123456789).unwrap(),
};

let mut insert = client.insert::<TimeExample>("time_example")?;
insert.write(&time_example).await?;
insert.end().await?;

// Insert data using chrono crate
let time_example_chrono = TimeExampleChrono {
time_field: chrono::NaiveTime::from_hms_opt(13, 45, 67).unwrap(),
time_optional: Some(chrono::NaiveTime::from_hms_opt(0, 0, 1).unwrap()),
time64_seconds: chrono::NaiveTime::from_hms_opt(2, 3, 4).unwrap(),
time64_millis: chrono::NaiveTime::from_hms_milli_opt(5, 6, 7, 456).unwrap(),
time64_micros: chrono::NaiveTime::from_hms_micro_opt(8, 9, 10, 789012).unwrap(),
time64_nanos: chrono::NaiveTime::from_hms_nano_opt(11, 12, 13, 987654321).unwrap(),
};

let mut insert = client.insert::<TimeExampleChrono>("time_example")?;
insert.write(&time_example_chrono).await?;
insert.end().await?;

// Query the data
let rows: Vec<TimeExample> = client
.query("SELECT * FROM time_example ORDER BY time_field")
.fetch_all()
.await?;
for time_example in rows {
println!("Time example: {:?}", time_example);
}

println!("✅ Time and Time64 types example completed successfully!");

Ok(())
}
39 changes: 38 additions & 1 deletion src/rowbinary/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ struct Timestamp32(u32);
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Timestamp64(u64);

#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Time32(u32);

#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Time64(u64);

#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct FixedPoint64(i64);

Expand All @@ -25,6 +31,8 @@ struct Sample<'a> {
float64: f64,
datetime: Timestamp32,
datetime64: Timestamp64,
time32: Time32,
time64: Time64,
decimal64: FixedPoint64,
decimal128: FixedPoint128,
string: &'a str,
Expand All @@ -51,6 +59,8 @@ impl Row for Sample<'_> {
"float64",
"datetime",
"datetime64",
"time32",
"time64",
"decimal64",
"decimal128",
"string",
Expand All @@ -61,7 +71,7 @@ impl Row for Sample<'_> {
"array",
"boolean",
];
const COLUMN_COUNT: usize = 19;
const COLUMN_COUNT: usize = 21;
const KIND: crate::row::RowKind = crate::row::RowKind::Struct;

type Value<'a> = Sample<'a>;
Expand All @@ -79,6 +89,8 @@ fn sample() -> Sample<'static> {
float64: 42.42,
datetime: Timestamp32(2_301_990_162),
datetime64: Timestamp64(2_301_990_162_123),
time32: Time32(42_000), // 11:40:00 (42,000 seconds since midnight)
time64: Time64(42_000_000_000), // 11:40:00.000000000 (42,000,000,000 nanoseconds since midnight)
decimal64: FixedPoint64(4242 * 10_000_000),
decimal128: FixedPoint128(4242 * 10_000_000),
string: "01234",
Expand Down Expand Up @@ -115,6 +127,10 @@ fn sample_serialized() -> Vec<u8> {
// [DateTime64(3)] 2042-12-12 12:42:42'123
// (ts: 2301990162123)
0xcb, 0x4e, 0x4e, 0xf9, 0x17, 0x02, 0x00, 0x00, //
// [Time32] 11:40:00 (42,000 seconds since midnight)
0x10, 0xa4, 0x00, 0x00, //
// [Time64] 11:40:00.000000000 (42,000,000,000 nanoseconds since midnight)
0x00, 0x24, 0x65, 0xc7, 0x09, 0x00, 0x00, 0x00, //
// [Decimal64(9)] 42.420000000
0x00, 0xd5, 0x6d, 0xe0, 0x09, 0x00, 0x00, 0x00, //
// [Decimal128(9)] 42.420000000
Expand Down Expand Up @@ -160,3 +176,24 @@ fn it_deserializes() {
assert_eq!(actual, sample());
}
}

#[test]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also add a separate time/64 it for a round-trip, including corner case values

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, WIP

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

fn it_serializes_time64_correctly() {
let time64 = Time64(42_000_000_000);
println!("Time64 value: {}", time64.0);
let mut actual = Vec::new();
super::serialize_into(&mut actual, &time64).unwrap();

// Expected: 42000000000 in little-endian
let expected = vec![0x00, 0x24, 0x65, 0xc7, 0x09, 0x00, 0x00, 0x00];
println!("Actual bytes: {:?}", actual);
println!("Expected bytes: {:?}", expected);

// Calculate what the actual bytes represent
let actual_value = u64::from_le_bytes([
actual[0], actual[1], actual[2], actual[3], actual[4], actual[5], actual[6], actual[7],
]);
println!("Actual value: {}", actual_value);

assert_eq!(actual, expected, "Time64 serialization mismatch");
}
8 changes: 7 additions & 1 deletion src/rowbinary/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -487,11 +487,17 @@ fn validate_impl<'de, 'cursor, R: Row>(
SerdeType::U32
if data_type == &DataTypeNode::UInt32
|| matches!(data_type, DataTypeNode::DateTime(_))
|| matches!(data_type, DataTypeNode::Time)
|| data_type == &DataTypeNode::IPv4 =>
{
None
}
SerdeType::U64 if data_type == &DataTypeNode::UInt64 => None,
SerdeType::U64
if data_type == &DataTypeNode::UInt64
|| matches!(data_type, DataTypeNode::Time64(_, _)) =>
{
None
}
SerdeType::U128 if data_type == &DataTypeNode::UInt128 => None,
SerdeType::F32 if data_type == &DataTypeNode::Float32 => None,
SerdeType::F64 if data_type == &DataTypeNode::Float64 => None,
Expand Down
Loading
Loading