use std::sync::Arc;
use serde::ser::{SerializeMap, SerializeSeq, SerializeStruct};
use serde::{Serialize, Serializer};
use duckdb::core::{LogicalTypeHandle, LogicalTypeId};
use crate::temporal::{self, Unit};
const VALUE_BYTES: usize = 16;
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum Value {
Null,
Bool(bool),
Int(i64),
UInt(u64),
HugeInt(i128),
UHugeInt(u128),
Float(f64),
Decimal(String),
Text(String),
Blob(Vec<u8>),
Date(String),
Time(String),
Timestamp(String),
Interval {
months: i32,
days: i32,
nanos: i64,
},
List(Vec<Self>),
Struct(Vec<(String, Self)>),
Map(Vec<(Self, Self)>),
}
impl Value {
pub(crate) fn size(&self) -> usize {
let content = match self {
Self::Decimal(v)
| Self::Text(v)
| Self::Date(v)
| Self::Time(v)
| Self::Timestamp(v) => v.len(),
Self::Blob(v) => v.len(),
Self::List(items) => items.iter().map(Self::size).sum(),
Self::Struct(fields) => fields.iter().map(|(k, v)| k.len() + v.size()).sum(),
Self::Map(entries) => entries.iter().map(|(k, v)| k.size() + v.size()).sum(),
_ => 0,
};
VALUE_BYTES + content
}
#[must_use]
pub const fn is_null(&self) -> bool {
matches!(self, Self::Null)
}
#[must_use]
pub const fn as_str(&self) -> Option<&str> {
match self {
Self::Decimal(v)
| Self::Text(v)
| Self::Date(v)
| Self::Time(v)
| Self::Timestamp(v) => Some(v.as_str()),
_ => None,
}
}
#[must_use]
pub fn as_i64(&self) -> Option<i64> {
match *self {
Self::Int(v) => Some(v),
Self::UInt(v) => i64::try_from(v).ok(),
Self::HugeInt(v) => i64::try_from(v).ok(),
Self::UHugeInt(v) => i64::try_from(v).ok(),
_ => None,
}
}
#[must_use]
#[allow(clippy::cast_precision_loss, reason = "the caller asks for an f64")]
pub fn as_f64(&self) -> Option<f64> {
match self {
Self::Float(v) => Some(*v),
Self::Int(v) => Some(*v as f64),
Self::UInt(v) => Some(*v as f64),
Self::HugeInt(v) => Some(*v as f64),
Self::UHugeInt(v) => Some(*v as f64),
Self::Decimal(v) => v.parse().ok(),
_ => None,
}
}
#[must_use]
pub const fn as_bool(&self) -> Option<bool> {
match self {
Self::Bool(v) => Some(*v),
_ => None,
}
}
}
impl Value {
pub(crate) fn from_raw(raw: duckdb::types::Value) -> Self {
use duckdb::types::Value as Raw;
match raw {
Raw::Null => Self::Null,
Raw::Boolean(v) => Self::Bool(v),
Raw::TinyInt(v) => Self::Int(v.into()),
Raw::SmallInt(v) => Self::Int(v.into()),
Raw::Int(v) => Self::Int(v.into()),
Raw::BigInt(v) => Self::Int(v),
Raw::HugeInt(v) => Self::HugeInt(v),
Raw::UTinyInt(v) => Self::UInt(v.into()),
Raw::USmallInt(v) => Self::UInt(v.into()),
Raw::UInt(v) => Self::UInt(v.into()),
Raw::UBigInt(v) => Self::UInt(v),
Raw::UHugeInt(v) => Self::UHugeInt(v),
Raw::Float(v) => Self::Float(v.into()),
Raw::Double(v) => Self::Float(v),
Raw::Decimal(v) => Self::Decimal(v.to_string()),
Raw::Timestamp(unit, v) => Self::Timestamp(temporal::timestamp_text(unit_of(unit), v)),
Raw::Text(v) | Raw::Enum(v) => Self::Text(v),
Raw::Blob(v) | Raw::Geometry(v) => Self::Blob(v),
Raw::Date32(v) => Self::Date(temporal::date_text(v)),
Raw::Time64(unit, v) => Self::Time(temporal::time_text(unit_of(unit), v)),
Raw::Interval {
months,
days,
nanos,
} => Self::Interval {
months,
days,
nanos,
},
Raw::List(items) | Raw::Array(items) => {
Self::List(items.into_iter().map(Self::from_raw).collect())
}
Raw::Struct(fields) => Self::Struct(
fields
.iter()
.map(|(k, v)| (k.clone(), Self::from_raw(v.clone())))
.collect(),
),
Raw::Map(entries) => Self::Map(
entries
.iter()
.map(|(k, v)| (Self::from_raw(k.clone()), Self::from_raw(v.clone())))
.collect(),
),
Raw::Union(inner) => Self::from_raw(*inner),
other => Self::Text(format!("{other:?}")),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Shape {
Plain,
TimestampTz,
Decimal(u8),
UHugeInt,
List(Box<Self>),
Struct(Vec<Self>),
Map(Box<Self>, Box<Self>),
}
pub(crate) fn shape_of(ty: &LogicalTypeHandle) -> Result<Shape, &'static str> {
Ok(match ty.id() {
LogicalTypeId::TimeNs => return Err("TIME_NS"),
LogicalTypeId::TimeTZ => return Err("TIMETZ"),
LogicalTypeId::Bit => return Err("BIT"),
LogicalTypeId::Bignum => return Err("BIGNUM"),
LogicalTypeId::Variant => return Err("VARIANT"),
LogicalTypeId::Unsupported => return Err("an unknown type"),
LogicalTypeId::TimestampTZ => Shape::TimestampTz,
LogicalTypeId::Decimal => Shape::Decimal(ty.decimal_scale()),
LogicalTypeId::UHugeint => Shape::UHugeInt,
LogicalTypeId::List | LogicalTypeId::Array => {
Shape::List(Box::new(shape_of(&ty.child(0))?))
}
LogicalTypeId::Struct => Shape::Struct(
(0..ty.num_children())
.map(|index| shape_of(&ty.child(index)))
.collect::<Result<_, _>>()?,
),
LogicalTypeId::Map => Shape::Map(
Box::new(shape_of(&ty.child(0))?),
Box::new(shape_of(&ty.child(1))?),
),
LogicalTypeId::Union => {
for index in 0..ty.num_children() {
shape_of(&ty.child(index))?;
}
Shape::Plain
}
_ => Shape::Plain,
})
}
impl Value {
pub(crate) fn from_shaped(raw: duckdb::types::Value, shape: &Shape) -> Self {
use duckdb::types::Value as Raw;
match (raw, shape) {
(Raw::Timestamp(unit, v), Shape::TimestampTz) => {
let text = temporal::timestamp_text(unit_of(unit), v);
if text.ends_with("infinity") {
Self::Timestamp(text)
} else {
Self::Timestamp(text + "Z")
}
}
(Raw::HugeInt(v), Shape::Decimal(scale)) => Self::Decimal(decimal_text(v, *scale)),
#[allow(clippy::cast_sign_loss, reason = "the bits are a UHUGEINT")]
(Raw::HugeInt(v), Shape::UHugeInt) => Self::UHugeInt(v as u128),
(Raw::List(items) | Raw::Array(items), Shape::List(item)) => Self::List(
items
.into_iter()
.map(|raw| Self::from_shaped(raw, item))
.collect(),
),
(Raw::Struct(fields), Shape::Struct(shapes)) => Self::Struct(
fields
.iter()
.zip(shapes.iter().chain(std::iter::repeat(&Shape::Plain)))
.map(|((k, v), shape)| (k.clone(), Self::from_shaped(v.clone(), shape)))
.collect(),
),
(Raw::Map(entries), Shape::Map(key, value)) => Self::Map(
entries
.iter()
.map(|(k, v)| {
(
Self::from_shaped(k.clone(), key),
Self::from_shaped(v.clone(), value),
)
})
.collect(),
),
(raw, _) => Self::from_raw(raw),
}
}
}
fn decimal_text(value: i128, scale: u8) -> String {
duckdb::types::Decimal::new(38, scale, value)
.map_or_else(|_| value.to_string(), |decimal| decimal.to_string())
}
const fn unit_of(unit: duckdb::types::TimeUnit) -> Unit {
use duckdb::types::TimeUnit;
match unit {
TimeUnit::Second => Unit::Seconds,
TimeUnit::Millisecond => Unit::Millis,
TimeUnit::Microsecond => Unit::Micros,
TimeUnit::Nanosecond => Unit::Nanos,
}
}
#[derive(Serialize)]
struct Entry<'a> {
key: &'a Value,
value: &'a Value,
}
impl Value {
const fn is_scalar_key(&self) -> bool {
!matches!(
self,
Self::Null
| Self::Blob(_)
| Self::Interval { .. }
| Self::List(_)
| Self::Struct(_)
| Self::Map(_)
)
}
}
impl Serialize for Value {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
Self::Null => serializer.serialize_none(),
Self::Bool(v) => serializer.serialize_bool(*v),
Self::Int(v) => serializer.serialize_i64(*v),
Self::UInt(v) => serializer.serialize_u64(*v),
Self::HugeInt(v) => serializer.serialize_i128(*v),
Self::UHugeInt(v) => serializer.serialize_u128(*v),
Self::Float(v) => serializer.serialize_f64(*v),
Self::Decimal(v)
| Self::Text(v)
| Self::Date(v)
| Self::Time(v)
| Self::Timestamp(v) => serializer.serialize_str(v),
Self::Blob(v) => serializer.collect_seq(v),
Self::Interval {
months,
days,
nanos,
} => {
let mut out = serializer.serialize_struct("Interval", 3)?;
out.serialize_field("months", months)?;
out.serialize_field("days", days)?;
out.serialize_field("nanos", nanos)?;
out.end()
}
Self::List(items) => {
let mut out = serializer.serialize_seq(Some(items.len()))?;
for item in items {
out.serialize_element(item)?;
}
out.end()
}
Self::Struct(fields) => serializer.collect_map(fields.iter().map(|(k, v)| (k, v))),
Self::Map(entries) if entries.iter().all(|(k, _)| k.is_scalar_key()) => {
let mut out = serializer.serialize_map(Some(entries.len()))?;
for (k, v) in entries {
out.serialize_entry(k, v)?;
}
out.end()
}
Self::Map(entries) => {
let mut out = serializer.serialize_seq(Some(entries.len()))?;
for (key, value) in entries {
out.serialize_element(&Entry { key, value })?;
}
out.end()
}
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Row {
columns: Arc<[String]>,
values: Vec<Value>,
}
impl Row {
pub(crate) const fn new(columns: Arc<[String]>, values: Vec<Value>) -> Self {
Self { columns, values }
}
#[must_use]
pub fn columns(&self) -> &[String] {
&self.columns
}
#[must_use]
pub fn values(&self) -> &[Value] {
&self.values
}
#[must_use]
pub fn into_values(self) -> Vec<Value> {
self.values
}
#[must_use]
pub fn get(&self, column: &str) -> Option<&Value> {
let index = self.columns.iter().position(|name| name == column)?;
self.values.get(index)
}
pub(crate) fn size(&self) -> usize {
self.values.iter().map(Value::size).sum()
}
}
impl Serialize for Row {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_map(self.columns.iter().zip(&self.values))
}
}
#[cfg(test)]
mod tests;