1use postgresql_protocol::oid::{
4 BOOLOID, BPCHAROID, BYTEAOID, CIDROID, DATEOID, FLOAT4OID, FLOAT8OID, INETOID, INT2OID, INT4OID, INT8OID,
5 INTERVALOID, JSONBOID, JSONOID, NAMEOID, Oid, TEXTOID, TIMEOID, TIMESTAMPOID, TIMESTAMPTZOID, TIMETZOID, UUIDOID,
6 VARCHAROID, array_element,
7};
8
9use crate::{encode::PG_EPOCH_UNIX, error::Error, interval::Interval, json::Json};
10
11pub trait FromSql: Sized {
13 fn from_sql(oid: Oid, buf: &[u8]) -> Result<Self, Error>;
17
18 fn from_sql_zoned(oid: Oid, buf: &[u8], zone: time::TimeZone) -> Result<Self, Error> {
23 let _ = zone;
24 Self::from_sql(oid, buf)
25 }
26
27 fn from_sql_null() -> Result<Self, Error> {
32 Err(Error::UnexpectedNull(String::new()))
33 }
34
35 fn accepts(oid: Oid) -> bool {
41 let _ = oid;
42 true
43 }
44}
45
46fn need(buf: &[u8], n: usize, what: &str) -> Result<(), Error> {
47 if buf.len() < n {
48 Err(Error::Decode(format!("{what}: buffer of {} bytes is too short", buf.len())))
49 } else {
50 Ok(())
51 }
52}
53
54macro_rules! impl_int {
55 ($ty:ty, $n:expr, $name:expr, $oid:expr) => {
56 impl FromSql for $ty {
57 fn from_sql(_oid: Oid, buf: &[u8]) -> Result<Self, Error> {
58 need(buf, $n, $name)?;
59 let arr: [u8; $n] = buf[..$n].try_into().unwrap();
60 Ok(<$ty>::from_be_bytes(arr))
61 }
62
63 fn accepts(oid: Oid) -> bool {
64 oid == $oid
65 }
66 }
67 };
68}
69
70impl_int!(i16, 2, "int2", INT2OID);
71impl_int!(i32, 4, "int4", INT4OID);
72impl_int!(i64, 8, "int8", INT8OID);
73
74impl FromSql for f32 {
75 fn from_sql(_oid: Oid, buf: &[u8]) -> Result<Self, Error> {
76 need(buf, 4, "float4")?;
77 Ok(f32::from_be_bytes(buf[..4].try_into().unwrap()))
78 }
79
80 fn accepts(oid: Oid) -> bool {
81 oid == FLOAT4OID
82 }
83}
84
85impl FromSql for f64 {
86 fn from_sql(_oid: Oid, buf: &[u8]) -> Result<Self, Error> {
87 need(buf, 8, "float8")?;
88 Ok(f64::from_be_bytes(buf[..8].try_into().unwrap()))
89 }
90
91 fn accepts(oid: Oid) -> bool {
92 oid == FLOAT8OID
93 }
94}
95
96impl FromSql for bool {
97 fn from_sql(_oid: Oid, buf: &[u8]) -> Result<Self, Error> {
98 need(buf, 1, "bool")?;
99 Ok(buf[0] != 0)
100 }
101
102 fn accepts(oid: Oid) -> bool {
103 oid == BOOLOID
104 }
105}
106
107impl FromSql for String {
108 fn from_sql(_oid: Oid, buf: &[u8]) -> Result<Self, Error> {
109 Ok(std::str::from_utf8(buf)?.to_owned())
110 }
111
112 fn accepts(oid: Oid) -> bool {
113 matches!(oid, TEXTOID | VARCHAROID | BPCHAROID | NAMEOID)
114 }
115}
116
117impl FromSql for Vec<u8> {
118 fn from_sql(_oid: Oid, buf: &[u8]) -> Result<Self, Error> {
119 Ok(buf.to_vec())
120 }
121
122 fn accepts(oid: Oid) -> bool {
123 oid == BYTEAOID
124 }
125}
126
127impl FromSql for uuid::Uuid {
128 fn from_sql(_oid: Oid, buf: &[u8]) -> Result<Self, Error> {
129 uuid::Uuid::from_slice(buf).map_err(Error::Uuid)
130 }
131
132 fn accepts(oid: Oid) -> bool {
133 oid == UUIDOID
134 }
135}
136
137fn from_pg_micros(micros: i64) -> Result<time::DateTime, Error> {
138 let nanos = (PG_EPOCH_UNIX as i128) * 1_000_000_000 + (micros as i128) * 1_000;
139 Ok(time::DateTime::from_unix_nanos(nanos)?)
140}
141
142fn reject_timestamp_infinity(micros: i64) -> Result<(), Error> {
149 if micros == i64::MAX || micros == i64::MIN {
150 return Err(Error::Decode(
151 "PostgreSQL `infinity` / `-infinity` timestamps are not supported".into(),
152 ));
153 }
154 Ok(())
155}
156
157fn reject_date_infinity(days: i32) -> Result<(), Error> {
159 if days == i32::MAX || days == i32::MIN {
160 return Err(Error::Decode(
161 "PostgreSQL `infinity` / `-infinity` dates are not supported".into(),
162 ));
163 }
164 Ok(())
165}
166
167impl FromSql for time::DateTime {
168 fn from_sql(oid: Oid, buf: &[u8]) -> Result<Self, Error> {
169 match oid {
170 TIMESTAMPTZOID | TIMESTAMPOID => {
171 need(buf, 8, "timestamp")?;
172 let micros = i64::from_be_bytes(buf[..8].try_into().unwrap());
173 reject_timestamp_infinity(micros)?;
174 from_pg_micros(micros)
175 }
176 DATEOID => {
177 need(buf, 4, "date")?;
178 let days = i32::from_be_bytes(buf[..4].try_into().unwrap());
179 reject_date_infinity(days)?;
180 let unix = PG_EPOCH_UNIX + days as i64 * 86_400;
181 Ok(time::DateTime::from_unix(unix)?)
182 }
183 TIMEOID => {
184 need(buf, 8, "time")?;
185 let micros = i64::from_be_bytes(buf[..8].try_into().unwrap());
186 if !(0..86_400_000_000).contains(µs) {
192 return Err(Error::Decode(format!("time value {micros} is outside a single day")));
193 }
194 Ok(time::DateTime::from_unix_nanos((micros as i128) * 1_000)?)
195 }
196 TIMETZOID => {
197 need(buf, 12, "timetz")?;
198 let micros = i64::from_be_bytes(buf[..8].try_into().unwrap());
199 let offset = i32::from_be_bytes(buf[8..12].try_into().unwrap());
200 if !(0..86_400_000_000).contains(µs) {
201 return Err(Error::Decode(format!("timetz value {micros} is outside a single day")));
202 }
203 let nanos = (micros as i128 - (offset as i128) * 1_000_000) * 1_000;
204 Ok(time::DateTime::from_unix_nanos(nanos)?)
205 }
206 other => Err(Error::Decode(format!("cannot decode OID {other} as DateTime"))),
207 }
208 }
209
210 fn accepts(oid: Oid) -> bool {
211 matches!(oid, TIMESTAMPTZOID | TIMESTAMPOID | DATEOID | TIMEOID | TIMETZOID)
212 }
213
214 fn from_sql_zoned(oid: Oid, buf: &[u8], zone: time::TimeZone) -> Result<Self, Error> {
215 match oid {
216 TIMESTAMPTZOID | TIMETZOID => Self::from_sql(oid, buf),
218 TIMESTAMPOID => {
220 need(buf, 8, "timestamp")?;
221 let micros = i64::from_be_bytes(buf[..8].try_into().unwrap());
222 reject_timestamp_infinity(micros)?;
223 civil_in_zone(from_pg_micros(micros)?, zone)
224 }
225 DATEOID => {
226 need(buf, 4, "date")?;
227 let days = i32::from_be_bytes(buf[..4].try_into().unwrap());
228 reject_date_infinity(days)?;
229 civil_in_zone(time::DateTime::from_unix(PG_EPOCH_UNIX + days as i64 * 86_400)?, zone)
230 }
231 TIMEOID => {
232 need(buf, 8, "time")?;
233 let micros = i64::from_be_bytes(buf[..8].try_into().unwrap());
234 civil_in_zone(time::DateTime::from_unix_nanos((micros as i128) * 1_000)?, zone)
235 }
236 other => Err(Error::Decode(format!("cannot decode OID {other} as DateTime"))),
237 }
238 }
239}
240
241fn civil_in_zone(naive: time::DateTime, zone: time::TimeZone) -> Result<time::DateTime, Error> {
244 Ok(time::DateTime::from_parts_with(
245 naive.year(),
246 naive.month(),
247 naive.day(),
248 naive.hour(),
249 naive.minute(),
250 naive.second(),
251 naive.nanosecond(),
252 zone,
253 time::Disambiguation::Compatible,
254 )?)
255}
256
257pub fn json_from_sql<T: serde::de::DeserializeOwned>(oid: Oid, buf: &[u8]) -> Result<T, Error> {
263 match oid {
264 JSONBOID => {
265 need(buf, 1, "jsonb")?;
266 if buf[0] != 1 {
267 return Err(Error::Decode(format!("unsupported jsonb format version {}", buf[0])));
268 }
269 Ok(serde_json::from_slice(&buf[1..])?)
270 }
271 JSONOID => Ok(serde_json::from_slice(buf)?),
272 other => Err(Error::Decode(format!("cannot decode OID {other} as JSON"))),
273 }
274}
275
276pub fn json_accepts(oid: Oid) -> bool {
278 matches!(oid, JSONOID | JSONBOID)
279}
280
281impl FromSql for serde_json::Value {
282 fn from_sql(oid: Oid, buf: &[u8]) -> Result<Self, Error> {
283 json_from_sql(oid, buf)
284 }
285
286 fn accepts(oid: Oid) -> bool {
287 json_accepts(oid)
288 }
289}
290
291impl<T: serde::de::DeserializeOwned> FromSql for Json<T> {
292 fn from_sql(oid: Oid, buf: &[u8]) -> Result<Self, Error> {
293 json_from_sql(oid, buf).map(Json)
294 }
295
296 fn accepts(oid: Oid) -> bool {
297 json_accepts(oid)
298 }
299}
300
301impl FromSql for ipnetwork::IpNetwork {
302 fn from_sql(_oid: Oid, buf: &[u8]) -> Result<Self, Error> {
303 need(buf, 4, "inet")?;
304 let family = buf[0];
305 let prefix = buf[1];
306 let len = buf[3] as usize;
307 need(buf, 4 + len, "inet")?;
308 let addr = &buf[4..4 + len];
309 let network = match family {
310 2 => {
311 let octets: [u8; 4] = addr
312 .try_into()
313 .map_err(|_| Error::Decode("invalid IPv4 length".into()))?;
314 ipnetwork::IpNetwork::new(std::net::IpAddr::from(octets), prefix)?
315 }
316 3 => {
317 let octets: [u8; 16] = addr
318 .try_into()
319 .map_err(|_| Error::Decode("invalid IPv6 length".into()))?;
320 ipnetwork::IpNetwork::new(std::net::IpAddr::from(octets), prefix)?
321 }
322 other => return Err(Error::Decode(format!("unknown inet family {other}"))),
323 };
324 Ok(network)
325 }
326
327 fn accepts(oid: Oid) -> bool {
328 matches!(oid, INETOID | CIDROID)
329 }
330}
331
332impl FromSql for Interval {
333 fn from_sql(_oid: Oid, buf: &[u8]) -> Result<Self, Error> {
334 need(buf, 16, "interval")?;
335 Ok(Interval {
336 micros: i64::from_be_bytes(buf[0..8].try_into().unwrap()),
337 days: i32::from_be_bytes(buf[8..12].try_into().unwrap()),
338 months: i32::from_be_bytes(buf[12..16].try_into().unwrap()),
339 })
340 }
341
342 fn accepts(oid: Oid) -> bool {
343 oid == INTERVALOID
344 }
345}
346
347impl<T: FromSql> FromSql for Option<T> {
348 fn from_sql(oid: Oid, buf: &[u8]) -> Result<Self, Error> {
349 T::from_sql(oid, buf).map(Some)
350 }
351
352 fn from_sql_zoned(oid: Oid, buf: &[u8], zone: time::TimeZone) -> Result<Self, Error> {
353 T::from_sql_zoned(oid, buf, zone).map(Some)
354 }
355
356 fn from_sql_null() -> Result<Self, Error> {
357 Ok(None)
358 }
359
360 fn accepts(oid: Oid) -> bool {
361 T::accepts(oid)
362 }
363}
364
365impl<T: FromSql> FromSql for Vec<T> {
366 fn from_sql(_oid: Oid, buf: &[u8]) -> Result<Self, Error> {
367 decode_array(buf, time::TimeZone::UTC)
368 }
369
370 fn from_sql_zoned(_oid: Oid, buf: &[u8], zone: time::TimeZone) -> Result<Self, Error> {
371 decode_array(buf, zone)
372 }
373
374 fn accepts(oid: Oid) -> bool {
375 array_element(oid).is_some_and(|elem| T::accepts(elem.oid))
376 }
377}
378
379fn decode_array<T: FromSql>(buf: &[u8], zone: time::TimeZone) -> Result<Vec<T>, Error> {
380 need(buf, 12, "array")?;
381 let ndim = i32::from_be_bytes(buf[0..4].try_into().unwrap());
382 let element_oid = u32::from_be_bytes(buf[8..12].try_into().unwrap());
383
384 if ndim == 0 {
385 return Ok(Vec::new());
386 }
387 if ndim != 1 {
388 return Err(Error::Decode("only one-dimensional arrays are supported".into()));
389 }
390 need(buf, 20, "array")?;
391
392 let raw_count = i32::from_be_bytes(buf[12..16].try_into().unwrap());
393 let count = usize::try_from(raw_count).map_err(|_| Error::Decode("array has a negative element count".into()))?;
394 if count > (buf.len() - 20) / 4 {
398 return Err(Error::Decode(format!(
399 "array claims {count} elements but the payload holds at most {}",
400 (buf.len() - 20) / 4
401 )));
402 }
403 if !T::accepts(element_oid) {
407 return Err(Error::Decode(format!(
408 "array element type OID {element_oid} cannot be decoded as the requested type"
409 )));
410 }
411
412 let mut pos = 20;
413 let mut out = Vec::with_capacity(count);
414 for _ in 0..count {
415 need(&buf[pos..], 4, "array element")?;
416 let raw_len = i32::from_be_bytes(buf[pos..pos + 4].try_into().unwrap());
417 pos += 4;
418 if raw_len == -1 {
419 out.push(T::from_sql_null()?);
420 } else {
421 let len = usize::try_from(raw_len)
422 .map_err(|_| Error::Decode(format!("array element has an invalid length {raw_len}")))?;
423 need(&buf[pos..], len, "array element")?;
424 out.push(T::from_sql_zoned(element_oid, &buf[pos..pos + len], zone)?);
425 pos += len;
426 }
427 }
428 Ok(out)
429}
430
431#[cfg(test)]
432mod tests {
433 use postgresql_protocol::oid::{INT4_ARRAY_OID, INT8_ARRAY_OID, JSONBOID, JSONOID, TEXT_ARRAY_OID};
434
435 use super::*;
436
437 #[test]
438 fn jsonb_version_byte_is_validated() {
439 assert_eq!(
441 json_from_sql::<serde_json::Value>(JSONBOID, b"\x01{}").unwrap(),
442 serde_json::json!({})
443 );
444 assert!(json_from_sql::<serde_json::Value>(JSONBOID, b"\x02{}").is_err());
446 assert!(json_from_sql::<serde_json::Value>(JSONBOID, b"").is_err());
448 assert_eq!(
450 json_from_sql::<serde_json::Value>(JSONOID, b"{}").unwrap(),
451 serde_json::json!({})
452 );
453 }
454
455 #[test]
456 fn decode_basic() {
457 assert_eq!(i32::from_sql(23, &42i32.to_be_bytes()).unwrap(), 42);
458 assert!(bool::from_sql(16, &[1]).unwrap());
459 assert_eq!(String::from_sql(25, b"hi").unwrap(), "hi");
460 assert_eq!(Option::<i32>::from_sql_null().unwrap(), None);
461 }
462
463 #[test]
464 fn decode_array() {
465 let mut buf = Vec::new();
467 buf.extend_from_slice(&1i32.to_be_bytes());
468 buf.extend_from_slice(&1i32.to_be_bytes());
469 buf.extend_from_slice(&23u32.to_be_bytes());
470 buf.extend_from_slice(&3i32.to_be_bytes());
471 buf.extend_from_slice(&1i32.to_be_bytes());
472 buf.extend_from_slice(&4i32.to_be_bytes());
473 buf.extend_from_slice(&1i32.to_be_bytes());
474 buf.extend_from_slice(&(-1i32).to_be_bytes());
475 buf.extend_from_slice(&4i32.to_be_bytes());
476 buf.extend_from_slice(&3i32.to_be_bytes());
477
478 assert_eq!(
479 Vec::<i32>::from_sql(1007, &buf)
480 .unwrap_err()
481 .to_string()
482 .contains("NULL"),
483 true
484 );
485 assert_eq!(Vec::<Option<i32>>::from_sql(1007, &buf).unwrap(), vec![Some(1), None, Some(3)]);
486 }
487
488 #[test]
489 fn array_with_impossible_count_is_rejected() {
490 let mut buf = Vec::new();
493 buf.extend_from_slice(&1i32.to_be_bytes()); buf.extend_from_slice(&0i32.to_be_bytes()); buf.extend_from_slice(&23u32.to_be_bytes()); buf.extend_from_slice(&i32::MAX.to_be_bytes()); buf.extend_from_slice(&1i32.to_be_bytes()); let err = Vec::<Option<i32>>::from_sql(1007, &buf).unwrap_err();
499 assert!(matches!(err, Error::Decode(_)), "{err}");
500
501 let mut buf = Vec::new();
503 buf.extend_from_slice(&1i32.to_be_bytes());
504 buf.extend_from_slice(&0i32.to_be_bytes());
505 buf.extend_from_slice(&23u32.to_be_bytes());
506 buf.extend_from_slice(&(-1i32).to_be_bytes());
507 buf.extend_from_slice(&1i32.to_be_bytes());
508 assert!(Vec::<Option<i32>>::from_sql(1007, &buf).is_err());
509 }
510
511 #[test]
512 fn array_with_invalid_element_length_is_rejected() {
513 let mut buf = Vec::new();
514 buf.extend_from_slice(&1i32.to_be_bytes());
515 buf.extend_from_slice(&0i32.to_be_bytes());
516 buf.extend_from_slice(&23u32.to_be_bytes());
517 buf.extend_from_slice(&1i32.to_be_bytes());
518 buf.extend_from_slice(&1i32.to_be_bytes());
519 buf.extend_from_slice(&(-2i32).to_be_bytes()); assert!(Vec::<Option<i32>>::from_sql(1007, &buf).is_err());
521 }
522
523 #[test]
524 fn short_array_headers_are_rejected() {
525 for len in 0..12 {
527 assert!(
528 Vec::<Option<i32>>::from_sql(1007, &vec![0u8; len]).is_err(),
529 "len {len} must be rejected"
530 );
531 }
532 for len in 12..20 {
534 let mut buf = vec![0u8; len];
535 buf[0..4].copy_from_slice(&1i32.to_be_bytes());
536 assert!(Vec::<Option<i32>>::from_sql(1007, &buf).is_err(), "len {len} must be rejected");
537 }
538 assert!(Vec::<Option<i32>>::from_sql(1007, &vec![0u8; 12]).unwrap().is_empty());
540 }
541
542 #[test]
543 fn type_mismatches_are_rejected_before_decoding() {
544 assert!(i32::accepts(INT4OID));
545 assert!(!i32::accepts(INT8OID));
546 assert!(!bool::accepts(INT4OID));
547 assert!(String::accepts(VARCHAROID));
548 assert!(!String::accepts(JSONOID));
549 assert!(time::DateTime::accepts(TIMESTAMPOID));
550 assert!(Vec::<i32>::accepts(INT4_ARRAY_OID));
551 assert!(!Vec::<i32>::accepts(INT8_ARRAY_OID));
552 assert!(Vec::<Option<String>>::accepts(TEXT_ARRAY_OID));
553 }
554
555 #[test]
556 fn array_element_oid_must_match_the_target_type() {
557 let mut buf = Vec::new();
560 buf.extend_from_slice(&1i32.to_be_bytes()); buf.extend_from_slice(&0i32.to_be_bytes()); buf.extend_from_slice(&TEXTOID.to_be_bytes()); buf.extend_from_slice(&1i32.to_be_bytes()); buf.extend_from_slice(&1i32.to_be_bytes()); buf.extend_from_slice(&4i32.to_be_bytes());
566 buf.extend_from_slice(&42i32.to_be_bytes());
567 let err = Vec::<i32>::from_sql(INT4_ARRAY_OID, &buf).unwrap_err();
568 assert!(matches!(err, Error::Decode(_)), "{err}");
569
570 let mut buf = Vec::new();
572 buf.extend_from_slice(&1i32.to_be_bytes());
573 buf.extend_from_slice(&0i32.to_be_bytes());
574 buf.extend_from_slice(&999_999u32.to_be_bytes());
575 buf.extend_from_slice(&1i32.to_be_bytes());
576 buf.extend_from_slice(&1i32.to_be_bytes());
577 buf.extend_from_slice(&2i32.to_be_bytes());
578 buf.extend_from_slice(b"hi");
579 assert!(Vec::<String>::from_sql(TEXT_ARRAY_OID, &buf).is_err());
580 }
581
582 #[test]
583 fn infinity_timestamps_and_dates_are_rejected() {
584 for micros in [i64::MAX, i64::MIN] {
585 assert!(time::DateTime::from_sql(TIMESTAMPTZOID, µs.to_be_bytes()).is_err());
586 assert!(time::DateTime::from_sql(TIMESTAMPOID, µs.to_be_bytes()).is_err());
587 assert!(time::DateTime::from_sql_zoned(TIMESTAMPOID, µs.to_be_bytes(), time::TimeZone::UTC).is_err());
588 }
589 for days in [i32::MAX, i32::MIN] {
590 assert!(time::DateTime::from_sql(DATEOID, &days.to_be_bytes()).is_err());
591 assert!(time::DateTime::from_sql_zoned(DATEOID, &days.to_be_bytes(), time::TimeZone::UTC).is_err());
592 }
593 }
594
595 #[test]
596 fn decode_timestamptz() {
597 let dt = time::DateTime::from_unix(PG_EPOCH_UNIX + 5).unwrap();
598 assert_eq!(time::DateTime::from_sql(1184, &5_000_000i64.to_be_bytes()).unwrap(), dt);
599 }
600
601 #[test]
602 fn time_values_are_range_checked() {
603 assert!(time::DateTime::from_sql(TIMEOID, &0i64.to_be_bytes()).is_ok());
605 assert!(time::DateTime::from_sql(TIMEOID, &86_399_999_999i64.to_be_bytes()).is_ok());
606 for micros in [86_400_000_000i64, i64::MAX, i64::MIN, -1] {
608 assert!(
609 time::DateTime::from_sql(TIMEOID, µs.to_be_bytes()).is_err(),
610 "time {micros} must be rejected"
611 );
612 }
613 let mut buf = 5_000_000i64.to_be_bytes().to_vec();
615 buf.extend_from_slice(&3_600i32.to_be_bytes());
616 assert!(time::DateTime::from_sql(TIMETZOID, &buf).is_ok());
617 let mut buf = i64::MAX.to_be_bytes().to_vec();
618 buf.extend_from_slice(&i32::MAX.to_be_bytes());
619 assert!(time::DateTime::from_sql(TIMETZOID, &buf).is_err());
620 }
621
622 #[test]
623 fn inet_mismatched_family_and_lengths_are_rejected() {
624 let mut buf = vec![2u8, 32u8, 0u8, 16u8];
626 buf.extend_from_slice(&[0u8; 16]);
627 assert!(ipnetwork::IpNetwork::from_sql(INETOID, &buf).is_err());
628 assert!(ipnetwork::IpNetwork::from_sql(INETOID, &[2u8, 8u8, 0u8, 0u8]).is_err());
630 assert!(ipnetwork::IpNetwork::from_sql(INETOID, &[9u8, 8u8, 0u8, 4u8, 1, 2, 3, 4]).is_err());
632 assert!(ipnetwork::IpNetwork::from_sql(INETOID, &[2u8, 8u8, 0u8]).is_err());
634 assert!(ipnetwork::IpNetwork::from_sql(INETOID, &[2u8, 24u8, 1u8, 4u8, 10, 0, 0, 0]).is_ok());
636 }
637}