1use rupg_common::{Error, Oid, Result};
6
7use crate::typeid::TypeId;
8
9#[derive(Clone, Debug, PartialEq)]
11pub enum Datum {
12 Null,
14 Bool(bool),
16 Int2(i16),
18 Int4(i32),
20 Int8(i64),
22 Float4(f32),
24 Float8(f64),
26 Oid(Oid),
28 Text(String),
30 Bytea(Vec<u8>),
32 Uuid([u8; 16]),
34 Date(i32),
36 Timestamp(i64),
38 TimestampTz(i64),
40}
41
42impl Datum {
43 pub fn type_id(&self) -> Option<TypeId> {
45 Some(match self {
46 Datum::Null => return None,
47 Datum::Bool(_) => TypeId::BOOL,
48 Datum::Int2(_) => TypeId::INT2,
49 Datum::Int4(_) => TypeId::INT4,
50 Datum::Int8(_) => TypeId::INT8,
51 Datum::Float4(_) => TypeId::FLOAT4,
52 Datum::Float8(_) => TypeId::FLOAT8,
53 Datum::Oid(_) => TypeId::OID,
54 Datum::Text(_) => TypeId::TEXT,
55 Datum::Bytea(_) => TypeId::BYTEA,
56 Datum::Uuid(_) => TypeId::UUID,
57 Datum::Date(_) => TypeId::DATE,
58 Datum::Timestamp(_) => TypeId::TIMESTAMP,
59 Datum::TimestampTz(_) => TypeId::TIMESTAMPTZ,
60 })
61 }
62
63 pub fn is_null(&self) -> bool {
65 matches!(self, Datum::Null)
66 }
67
68 pub fn encode(&self) -> Option<Vec<u8>> {
70 Some(match self {
71 Datum::Null => return None,
72 Datum::Bool(v) => vec![u8::from(*v)],
73 Datum::Int2(v) => v.to_le_bytes().to_vec(),
74 Datum::Int4(v) | Datum::Date(v) => v.to_le_bytes().to_vec(),
75 Datum::Int8(v) | Datum::Timestamp(v) | Datum::TimestampTz(v) => {
76 v.to_le_bytes().to_vec()
77 }
78 Datum::Float4(v) => v.to_le_bytes().to_vec(),
79 Datum::Float8(v) => v.to_le_bytes().to_vec(),
80 Datum::Oid(v) => v.0.to_le_bytes().to_vec(),
81 Datum::Text(v) => v.as_bytes().to_vec(),
82 Datum::Bytea(v) => v.clone(),
83 Datum::Uuid(v) => v.to_vec(),
84 })
85 }
86
87 pub fn decode(ty: TypeId, bytes: Option<&[u8]>) -> Result<Datum> {
89 let Some(bytes) = bytes else {
90 return Ok(Datum::Null);
91 };
92 let bad = || {
93 Error::corrupted(format!("a stored {ty} value of {} bytes is not valid", bytes.len()))
94 };
95 if ty.width().is_some_and(|w| usize::from(w) != bytes.len()) {
96 return Err(bad());
97 }
98 let n2 = || <[u8; 2]>::try_from(bytes).map_err(|_| bad());
99 let n4 = || <[u8; 4]>::try_from(bytes).map_err(|_| bad());
100 let n8 = || <[u8; 8]>::try_from(bytes).map_err(|_| bad());
101 Ok(match ty {
102 TypeId::BOOL => match bytes {
103 [0] => Datum::Bool(false),
104 [1] => Datum::Bool(true),
105 _ => return Err(bad()),
106 },
107 TypeId::INT2 => Datum::Int2(i16::from_le_bytes(n2()?)),
108 TypeId::INT4 => Datum::Int4(i32::from_le_bytes(n4()?)),
109 TypeId::INT8 => Datum::Int8(i64::from_le_bytes(n8()?)),
110 TypeId::FLOAT4 => Datum::Float4(f32::from_le_bytes(n4()?)),
111 TypeId::FLOAT8 => Datum::Float8(f64::from_le_bytes(n8()?)),
112 TypeId::OID => Datum::Oid(Oid(u32::from_le_bytes(n4()?))),
113 TypeId::DATE => Datum::Date(i32::from_le_bytes(n4()?)),
114 TypeId::TIMESTAMP => Datum::Timestamp(i64::from_le_bytes(n8()?)),
115 TypeId::TIMESTAMPTZ => Datum::TimestampTz(i64::from_le_bytes(n8()?)),
116 TypeId::UUID => Datum::Uuid(bytes.try_into().map_err(|_| bad())?),
117 TypeId::TEXT => Datum::Text(String::from_utf8(bytes.to_vec()).map_err(|_| bad())?),
118 TypeId::BYTEA => Datum::Bytea(bytes.to_vec()),
119 _ => {
120 return Err(Error::internal(format!(
121 "the type with OID {} has no storage form at M1",
122 ty.0
123 )));
124 }
125 })
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use rupg_common::SqlState;
132
133 use super::*;
134
135 fn samples() -> Vec<Datum> {
136 vec![
137 Datum::Bool(false),
138 Datum::Bool(true),
139 Datum::Int2(i16::MIN),
140 Datum::Int4(-7),
141 Datum::Int8(i64::MAX),
142 Datum::Float4(-0.5),
143 Datum::Float8(f64::INFINITY),
144 Datum::Oid(Oid(16384)),
145 Datum::Text("héllo".into()),
146 Datum::Text(String::new()),
147 Datum::Bytea(vec![0, 255, 7]),
148 Datum::Uuid([9; 16]),
149 Datum::Date(i32::MIN),
150 Datum::Timestamp(-1),
151 Datum::TimestampTz(i64::MAX),
152 ]
153 }
154
155 #[test]
156 fn storage_forms() {
157 for d in samples() {
158 let ty = d.type_id().unwrap();
159 let bytes = d.encode().unwrap();
160 if let Some(w) = ty.width() {
161 assert_eq!(bytes.len(), usize::from(w), "{ty}");
162 }
163 assert_eq!(Datum::decode(ty, Some(&bytes)).unwrap(), d);
164 assert_eq!(TypeId::from_oid(ty.0), Some(ty));
165 assert_ne!(ty.name(), "unknown");
166 assert_ne!(ty.sql_name(), "unknown");
167 }
168 assert_eq!(Datum::Int4(0x0102_0304).encode().unwrap(), [4, 3, 2, 1]);
169 assert_eq!((Datum::Null.encode(), Datum::Null.type_id()), (None, None));
170 assert!(Datum::decode(TypeId::TEXT, None).unwrap().is_null());
171 let nan = Datum::decode(TypeId::FLOAT8, Some(&f64::NAN.to_le_bytes())).unwrap();
172 assert!(matches!(nan, Datum::Float8(v) if v.is_nan()));
173 assert_eq!(TypeId::from_oid(Oid(1700)), None);
174 assert_eq!((TypeId::INT4.sql_name(), TypeId::TEXT.sql_name()), ("integer", "text"));
175 }
176
177 #[test]
178 fn bad_storage_forms() {
179 let bad: [(TypeId, &[u8]); 5] = [
180 (TypeId::INT4, &[1, 2, 3]),
181 (TypeId::BOOL, &[2]),
182 (TypeId::UUID, &[0; 15]),
183 (TypeId::TEXT, &[0xff, 0xfe]),
184 (TypeId::INT8, &[]),
185 ];
186 for (ty, bytes) in bad {
187 assert_eq!(
188 Datum::decode(ty, Some(bytes)).unwrap_err().state(),
189 SqlState::DATA_CORRUPTED,
190 "{ty}"
191 );
192 }
193 let numeric = TypeId(Oid(1700));
194 assert_eq!(
195 Datum::decode(numeric, Some(&[0])).unwrap_err().state(),
196 SqlState::INTERNAL_ERROR
197 );
198 assert_eq!(numeric.name(), "unknown");
199 }
200}