use rkyv::{Archive, Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize)]
#[rkyv(
compare(PartialEq),
derive(Debug),
serialize_bounds(__S: rkyv::ser::Writer + rkyv::ser::Allocator),
deserialize_bounds(__D::Error: rkyv::rancor::Source),
// Needed for the same reason as `omit_bounds` below, but on the
// validation side: when a *consumer* turns on rkyv's `bytecheck` feature
// (this crate does not), the derive also emits a `Verify` impl, and the
// recursive fields leave it without the context bound. Only parsed when
// that feature is on, so it costs nothing when it is off.
bytecheck(bounds(__C: rkyv::validation::ArchiveContext)),
)]
pub enum DecodedValue {
Null,
Bool(bool),
I64(i64),
F64(f64),
Str(String),
Bytes(Vec<u8>),
Uuid([u8; 16]),
Decimal(String),
Interval {
months: i32,
days: i32,
microseconds: i64,
},
Date(i32),
Time(i64),
Timestamp(i64),
Timestamptz(i64),
Array(#[rkyv(omit_bounds)] Vec<DecodedValue>),
Composite(#[rkyv(omit_bounds)] Vec<DecodedValue>),
Object(#[rkyv(omit_bounds)] Vec<(String, DecodedValue)>),
Range {
#[rkyv(omit_bounds)]
lower: Option<Box<DecodedValue>>,
#[rkyv(omit_bounds)]
upper: Option<Box<DecodedValue>>,
inc_lower: bool,
inc_upper: bool,
empty: bool,
},
}
impl From<String> for DecodedValue {
fn from(value: String) -> Self {
DecodedValue::Str(value)
}
}
impl From<&str> for DecodedValue {
fn from(value: &str) -> Self {
DecodedValue::Str(value.to_string())
}
}
impl From<bool> for DecodedValue {
fn from(value: bool) -> Self {
DecodedValue::Bool(value)
}
}
impl From<i16> for DecodedValue {
fn from(value: i16) -> Self {
DecodedValue::I64(value.into())
}
}
impl From<i32> for DecodedValue {
fn from(value: i32) -> Self {
DecodedValue::I64(value.into())
}
}
impl From<i64> for DecodedValue {
fn from(value: i64) -> Self {
DecodedValue::I64(value)
}
}
impl From<f32> for DecodedValue {
fn from(value: f32) -> Self {
DecodedValue::F64(value.into())
}
}
impl From<f64> for DecodedValue {
fn from(value: f64) -> Self {
DecodedValue::F64(value)
}
}
impl From<Vec<u8>> for DecodedValue {
fn from(value: Vec<u8>) -> Self {
DecodedValue::Bytes(value)
}
}
impl From<uuid::Uuid> for DecodedValue {
fn from(value: uuid::Uuid) -> Self {
DecodedValue::Uuid(value.into_bytes())
}
}
#[derive(Debug, Clone, Archive, Serialize, Deserialize)]
#[rkyv(derive(Debug))]
pub struct CachedEntry {
pub rows: Vec<DecodedValue>,
pub tags: Vec<String>,
}
#[cfg(test)]
mod tests {
use super::*;
use rkyv::rancor::Error;
#[test]
fn round_trips_every_variant() {
let value = DecodedValue::Object(vec![
("id".into(), DecodedValue::Uuid([1; 16])),
("name".into(), DecodedValue::Str("Alice".into())),
("age".into(), DecodedValue::I64(30)),
("score".into(), DecodedValue::F64(1.5)),
("active".into(), DecodedValue::Bool(true)),
("balance".into(), DecodedValue::Decimal("12.50".into())),
(
"tags".into(),
DecodedValue::Array(vec![DecodedValue::Str("a".into()), DecodedValue::Null]),
),
("avatar".into(), DecodedValue::Bytes(vec![1, 2, 3])),
(
"point".into(),
DecodedValue::Composite(vec![DecodedValue::F64(1.0), DecodedValue::F64(2.0)]),
),
(
"span".into(),
DecodedValue::Interval {
months: 1,
days: 2,
microseconds: 3_600_000_000,
},
),
("day".into(), DecodedValue::Date(9525)),
("clock".into(), DecodedValue::Time(3_600_000_000)),
("naive_ts".into(), DecodedValue::Timestamp(1_000_000_000)),
("aware_ts".into(), DecodedValue::Timestamptz(1_000_000_000)),
(
"span_range".into(),
DecodedValue::Range {
lower: Some(Box::new(DecodedValue::I64(1))),
upper: Some(Box::new(DecodedValue::I64(10))),
inc_lower: true,
inc_upper: false,
empty: false,
},
),
]);
let bytes = rkyv::to_bytes::<Error>(&value).unwrap();
let archived = unsafe { rkyv::access_unchecked::<ArchivedDecodedValue>(&bytes) };
let decoded: DecodedValue = rkyv::deserialize::<DecodedValue, Error>(archived).unwrap();
assert_eq!(decoded, value);
}
#[test]
fn round_trips_cached_entry() {
let entry = CachedEntry {
rows: vec![DecodedValue::I64(1), DecodedValue::I64(2)],
tags: vec!["public.person".into()],
};
let bytes = rkyv::to_bytes::<Error>(&entry).unwrap();
let archived = unsafe { rkyv::access_unchecked::<ArchivedCachedEntry>(&bytes) };
let decoded: CachedEntry = rkyv::deserialize::<CachedEntry, Error>(archived).unwrap();
assert_eq!(decoded.rows, entry.rows);
assert_eq!(decoded.tags, entry.tags);
}
#[test]
fn from_native_string_types() {
assert_eq!(
DecodedValue::from("hello".to_string()),
DecodedValue::Str("hello".into())
);
assert_eq!(DecodedValue::from("hello"), DecodedValue::Str("hello".into()));
}
#[test]
fn from_native_bool() {
assert_eq!(DecodedValue::from(true), DecodedValue::Bool(true));
}
#[test]
fn from_native_integers_widen_into_i64() {
assert_eq!(DecodedValue::from(1i16), DecodedValue::I64(1));
assert_eq!(DecodedValue::from(2i32), DecodedValue::I64(2));
assert_eq!(DecodedValue::from(3i64), DecodedValue::I64(3));
}
#[test]
fn from_native_floats_widen_into_f64() {
assert_eq!(DecodedValue::from(1.5f32), DecodedValue::F64(1.5));
assert_eq!(DecodedValue::from(2.5f64), DecodedValue::F64(2.5));
}
#[test]
fn from_native_bytes() {
assert_eq!(DecodedValue::from(vec![1u8, 2, 3]), DecodedValue::Bytes(vec![1, 2, 3]));
}
#[test]
fn from_native_uuid() {
let u = uuid::Uuid::from_bytes([7; 16]);
assert_eq!(DecodedValue::from(u), DecodedValue::Uuid([7; 16]));
}
#[test]
fn into_conversion_works_at_a_query_param_style_call_site() {
let params: Vec<(&str, DecodedValue)> =
vec![("name", "Ada".into()), ("age", 30i64.into()), ("active", true.into())];
assert_eq!(params[0].1, DecodedValue::Str("Ada".into()));
assert_eq!(params[1].1, DecodedValue::I64(30));
assert_eq!(params[2].1, DecodedValue::Bool(true));
}
}