elephantry/
from_sql.rs

1use byteorder::ReadBytesExt;
2
3#[inline]
4pub fn not_null<T>(raw: Option<T>) -> crate::Result<T> {
5    raw.ok_or(crate::Error::NotNull)
6}
7
8macro_rules! read {
9    ($fn:ident, $ty:ty) => {
10        #[inline]
11        #[allow(dead_code)]
12        pub fn $fn(buf: &mut &[u8]) -> crate::Result<$ty> {
13            let n = buf.$fn::<byteorder::BigEndian>()?;
14
15            Ok(n)
16        }
17    };
18}
19
20read!(read_i16, i16);
21read!(read_i32, i32);
22read!(read_i64, i64);
23read!(read_f32, f32);
24read!(read_f64, f64);
25read!(read_u32, u32);
26read!(read_u128, u128);
27
28#[inline]
29#[allow(dead_code)]
30pub fn read_i8(buf: &mut &[u8]) -> crate::Result<i8> {
31    let n = buf.read_i8()?;
32
33    Ok(n)
34}
35
36#[inline]
37pub fn read_u8(buf: &mut &[u8]) -> crate::Result<u8> {
38    let n = buf.read_u8()?;
39
40    Ok(n)
41}
42
43macro_rules! number {
44    ($type:ty, $read:ident) => {
45        impl FromSql for $type {
46            fn from_binary(ty: &crate::pq::Type, raw: Option<&[u8]>) -> crate::Result<Self> {
47                let mut buf = crate::from_sql::not_null(raw)?;
48                let v = $read(&mut buf)?;
49
50                if !buf.is_empty() {
51                    return Err(Self::error(ty, raw));
52                }
53
54                Ok(v)
55            }
56
57            fn from_text(ty: &crate::pq::Type, raw: Option<&str>) -> crate::Result<Self> {
58                crate::from_sql::not_null(raw)?
59                    .parse()
60                    .map_err(|_| Self::error(ty, raw))
61            }
62        }
63    };
64}
65
66/**
67 * Trait to allow a rust type to be translated form a SQL value.
68 */
69pub trait FromSql: Sized {
70    /**
71     * Create a new struct from the binary representation.
72     *
73     * See the postgresql
74     * [adt](https://github.com/postgres/postgres/tree/REL_12_0/src/backend/utils/adt)
75     * module source code, mainly `*_recv` functions.
76     */
77    fn from_binary(ty: &crate::pq::Type, raw: Option<&[u8]>) -> crate::Result<Self>;
78
79    /**
80     * Create a new struct from the text representation.
81     *
82     * See the postgresql
83     * [adt](https://github.com/postgres/postgres/tree/REL_12_0/src/backend/utils/adt)
84     * module source code, mainly `*_in` functions.
85     */
86    fn from_text(ty: &crate::pq::Type, raw: Option<&str>) -> crate::Result<Self>;
87
88    /**
89     * Create a new struct from SQL value.
90     */
91    fn from_sql(
92        ty: &crate::pq::Type,
93        format: crate::pq::Format,
94        raw: Option<&[u8]>,
95    ) -> crate::Result<Self> {
96        match format {
97            crate::pq::Format::Binary => Self::from_binary(ty, raw),
98            crate::pq::Format::Text => {
99                let text = raw.map(|x| String::from_utf8(x.to_vec())).transpose()?;
100
101                Self::from_text(ty, text.as_deref())
102            }
103        }
104    }
105
106    fn error<T: std::fmt::Debug>(pg_type: &crate::pq::Type, raw: T) -> crate::Error {
107        crate::Error::FromSql {
108            pg_type: pg_type.clone(),
109            rust_type: std::any::type_name::<Self>().to_string(),
110            value: format!("{raw:?}"),
111        }
112    }
113}
114
115number!(f32, read_f32);
116number!(f64, read_f64);
117number!(i16, read_i16);
118number!(i32, read_i32);
119number!(i64, read_i64);
120
121impl FromSql for u16 {
122    fn from_text(ty: &crate::pq::Type, raw: Option<&str>) -> crate::Result<Self> {
123        i32::from_text(ty, raw).map(|x| x as u16)
124    }
125
126    fn from_binary(ty: &crate::pq::Type, raw: Option<&[u8]>) -> crate::Result<Self> {
127        let raw = raw.map(|x| {
128            let mut vec = vec![0; 4 - x.len()];
129            vec.extend_from_slice(x);
130            vec
131        });
132
133        i32::from_binary(ty, raw.as_deref()).map(|x| x as u16)
134    }
135}
136
137impl FromSql for u32 {
138    fn from_text(ty: &crate::pq::Type, raw: Option<&str>) -> crate::Result<Self> {
139        i64::from_text(ty, raw).map(|x| x as u32)
140    }
141
142    fn from_binary(ty: &crate::pq::Type, raw: Option<&[u8]>) -> crate::Result<Self> {
143        let raw = raw.map(|x| {
144            let mut vec = vec![0; 8 - x.len()];
145            vec.extend_from_slice(x);
146            vec
147        });
148
149        i64::from_binary(ty, raw.as_deref()).map(|x| x as u32)
150    }
151}
152
153impl FromSql for usize {
154    fn from_text(ty: &crate::pq::Type, raw: Option<&str>) -> crate::Result<Self> {
155        not_null(raw)?.parse().map_err(|_| Self::error(ty, raw))
156    }
157
158    fn from_binary(ty: &crate::pq::Type, raw: Option<&[u8]>) -> crate::Result<Self> {
159        let mut buf = not_null(raw)?;
160        #[cfg(target_pointer_width = "64")]
161        let v = buf.read_u64::<byteorder::BigEndian>()?;
162        #[cfg(target_pointer_width = "32")]
163        let v = buf.read_u32::<byteorder::BigEndian>()?;
164
165        if !buf.is_empty() {
166            return Err(Self::error(ty, raw));
167        }
168
169        Ok(v as usize)
170    }
171}
172
173impl FromSql for bool {
174    fn from_text(_: &crate::pq::Type, raw: Option<&str>) -> crate::Result<Self> {
175        Ok(not_null(raw)? == "t")
176    }
177
178    fn from_binary(ty: &crate::pq::Type, raw: Option<&[u8]>) -> crate::Result<Self> {
179        let buf = not_null(raw)?;
180        if buf.len() != 1 {
181            return Err(Self::error(ty, raw));
182        }
183
184        Ok(not_null(raw)?[0] != 0)
185    }
186}
187
188impl<T: FromSql> FromSql for Option<T> {
189    fn from_text(ty: &crate::pq::Type, raw: Option<&str>) -> crate::Result<Self> {
190        match raw {
191            Some(_) => Ok(Some(T::from_text(ty, raw)?)),
192            None => Ok(None),
193        }
194    }
195
196    fn from_binary(ty: &crate::pq::Type, raw: Option<&[u8]>) -> crate::Result<Self> {
197        match raw {
198            Some(_) => Ok(Some(T::from_binary(ty, raw)?)),
199            None => Ok(None),
200        }
201    }
202}
203
204impl FromSql for char {
205    fn from_text(ty: &crate::pq::Type, raw: Option<&str>) -> crate::Result<Self> {
206        not_null(raw)?
207            .chars()
208            .next()
209            .ok_or_else(|| Self::error(ty, raw))
210    }
211
212    fn from_binary(ty: &crate::pq::Type, raw: Option<&[u8]>) -> crate::Result<Self> {
213        let c = String::from_binary(ty, raw)?;
214
215        c.chars().next().ok_or_else(|| Self::error(ty, raw))
216    }
217}
218
219impl FromSql for String {
220    fn from_text(_: &crate::pq::Type, raw: Option<&str>) -> crate::Result<Self> {
221        Ok(not_null(raw)?.to_string())
222    }
223
224    fn from_binary(_: &crate::pq::Type, raw: Option<&[u8]>) -> crate::Result<Self> {
225        String::from_utf8(not_null(raw)?.to_vec()).map_err(Into::into)
226    }
227}
228
229impl FromSql for () {
230    fn from_text(_: &crate::pq::Type, _: Option<&str>) -> crate::Result<Self> {
231        Ok(())
232    }
233
234    fn from_binary(_: &crate::pq::Type, _: Option<&[u8]>) -> crate::Result<Self> {
235        Ok(())
236    }
237}
238
239#[cfg(test)]
240mod test {
241    crate::sql_test!(float4, f32, [("1.", 1.), ("-1.", -1.), ("2.1", 2.1)]);
242
243    crate::sql_test!(float8, f64, [("1.", 1.), ("-1.", -1.), ("2.1", 2.1)]);
244
245    crate::sql_test!(
246        int2,
247        i16,
248        [
249            (i16::MAX.to_string().as_str(), i16::MAX),
250            ("1", 1),
251            ("0", 0),
252            ("-1", -1),
253        ]
254    );
255
256    crate::sql_test!(
257        int,
258        u16,
259        [
260            (u16::MAX.to_string().as_str(), u16::MAX),
261            ("1", 1),
262            ("0", 0)
263        ]
264    );
265
266    crate::sql_test!(
267        int4,
268        i32,
269        [
270            (i32::MAX.to_string().as_str(), i32::MAX),
271            ("1", 1),
272            ("0", 0),
273            ("-1", -1),
274        ]
275    );
276
277    crate::sql_test!(
278        bigint,
279        u32,
280        [
281            (u32::MAX.to_string().as_str(), u32::MAX),
282            ("1", 1),
283            ("0", 0)
284        ]
285    );
286
287    crate::sql_test!(
288        int8,
289        i64,
290        [
291            (i64::MAX.to_string().as_str(), i64::MAX),
292            ("1", 1),
293            ("0", 0),
294            ("-1", -1),
295        ]
296    );
297
298    crate::sql_test!(oid, crate::pq::Oid, [("1", 1)]);
299
300    crate::sql_test!(
301        bool,
302        bool,
303        [
304            ("'t'", true),
305            ("'f'", false),
306            ("true", true),
307            ("false", false),
308        ]
309    );
310
311    crate::sql_test!(char, char, [("'f'", 'f'), ("'('", '(')]);
312
313    crate::sql_test!(varchar, Option<String>, [("null", None::<String>)]);
314
315    crate::sql_test!(
316        text,
317        String,
318        [("'foo'", "foo".to_string()), ("''", "".to_string())]
319    );
320
321    crate::sql_test!(us_postal_code, String, [("'12345'", "12345".to_string()),]);
322
323    crate::sql_test!(unknown, (), [("null", ())]);
324
325    #[derive(elephantry_derive::Enum, Debug, PartialEq)]
326    enum Mood {
327        Sad,
328        Ok,
329        Happy,
330    }
331
332    crate::sql_test!(
333        mood,
334        super::Mood,
335        [
336            ("'Sad'", super::Mood::Sad),
337            ("'Ok'", super::Mood::Ok),
338            ("'Happy'", super::Mood::Happy),
339        ]
340    );
341
342    #[derive(elephantry_derive::Composite, Debug, PartialEq)]
343    struct CompFoo {
344        f1: i32,
345        f2: String,
346    }
347
348    impl crate::entity::Simple for CompFoo {}
349
350    crate::sql_test!(
351        compfoo,
352        super::CompFoo,
353        [(
354            "'(1,foo)'",
355            super::CompFoo {
356                f1: 1,
357                f2: "foo".to_string()
358            }
359        )]
360    );
361}