use duckdb::types::{TimeUnit, Value as DuckValue, ValueRef};
use narwhal_core::Value;
pub(crate) fn value_to_sql(value: &Value) -> DuckValue {
match value {
Value::Null => DuckValue::Null,
Value::Bool(v) => DuckValue::Boolean(*v),
Value::Int(v) => DuckValue::BigInt(*v),
Value::Float(v) => DuckValue::Double(*v),
Value::String(v) => DuckValue::Text(v.clone()),
Value::Bytes(v) => DuckValue::Blob(v.clone()),
Value::Date(v) => DuckValue::Text(v.to_string()),
Value::Time(v) => DuckValue::Text(v.to_string()),
Value::DateTime(v) => DuckValue::Text(v.to_string()),
Value::Timestamp(v) => DuckValue::Text(v.to_rfc3339()),
Value::Uuid(v) => DuckValue::Text(v.to_string()),
Value::Json(v) => DuckValue::Text(v.to_string()),
Value::Unknown(v) => DuckValue::Text(v.clone()),
other => DuckValue::Text(format!("{other:?}")),
}
}
pub(crate) fn value_from_ref(value: ValueRef<'_>) -> Value {
match value {
ValueRef::Null => Value::Null,
ValueRef::Boolean(v) => Value::Bool(v),
ValueRef::TinyInt(v) => Value::Int(i64::from(v)),
ValueRef::SmallInt(v) => Value::Int(i64::from(v)),
ValueRef::Int(v) => Value::Int(i64::from(v)),
ValueRef::BigInt(v) => Value::Int(v),
ValueRef::HugeInt(v) => Value::String(v.to_string()),
ValueRef::UTinyInt(v) => Value::Int(i64::from(v)),
ValueRef::USmallInt(v) => Value::Int(i64::from(v)),
ValueRef::UInt(v) => Value::Int(i64::from(v)),
ValueRef::UBigInt(v) => Value::String(v.to_string()),
ValueRef::Float(v) => Value::Float(f64::from(v)),
ValueRef::Double(v) => Value::Float(v),
ValueRef::Decimal(d) => Value::String(d.to_string()),
ValueRef::Text(bytes) => match std::str::from_utf8(bytes) {
Ok(s) => Value::String(s.to_owned()),
Err(_) => Value::Bytes(bytes.to_vec()),
},
ValueRef::Blob(bytes) => Value::Bytes(bytes.to_vec()),
ValueRef::Date32(days) => {
chrono::NaiveDate::from_num_days_from_ce_opt(days + 719_163)
.map_or_else(|| Value::String(format!("date({days})")), Value::Date)
}
ValueRef::Time64(unit, ticks) => {
let ns = scaled_ns(unit, ticks);
let secs = (ns / 1_000_000_000) as u32;
let sub_ns = (ns % 1_000_000_000) as u32;
chrono::NaiveTime::from_num_seconds_from_midnight_opt(secs, sub_ns)
.map_or_else(|| Value::String(format!("time({ns}ns)")), Value::Time)
}
ValueRef::Timestamp(unit, ticks) => {
let ns = scaled_ns(unit, ticks);
let secs = ns / 1_000_000_000;
let sub_ns = (ns % 1_000_000_000) as u32;
chrono::DateTime::<chrono::Utc>::from_timestamp(secs, sub_ns).map_or_else(
|| Value::String(format!("timestamp({ns}ns)")),
Value::Timestamp,
)
}
ValueRef::Interval {
months,
days,
nanos,
} => {
let years = months / 12;
let rem_months = months % 12;
let hours = nanos / 3_600_000_000_000;
let rem_ns = nanos % 3_600_000_000_000;
let minutes = rem_ns / 60_000_000_000;
let rem_ns2 = rem_ns % 60_000_000_000;
let seconds = rem_ns2 / 1_000_000_000;
let sub_ns = rem_ns2 % 1_000_000_000;
let mut s = String::from("P");
if years != 0 {
s.push_str(&format!("{years}Y"));
}
if rem_months != 0 {
s.push_str(&format!("{rem_months}M"));
}
if days != 0 {
s.push_str(&format!("{days}D"));
}
if hours != 0 || minutes != 0 || seconds != 0 || sub_ns != 0 {
s.push('T');
if hours != 0 {
s.push_str(&format!("{hours}H"));
}
if minutes != 0 {
s.push_str(&format!("{minutes}M"));
}
if seconds != 0 || sub_ns != 0 {
s.push_str(&format!("{seconds}"));
if sub_ns != 0 {
s.push_str(format!(".{sub_ns:09}").trim_end_matches('0'));
}
s.push('S');
}
}
if s == "P" {
s.push_str("0D");
}
Value::String(s)
}
other => Value::String(format!("{:?}", other.to_owned())),
}
}
const fn scaled_ns(unit: TimeUnit, ticks: i64) -> i64 {
match unit {
TimeUnit::Second => ticks * 1_000_000_000,
TimeUnit::Millisecond => ticks * 1_000_000,
TimeUnit::Microsecond => ticks * 1_000,
TimeUnit::Nanosecond => ticks,
}
}