klickhouse icon indicating copy to clipboard operation
klickhouse copied to clipboard

`Display` trait implementation of `Value` not compatible with Clickhouse

Open cpg314 opened this issue 1 year ago • 1 comments

The following test currently fails:

async fn test_one<
    T: Clone + std::fmt::Debug + std::cmp::PartialEq + klickhouse::ToSql + klickhouse::FromSql,
>(
    sample: T,
    client: &klickhouse::Client,
) {
    let sample2: klickhouse::UnitValue<T> = client
        .query_one(klickhouse::QueryBuilder::new("SELECT $1").arg(sample.clone()))
        .await
        .unwrap();
    assert_eq!(sample, sample2.0);
}

#[tokio::test]
async fn values_display() {
    let client = super::get_client().await;

    test_one(klickhouse::Uuid::new_v4(), &client).await;
    test_one(
        klickhouse::DateTime::try_from(chrono::Utc::now()).unwrap(),
        &client,
    )
    .await;
    test_one(
        klickhouse::Date::from(chrono::NaiveDate::from_ymd_opt(2015, 3, 14).unwrap()),
        &client,
    )
    .await;

}

This is because the implementation of the Display trait on Value is sometimes but not consistently compatible with the respective Clickhouse types. For example, the following changes are required to make the above test pass:

@@ -386,19 +386,19 @@ impl fmt::Display for Value {
                 write!(f, "'")
             }
             Value::Uuid(uuid) => {
-                write!(f, "'{}'", uuid)
+                write!(f, "toUUID('{}')", uuid)
             }
             Value::Date(date) => {
                 let chrono_date: NaiveDate = (*date).into();
-                write!(f, "'{}'", chrono_date.format("%Y-%m-%d"))
+                write!(f, "{}", chrono_date.format("makeDate(%Y,%m,%d)"))
             }
             Value::DateTime(datetime) => {
                 let chrono_date: chrono::DateTime<Tz> =
                     (*datetime).try_into().map_err(|_| fmt::Error)?;
                 let string = chrono_date.to_rfc3339_opts(SecondsFormat::AutoSi, true);
-                write!(f, "'")?;
+                write!(f, "parseDateTimeBestEffort('")?;
                 escape_string(f, &string)?;
-                write!(f, "'")
+                write!(f, "')")
             }
             Value::DateTime64(datetime) => {
                let chrono_date: chrono::DateTime<Tz> =
                    FromSql::from_sql(&Type::DateTime64(datetime.2, datetime.0), self.clone())
                        .map_err(|_| fmt::Error)?;
                let string = chrono_date.to_rfc3339_opts(SecondsFormat::AutoSi, true);
                write!(f, "parseDateTime64BestEffort('")?;
                escape_string(f, &string)?;
                write!(f, "', {})", datetime.2)
            }

(node that DateTime64 was already correct)

I will send a draft PR with the test and the fixes for a couple of them but will probably need some time to address all Value variants.

cpg314 avatar Jan 09 '24 21:01 cpg314

WIP here: https://github.com/cpg314/klickhouse/commit/33cec571912569b3087ac7ad0d985a8a635a258e

cpg314 avatar Jan 09 '24 21:01 cpg314