1use crate::runtime::query_request::ParamValue;
13use reddb_types::Value;
14
15#[allow(dead_code)]
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum PgOid {
19 Bool = 16,
20 Bytea = 17,
21 Int8 = 20,
22 Int2 = 21,
23 Int4 = 23,
24 Text = 25,
25 Oid = 26,
26 Json = 114,
27 Float4 = 700,
28 Float8 = 701,
29 Unknown = 705,
30 Varchar = 1043,
31 Date = 1082,
32 Time = 1083,
33 Timestamp = 1114,
34 TimestampTz = 1184,
35 Numeric = 1700,
36 Uuid = 2950,
37 Jsonb = 3802,
38 Vector = 38000,
42}
43
44impl PgOid {
45 pub fn as_u32(self) -> u32 {
46 self as u32
47 }
48
49 pub fn from_u32(oid: u32) -> Self {
50 match oid {
51 16 => PgOid::Bool,
52 17 => PgOid::Bytea,
53 20 => PgOid::Int8,
54 21 => PgOid::Int2,
55 23 => PgOid::Int4,
56 25 => PgOid::Text,
57 26 => PgOid::Oid,
58 114 => PgOid::Json,
59 700 => PgOid::Float4,
60 701 => PgOid::Float8,
61 705 => PgOid::Unknown,
62 1043 => PgOid::Varchar,
63 1082 => PgOid::Date,
64 1083 => PgOid::Time,
65 1114 => PgOid::Timestamp,
66 1184 => PgOid::TimestampTz,
67 1700 => PgOid::Numeric,
68 2950 => PgOid::Uuid,
69 3802 => PgOid::Jsonb,
70 38000 => PgOid::Vector,
71 _ => PgOid::Unknown,
72 }
73 }
74
75 pub fn from_value(value: &Value) -> Self {
78 match value {
79 Value::Null => PgOid::Text,
80 Value::Boolean(_) => PgOid::Bool,
81 Value::Integer(_) => PgOid::Int8,
82 Value::UnsignedInteger(_) => PgOid::Int8,
83 Value::BigInt(_) => PgOid::Int8,
84 Value::Float(_) => PgOid::Float8,
85 Value::Text(_) => PgOid::Text,
86 Value::Blob(_) => PgOid::Bytea,
87 Value::Json(_) => PgOid::Jsonb,
88 Value::Uuid(_) => PgOid::Uuid,
89 Value::Date(_) => PgOid::Date,
90 Value::Timestamp(_) => PgOid::TimestampTz,
91 Value::TimestampMs(_) => PgOid::TimestampTz,
92 Value::Vector(_) => PgOid::Vector,
93 _ => PgOid::Text,
95 }
96 }
97}
98
99pub fn pg_param_to_param_value(
100 oid: u32,
101 format_code: i16,
102 bytes: Option<&[u8]>,
103) -> Result<ParamValue, String> {
104 let Some(bytes) = bytes else {
105 return Ok(ParamValue::Null);
106 };
107 match format_code {
108 0 => pg_text_param_to_value(PgOid::from_u32(oid), bytes),
109 1 => pg_binary_param_to_value(PgOid::from_u32(oid), bytes),
110 other => Err(format!("unsupported PG parameter format code {other}")),
111 }
112}
113
114fn pg_text_param_to_value(oid: PgOid, bytes: &[u8]) -> Result<ParamValue, String> {
115 let text = std::str::from_utf8(bytes).map_err(|e| format!("invalid UTF-8 parameter: {e}"))?;
116 match oid {
117 PgOid::Bool => match text.to_ascii_lowercase().as_str() {
118 "t" | "true" | "1" | "yes" | "on" => Ok(ParamValue::Bool(true)),
119 "f" | "false" | "0" | "no" | "off" => Ok(ParamValue::Bool(false)),
120 _ => Err(format!("invalid bool parameter {text:?}")),
121 },
122 PgOid::Int2 | PgOid::Int4 | PgOid::Int8 | PgOid::Oid => text
123 .parse::<i64>()
124 .map(ParamValue::Int64)
125 .map_err(|e| format!("invalid integer parameter {text:?}: {e}")),
126 PgOid::Float4 | PgOid::Float8 | PgOid::Numeric => text
127 .parse::<f64>()
128 .map(ParamValue::Float64)
129 .map_err(|e| format!("invalid float parameter {text:?}: {e}")),
130 PgOid::Bytea => parse_bytea_text(text).map(ParamValue::Bytes),
131 PgOid::Json | PgOid::Jsonb => Ok(ParamValue::Json(bytes.to_vec())),
132 PgOid::Timestamp | PgOid::TimestampTz => text
133 .parse::<i64>()
134 .map(ParamValue::Timestamp)
135 .or_else(|_| Ok(ParamValue::Text(text.to_string()))),
136 PgOid::Uuid => parse_uuid_text(text).map(ParamValue::Uuid),
137 PgOid::Vector => parse_vector_text(text).map(ParamValue::Vector),
138 PgOid::Text | PgOid::Varchar | PgOid::Unknown | PgOid::Date | PgOid::Time => {
139 Ok(ParamValue::Text(text.to_string()))
140 }
141 }
142}
143
144fn pg_binary_param_to_value(oid: PgOid, bytes: &[u8]) -> Result<ParamValue, String> {
145 match oid {
146 PgOid::Bool if bytes.len() == 1 => Ok(ParamValue::Bool(bytes[0] != 0)),
147 PgOid::Int2 if bytes.len() == 2 => {
148 Ok(ParamValue::Int64(
149 i16::from_be_bytes([bytes[0], bytes[1]]) as i64
150 ))
151 }
152 PgOid::Int4 | PgOid::Oid if bytes.len() == 4 => {
153 Ok(ParamValue::Int64(
154 i32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as i64,
155 ))
156 }
157 PgOid::Int8 if bytes.len() == 8 => Ok(ParamValue::Int64(i64::from_be_bytes([
158 bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
159 ]))),
160 PgOid::Float4 if bytes.len() == 4 => {
161 Ok(ParamValue::Float64(
162 f32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as f64,
163 ))
164 }
165 PgOid::Float8 if bytes.len() == 8 => Ok(ParamValue::Float64(f64::from_be_bytes([
166 bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
167 ]))),
168 PgOid::Text | PgOid::Varchar => std::str::from_utf8(bytes)
169 .map(|text| ParamValue::Text(text.to_owned()))
170 .map_err(|_| "invalid UTF-8 binary text parameter".to_string()),
171 PgOid::Bytea => Ok(ParamValue::Bytes(bytes.to_vec())),
172 PgOid::Json | PgOid::Jsonb => Ok(ParamValue::Json(bytes.to_vec())),
173 PgOid::Uuid if bytes.len() == 16 => {
174 let mut out = [0u8; 16];
175 out.copy_from_slice(bytes);
176 Ok(ParamValue::Uuid(out))
177 }
178 PgOid::Timestamp | PgOid::TimestampTz if bytes.len() == 8 => Ok(ParamValue::Timestamp(
179 pg_microseconds_to_unix_seconds(i64::from_be_bytes([
180 bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
181 ])),
182 )),
183 PgOid::Vector => parse_vector_binary(bytes).map(ParamValue::Vector),
184 _ => Err(format!(
185 "unsupported binary parameter for OID {} with {} bytes",
186 oid.as_u32(),
187 bytes.len()
188 )),
189 }
190}
191
192fn parse_bytea_text(text: &str) -> Result<Vec<u8>, String> {
193 let Some(hex) = text.strip_prefix("\\x") else {
194 return Ok(text.as_bytes().to_vec());
195 };
196 if hex.len() % 2 != 0 {
197 return Err("invalid bytea hex length".to_string());
198 }
199 (0..hex.len())
200 .step_by(2)
201 .map(|idx| {
202 u8::from_str_radix(&hex[idx..idx + 2], 16)
203 .map_err(|e| format!("invalid bytea hex: {e}"))
204 })
205 .collect()
206}
207
208fn parse_vector_text(text: &str) -> Result<Vec<f32>, String> {
209 let parsed: crate::json::Value =
210 crate::json::from_str(text).map_err(|e| format!("invalid vector parameter: {e}"))?;
211 let crate::json::Value::Array(items) = parsed else {
212 return Err("invalid vector parameter: expected JSON number array".to_string());
213 };
214 items
215 .iter()
216 .map(|item| {
217 item.as_f64().map(|value| value as f32).ok_or_else(|| {
218 "invalid vector parameter: array must contain only numbers".to_string()
219 })
220 })
221 .collect()
222}
223
224fn parse_vector_binary(bytes: &[u8]) -> Result<Vec<f32>, String> {
225 if bytes.len() < 4 {
226 return Err("invalid binary vector parameter: payload too short".to_string());
227 }
228 let dims = i16::from_be_bytes([bytes[0], bytes[1]]);
229 if dims < 0 {
230 return Err("invalid binary vector parameter: negative dimension".to_string());
231 }
232 let dims = dims as usize;
233 let expected = 4 + dims * 4;
234 if bytes.len() != expected {
235 return Err(format!(
236 "invalid binary vector parameter: expected {expected} bytes, got {}",
237 bytes.len()
238 ));
239 }
240 (0..dims)
241 .map(|idx| {
242 let off = 4 + idx * 4;
243 Ok(f32::from_be_bytes([
244 bytes[off],
245 bytes[off + 1],
246 bytes[off + 2],
247 bytes[off + 3],
248 ]))
249 })
250 .collect()
251}
252
253fn parse_uuid_text(text: &str) -> Result<[u8; 16], String> {
254 let compact = text.replace('-', "");
255 if compact.len() != 32 {
256 return Err(format!("invalid uuid parameter {text:?}"));
257 }
258 let bytes = (0..compact.len())
259 .step_by(2)
260 .map(|idx| {
261 u8::from_str_radix(&compact[idx..idx + 2], 16)
262 .map_err(|e| format!("invalid uuid parameter {text:?}: {e}"))
263 })
264 .collect::<Result<Vec<_>, _>>()?;
265 let mut out = [0u8; 16];
266 out.copy_from_slice(&bytes);
267 Ok(out)
268}
269
270fn pg_microseconds_to_unix_seconds(pg_micros: i64) -> i64 {
271 const PG_UNIX_EPOCH_OFFSET_SECONDS: i64 = 946_684_800;
273 PG_UNIX_EPOCH_OFFSET_SECONDS + pg_micros / 1_000_000
274}
275
276pub fn value_to_pg_wire_bytes(value: &Value) -> Option<Vec<u8>> {
283 match value {
284 Value::Null => None,
285 Value::Boolean(b) => Some((if *b { "t" } else { "f" }).as_bytes().to_vec()),
286 Value::Integer(n) => Some(n.to_string().into_bytes()),
287 Value::UnsignedInteger(n) => Some(n.to_string().into_bytes()),
288 Value::BigInt(n) => Some(n.to_string().into_bytes()),
289 Value::Float(f) => Some(f.to_string().into_bytes()),
290 Value::Text(s) => Some(s.as_bytes().to_vec()),
291 Value::Blob(b) => {
292 let mut out = Vec::with_capacity(2 + b.len() * 2);
294 out.extend_from_slice(b"\\x");
295 for byte in b {
296 out.extend_from_slice(format!("{byte:02x}").as_bytes());
297 }
298 Some(out)
299 }
300 Value::Json(bytes) => Some(bytes.clone()),
301 other => Some(other.to_string().into_bytes()),
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311
312 #[test]
313 fn pg_text_params_decode_common_oids() {
314 assert_eq!(
315 pg_param_to_param_value(PgOid::Bool.as_u32(), 0, Some(b"t")).unwrap(),
316 ParamValue::Bool(true)
317 );
318 assert_eq!(
319 pg_param_to_param_value(PgOid::Int4.as_u32(), 0, Some(b"42")).unwrap(),
320 ParamValue::Int64(42)
321 );
322 assert_eq!(
323 pg_param_to_param_value(PgOid::Float8.as_u32(), 0, Some(b"1.5")).unwrap(),
324 ParamValue::Float64(1.5)
325 );
326 assert_eq!(
327 pg_param_to_param_value(PgOid::Bytea.as_u32(), 0, Some(br"\xdeadbeef")).unwrap(),
328 ParamValue::Bytes(vec![0xde, 0xad, 0xbe, 0xef])
329 );
330 assert_eq!(
331 pg_param_to_param_value(PgOid::Jsonb.as_u32(), 0, Some(br#"{"a":1}"#)).unwrap(),
332 ParamValue::Json(br#"{"a":1}"#.to_vec())
333 );
334 assert_eq!(
335 pg_param_to_param_value(
336 PgOid::Uuid.as_u32(),
337 0,
338 Some(b"00112233-4455-6677-8899-aabbccddeeff")
339 )
340 .unwrap(),
341 ParamValue::Uuid([
342 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
343 0xee, 0xff,
344 ])
345 );
346 }
347
348 #[test]
349 fn pg_binary_params_decode_numeric_and_uuid_oids() {
350 assert_eq!(
351 pg_param_to_param_value(PgOid::Int2.as_u32(), 1, Some(&7i16.to_be_bytes())).unwrap(),
352 ParamValue::Int64(7)
353 );
354 assert_eq!(
355 pg_param_to_param_value(PgOid::Int8.as_u32(), 1, Some(&42i64.to_be_bytes())).unwrap(),
356 ParamValue::Int64(42)
357 );
358 assert_eq!(
359 pg_param_to_param_value(PgOid::Float4.as_u32(), 1, Some(&1.5f32.to_be_bytes()))
360 .unwrap(),
361 ParamValue::Float64(1.5)
362 );
363 assert_eq!(
364 pg_param_to_param_value(PgOid::Uuid.as_u32(), 1, Some(&[0x11; 16])).unwrap(),
365 ParamValue::Uuid([0x11; 16])
366 );
367 let mut vector = Vec::new();
368 vector.extend_from_slice(&2i16.to_be_bytes());
369 vector.extend_from_slice(&0i16.to_be_bytes());
370 vector.extend_from_slice(&1.0f32.to_be_bytes());
371 vector.extend_from_slice(&(-0.5f32).to_be_bytes());
372 assert_eq!(
373 pg_param_to_param_value(PgOid::Vector.as_u32(), 1, Some(&vector)).unwrap(),
374 ParamValue::Vector(vec![1.0, -0.5])
375 );
376 }
377
378 #[test]
379 fn pg_binary_text_parameters_validate_utf8() {
380 for oid in [PgOid::Text, PgOid::Varchar] {
381 assert_eq!(
382 pg_param_to_param_value(oid.as_u32(), 1, Some("olá".as_bytes())).unwrap(),
383 ParamValue::Text("olá".to_string())
384 );
385 assert!(pg_param_to_param_value(oid.as_u32(), 1, Some(&[0xff])).is_err());
386 }
387 }
388
389 #[test]
390 fn pg_null_param_decodes_to_value_null() {
391 assert_eq!(
392 pg_param_to_param_value(PgOid::Text.as_u32(), 0, None).unwrap(),
393 ParamValue::Null
394 );
395 }
396
397 #[test]
398 fn pg_vector_text_param_decodes_json_array() {
399 assert_eq!(
400 pg_param_to_param_value(PgOid::Vector.as_u32(), 0, Some(b"[1.0, -0.5]")).unwrap(),
401 ParamValue::Vector(vec![1.0, -0.5])
402 );
403 }
404}