Skip to main content

pg/
decode.rs

1use crate::error::{PgError, Result};
2
3pub trait FromSql: Sized {
4    fn from_sql(type_oid: u32, buf: &[u8]) -> Result<Self>;
5}
6
7impl FromSql for i16 {
8    fn from_sql(_type_oid: u32, buf: &[u8]) -> Result<Self> {
9        if buf.len() < 2 {
10            return Err(PgError::Decode("i16: buffer too short".into()));
11        }
12        Ok(i16::from_be_bytes([buf[0], buf[1]]))
13    }
14}
15
16impl FromSql for i32 {
17    fn from_sql(_type_oid: u32, buf: &[u8]) -> Result<Self> {
18        if buf.len() < 4 {
19            return Err(PgError::Decode("i32: buffer too short".into()));
20        }
21        Ok(i32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]))
22    }
23}
24
25impl FromSql for i64 {
26    fn from_sql(_type_oid: u32, buf: &[u8]) -> Result<Self> {
27        if buf.len() < 8 {
28            return Err(PgError::Decode("i64: buffer too short".into()));
29        }
30        let arr: [u8; 8] = buf[..8].try_into().unwrap();
31        Ok(i64::from_be_bytes(arr))
32    }
33}
34
35impl FromSql for f32 {
36    fn from_sql(_type_oid: u32, buf: &[u8]) -> Result<Self> {
37        if buf.len() < 4 {
38            return Err(PgError::Decode("f32: buffer too short".into()));
39        }
40        let arr: [u8; 4] = buf[..4].try_into().unwrap();
41        Ok(f32::from_be_bytes(arr))
42    }
43}
44
45impl FromSql for f64 {
46    fn from_sql(_type_oid: u32, buf: &[u8]) -> Result<Self> {
47        if buf.len() < 8 {
48            return Err(PgError::Decode("f64: buffer too short".into()));
49        }
50        let arr: [u8; 8] = buf[..8].try_into().unwrap();
51        Ok(f64::from_be_bytes(arr))
52    }
53}
54
55impl FromSql for bool {
56    fn from_sql(_type_oid: u32, buf: &[u8]) -> Result<Self> {
57        if buf.is_empty() {
58            return Err(PgError::Decode("bool: empty buffer".into()));
59        }
60        Ok(buf[0] != 0)
61    }
62}
63
64impl FromSql for String {
65    fn from_sql(_type_oid: u32, buf: &[u8]) -> Result<Self> {
66        String::from_utf8(buf.to_vec()).map_err(|e| PgError::Decode(format!("string: invalid utf-8: {}", e)))
67    }
68}
69
70impl FromSql for Vec<u8> {
71    fn from_sql(_type_oid: u32, buf: &[u8]) -> Result<Self> {
72        Ok(buf.to_vec())
73    }
74}
75
76impl FromSql for uuid::Uuid {
77    fn from_sql(_type_oid: u32, buf: &[u8]) -> Result<Self> {
78        uuid::Uuid::from_slice(buf).map_err(|e| PgError::Decode(format!("uuid: {}", e)))
79    }
80}
81
82impl FromSql for chrono::DateTime<chrono::Utc> {
83    fn from_sql(_type_oid: u32, buf: &[u8]) -> Result<Self> {
84        if buf.len() < 8 {
85            return Err(PgError::Decode("timestamptz: buffer too short".into()));
86        }
87        let arr: [u8; 8] = buf[..8].try_into().unwrap();
88        let micros = i64::from_be_bytes(arr);
89        let pg_epoch = chrono::NaiveDate::from_ymd_opt(2000, 1, 1)
90            .and_then(|d| d.and_hms_opt(0, 0, 0))
91            .map(|d| d.and_utc())
92            .unwrap();
93        let duration = chrono::TimeDelta::microseconds(micros);
94        Ok(pg_epoch + duration)
95    }
96}
97
98impl<T: FromSql> FromSql for Option<T> {
99    fn from_sql(type_oid: u32, buf: &[u8]) -> Result<Self> {
100        if buf.is_empty() {
101            return Ok(None);
102        }
103        T::from_sql(type_oid, buf).map(Some)
104    }
105}
106
107impl<T: FromSql> FromSql for Vec<T> {
108    fn from_sql(_type_oid: u32, buf: &[u8]) -> Result<Self> {
109        if buf.len() < 12 {
110            return Err(PgError::Decode("array: buffer too short".into()));
111        }
112        let _num_dims = i32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
113        let _has_nulls = i32::from_be_bytes([buf[4], buf[5], buf[6], buf[7]]);
114        let _elem_oid = u32::from_be_bytes([buf[8], buf[9], buf[10], buf[11]]);
115
116        let dim_count = i32::from_be_bytes([buf[12], buf[13], buf[14], buf[15]]);
117        let _dim_lbound = i32::from_be_bytes([buf[16], buf[17], buf[18], buf[19]]);
118
119        let mut offset = 20usize;
120        let mut result = Vec::with_capacity(dim_count as usize);
121
122        for _ in 0..dim_count {
123            if offset + 4 > buf.len() {
124                return Err(PgError::Decode("array: invalid element offset".into()));
125            }
126            let elem_len = i32::from_be_bytes([buf[offset], buf[offset + 1], buf[offset + 2], buf[offset + 3]]);
127            offset += 4;
128            if elem_len == -1 {
129                continue;
130            }
131            if offset + elem_len as usize > buf.len() {
132                return Err(PgError::Decode("array: element length exceeds buffer".into()));
133            }
134            let elem = T::from_sql(_elem_oid, &buf[offset..offset + elem_len as usize])?;
135            result.push(elem);
136            offset += elem_len as usize;
137        }
138
139        Ok(result)
140    }
141}