use rupg_common::{Error, Oid, Result};
use crate::typeid::TypeId;
#[derive(Clone, Debug, PartialEq)]
pub enum Datum {
Null,
Bool(bool),
Int2(i16),
Int4(i32),
Int8(i64),
Float4(f32),
Float8(f64),
Oid(Oid),
Text(String),
Bytea(Vec<u8>),
Uuid([u8; 16]),
Date(i32),
Timestamp(i64),
TimestampTz(i64),
}
impl Datum {
pub fn type_id(&self) -> Option<TypeId> {
Some(match self {
Datum::Null => return None,
Datum::Bool(_) => TypeId::BOOL,
Datum::Int2(_) => TypeId::INT2,
Datum::Int4(_) => TypeId::INT4,
Datum::Int8(_) => TypeId::INT8,
Datum::Float4(_) => TypeId::FLOAT4,
Datum::Float8(_) => TypeId::FLOAT8,
Datum::Oid(_) => TypeId::OID,
Datum::Text(_) => TypeId::TEXT,
Datum::Bytea(_) => TypeId::BYTEA,
Datum::Uuid(_) => TypeId::UUID,
Datum::Date(_) => TypeId::DATE,
Datum::Timestamp(_) => TypeId::TIMESTAMP,
Datum::TimestampTz(_) => TypeId::TIMESTAMPTZ,
})
}
pub fn is_null(&self) -> bool {
matches!(self, Datum::Null)
}
pub fn encode(&self) -> Option<Vec<u8>> {
Some(match self {
Datum::Null => return None,
Datum::Bool(v) => vec![u8::from(*v)],
Datum::Int2(v) => v.to_le_bytes().to_vec(),
Datum::Int4(v) | Datum::Date(v) => v.to_le_bytes().to_vec(),
Datum::Int8(v) | Datum::Timestamp(v) | Datum::TimestampTz(v) => {
v.to_le_bytes().to_vec()
}
Datum::Float4(v) => v.to_le_bytes().to_vec(),
Datum::Float8(v) => v.to_le_bytes().to_vec(),
Datum::Oid(v) => v.0.to_le_bytes().to_vec(),
Datum::Text(v) => v.as_bytes().to_vec(),
Datum::Bytea(v) => v.clone(),
Datum::Uuid(v) => v.to_vec(),
})
}
pub fn decode(ty: TypeId, bytes: Option<&[u8]>) -> Result<Datum> {
let Some(bytes) = bytes else {
return Ok(Datum::Null);
};
let bad = || {
Error::corrupted(format!("a stored {ty} value of {} bytes is not valid", bytes.len()))
};
if ty.width().is_some_and(|w| usize::from(w) != bytes.len()) {
return Err(bad());
}
let n2 = || <[u8; 2]>::try_from(bytes).map_err(|_| bad());
let n4 = || <[u8; 4]>::try_from(bytes).map_err(|_| bad());
let n8 = || <[u8; 8]>::try_from(bytes).map_err(|_| bad());
Ok(match ty {
TypeId::BOOL => match bytes {
[0] => Datum::Bool(false),
[1] => Datum::Bool(true),
_ => return Err(bad()),
},
TypeId::INT2 => Datum::Int2(i16::from_le_bytes(n2()?)),
TypeId::INT4 => Datum::Int4(i32::from_le_bytes(n4()?)),
TypeId::INT8 => Datum::Int8(i64::from_le_bytes(n8()?)),
TypeId::FLOAT4 => Datum::Float4(f32::from_le_bytes(n4()?)),
TypeId::FLOAT8 => Datum::Float8(f64::from_le_bytes(n8()?)),
TypeId::OID => Datum::Oid(Oid(u32::from_le_bytes(n4()?))),
TypeId::DATE => Datum::Date(i32::from_le_bytes(n4()?)),
TypeId::TIMESTAMP => Datum::Timestamp(i64::from_le_bytes(n8()?)),
TypeId::TIMESTAMPTZ => Datum::TimestampTz(i64::from_le_bytes(n8()?)),
TypeId::UUID => Datum::Uuid(bytes.try_into().map_err(|_| bad())?),
TypeId::TEXT => Datum::Text(String::from_utf8(bytes.to_vec()).map_err(|_| bad())?),
TypeId::BYTEA => Datum::Bytea(bytes.to_vec()),
_ => {
return Err(Error::internal(format!(
"the type with OID {} has no storage form at M1",
ty.0
)));
}
})
}
}
#[cfg(test)]
mod tests {
use rupg_common::SqlState;
use super::*;
fn samples() -> Vec<Datum> {
vec![
Datum::Bool(false),
Datum::Bool(true),
Datum::Int2(i16::MIN),
Datum::Int4(-7),
Datum::Int8(i64::MAX),
Datum::Float4(-0.5),
Datum::Float8(f64::INFINITY),
Datum::Oid(Oid(16384)),
Datum::Text("héllo".into()),
Datum::Text(String::new()),
Datum::Bytea(vec![0, 255, 7]),
Datum::Uuid([9; 16]),
Datum::Date(i32::MIN),
Datum::Timestamp(-1),
Datum::TimestampTz(i64::MAX),
]
}
#[test]
fn storage_forms() {
for d in samples() {
let ty = d.type_id().unwrap();
let bytes = d.encode().unwrap();
if let Some(w) = ty.width() {
assert_eq!(bytes.len(), usize::from(w), "{ty}");
}
assert_eq!(Datum::decode(ty, Some(&bytes)).unwrap(), d);
assert_eq!(TypeId::from_oid(ty.0), Some(ty));
assert_ne!(ty.name(), "unknown");
assert_ne!(ty.sql_name(), "unknown");
}
assert_eq!(Datum::Int4(0x0102_0304).encode().unwrap(), [4, 3, 2, 1]);
assert_eq!((Datum::Null.encode(), Datum::Null.type_id()), (None, None));
assert!(Datum::decode(TypeId::TEXT, None).unwrap().is_null());
let nan = Datum::decode(TypeId::FLOAT8, Some(&f64::NAN.to_le_bytes())).unwrap();
assert!(matches!(nan, Datum::Float8(v) if v.is_nan()));
assert_eq!(TypeId::from_oid(Oid(1700)), None);
assert_eq!((TypeId::INT4.sql_name(), TypeId::TEXT.sql_name()), ("integer", "text"));
}
#[test]
fn bad_storage_forms() {
let bad: [(TypeId, &[u8]); 5] = [
(TypeId::INT4, &[1, 2, 3]),
(TypeId::BOOL, &[2]),
(TypeId::UUID, &[0; 15]),
(TypeId::TEXT, &[0xff, 0xfe]),
(TypeId::INT8, &[]),
];
for (ty, bytes) in bad {
assert_eq!(
Datum::decode(ty, Some(bytes)).unwrap_err().state(),
SqlState::DATA_CORRUPTED,
"{ty}"
);
}
let numeric = TypeId(Oid(1700));
assert_eq!(
Datum::decode(numeric, Some(&[0])).unwrap_err().state(),
SqlState::INTERNAL_ERROR
);
assert_eq!(numeric.name(), "unknown");
}
}