Skip to main content

postgresql/
decode.rs

1//! Decoding PostgreSQL binary values into Rust values.
2
3use 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
11/// A value that can be decoded from the PostgreSQL binary format.
12pub trait FromSql: Sized {
13    /// Decodes a non-null value of type `oid`.
14    ///
15    /// Returns an error if `buf` is too short or malformed for the type.
16    fn from_sql(oid: Oid, buf: &[u8]) -> Result<Self, Error>;
17
18    /// Decodes a non-null value, using `zone` to interpret zone-less temporal
19    /// values (`timestamp`, `date`, `time`).
20    ///
21    /// The default implementation ignores `zone`.
22    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    /// Decodes SQL `NULL`.
28    ///
29    /// The default implementation errors; [`Option<T>`] overrides it to return
30    /// `None`.
31    fn from_sql_null() -> Result<Self, Error> {
32        Err(Error::UnexpectedNull(String::new()))
33    }
34
35    /// Whether this type can be decoded from a column of type `oid`.
36    ///
37    /// Used to report a type mismatch ("column `x` is `int4`, cannot decode as
38    /// `bool`") instead of silently reinterpreting the bytes of another type.
39    /// The default accepts every type.
40    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
142/// Rejects PostgreSQL's `infinity` / `-infinity` timestamp sentinels.
143///
144/// PostgreSQL encodes them as the extreme `i64` values, which are not a real
145/// instant and cannot be represented as a `time::DateTime`. Reporting them as a
146/// clear error is better than silently decoding a nonsensical far-future date
147/// (or overflowing).
148fn 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
157/// Rejects PostgreSQL's `infinity` / `-infinity` date sentinels (extreme `i32`).
158fn 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                // PostgreSQL's `time` counts microseconds since midnight,
187                // `0..=86_400_000_000` (where the maximum is `24:00:00`). A
188                // value outside that range is not a time of day; rejecting it
189                // avoids decoding `24:00:00` as the next day's midnight and
190                // bounds a hostile value.
191                if !(0..86_400_000_000).contains(&micros) {
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(&micros) {
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            // Absolute instants: the configured zone is irrelevant.
217            TIMESTAMPTZOID | TIMETZOID => Self::from_sql(oid, buf),
218            // Zone-less values: interpret the stored wall clock in `zone`.
219            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
241/// Reinterprets the civil fields of `naive` (a UTC instant) as a wall clock in
242/// `zone`, returning the corresponding instant.
243fn 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
257/// Decodes a `json` / `jsonb` payload into any `serde` type.
258///
259/// Returns an error if `oid` is not a JSON type, if a `jsonb` payload is
260/// missing its version byte or carries an unsupported one, or if the payload
261/// is not valid JSON for `T`.
262pub 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
276/// Whether `oid` is a JSON type (`json` or `jsonb`).
277pub 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    // Every element costs at least its 4-byte length prefix, so a count that
395    // cannot fit in the payload is malformed (or hostile) and must not be used
396    // to size an allocation.
397    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    // The element OID comes from the payload and must be one the target type
404    // can actually hold; otherwise a server could have an `int4[]` column
405    // decode as, say, `text` with a different element type.
406    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        // Version 1 is the only supported `jsonb` wire format.
440        assert_eq!(
441            json_from_sql::<serde_json::Value>(JSONBOID, b"\x01{}").unwrap(),
442            serde_json::json!({})
443        );
444        // A different version must be rejected, not decoded.
445        assert!(json_from_sql::<serde_json::Value>(JSONBOID, b"\x02{}").is_err());
446        // A missing version byte is rejected too.
447        assert!(json_from_sql::<serde_json::Value>(JSONBOID, b"").is_err());
448        // `json` (not `jsonb`) has no version byte.
449        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        // 3 elements: [1, NULL, 3]
466        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        // 2^31-1 claimed elements in a 24-byte payload must not size a
491        // multi-gigabyte allocation.
492        let mut buf = Vec::new();
493        buf.extend_from_slice(&1i32.to_be_bytes()); // ndim
494        buf.extend_from_slice(&0i32.to_be_bytes()); // has nulls
495        buf.extend_from_slice(&23u32.to_be_bytes()); // element oid
496        buf.extend_from_slice(&i32::MAX.to_be_bytes()); // count
497        buf.extend_from_slice(&1i32.to_be_bytes()); // lower bound
498        let err = Vec::<Option<i32>>::from_sql(1007, &buf).unwrap_err();
499        assert!(matches!(err, Error::Decode(_)), "{err}");
500
501        // Negative counts are rejected rather than reinterpreted.
502        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()); // not NULL (-1), not a length
520        assert!(Vec::<Option<i32>>::from_sql(1007, &buf).is_err());
521    }
522
523    #[test]
524    fn short_array_headers_are_rejected() {
525        // Less than the 12-byte fixed header.
526        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        // 12..=19 bytes: a dimension count of 1 promises the 20-byte header.
533        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        // Exactly 12 zero bytes is a valid empty array (ndim = 0).
539        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        // An int4[] column whose payload declares the elements are `text`:
558        // the element OID comes from the payload and must be validated.
559        let mut buf = Vec::new();
560        buf.extend_from_slice(&1i32.to_be_bytes()); // ndim
561        buf.extend_from_slice(&0i32.to_be_bytes()); // has nulls
562        buf.extend_from_slice(&TEXTOID.to_be_bytes()); // element oid = text
563        buf.extend_from_slice(&1i32.to_be_bytes()); // count
564        buf.extend_from_slice(&1i32.to_be_bytes()); // lower bound
565        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        // An unknown element OID is rejected too, instead of being ignored.
571        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, &micros.to_be_bytes()).is_err());
586            assert!(time::DateTime::from_sql(TIMESTAMPOID, &micros.to_be_bytes()).is_err());
587            assert!(time::DateTime::from_sql_zoned(TIMESTAMPOID, &micros.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        // Midnight and the last representable time before 24:00 decode.
604        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        // 24:00:00 and hostile values are rejected, never panic.
607        for micros in [86_400_000_000i64, i64::MAX, i64::MIN, -1] {
608            assert!(
609                time::DateTime::from_sql(TIMEOID, &micros.to_be_bytes()).is_err(),
610                "time {micros} must be rejected"
611            );
612        }
613        // timetz: an in-day time decodes; hostile values error rather than panic.
614        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        // family 2 (IPv4) with a 16-byte address.
625        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        // family 2 with a zero-length address.
629        assert!(ipnetwork::IpNetwork::from_sql(INETOID, &[2u8, 8u8, 0u8, 0u8]).is_err());
630        // unknown address family.
631        assert!(ipnetwork::IpNetwork::from_sql(INETOID, &[9u8, 8u8, 0u8, 4u8, 1, 2, 3, 4]).is_err());
632        // truncated header.
633        assert!(ipnetwork::IpNetwork::from_sql(INETOID, &[2u8, 8u8, 0u8]).is_err());
634        // a valid IPv4 value still decodes.
635        assert!(ipnetwork::IpNetwork::from_sql(INETOID, &[2u8, 24u8, 1u8, 4u8, 10, 0, 0, 0]).is_ok());
636    }
637}