xitca_postgres/
from_sql.rs

1use core::ops::Range;
2
3use xitca_io::bytes::Bytes;
4use xitca_unsafe_collection::bytes::BytesStr;
5
6use super::types::{FromSql, Type};
7
8pub type FromSqlError = Box<dyn std::error::Error + Sync + Send>;
9
10/// extension trait for [FromSql]
11///
12/// instead of working with explicit reference as `&[u8]` for parsing raw sql bytes this extension
13/// offers cheap copy/slicing of [Bytes] type for reference counting based zero copy parsing.
14///
15/// # Examples
16/// ```rust
17/// # use xitca_postgres::row::Row;
18/// use xitca_unsafe_collection::bytes::BytesStr; // a reference counted &str type has FromSqlExt impl
19/// fn parse_row(row: Row<'_>) {
20///     let s = row.get_zc::<BytesStr>(0); // parse index0 column with zero copy.
21///     println!("{}", s.as_str());
22/// }
23/// ```
24pub trait FromSqlExt<'a>: Sized {
25    /// [Type] represents the Postgres type hint which Self must be matching.
26    /// [Bytes] represents the reference of raw bytes of row data Self belongs to.
27    /// [Range] represents the start and end indexing into the raw data for correctly parsing Self.
28    /// When [Range] is an empty value it indicates trait implementor encounters a null pg value. It's
29    /// suggested to call [Range::is_empty] to check for this case and properly handle it
30    fn from_sql_nullable_ext(ty: &Type, col: (&Range<usize>, &'a Bytes)) -> Result<Self, FromSqlError>;
31
32    /// Determines if a value of this type can be created from the specified Postgres [Type].
33    fn accepts(ty: &Type) -> bool;
34}
35
36impl<'a> FromSqlExt<'a> for BytesStr {
37    fn from_sql_nullable_ext(ty: &Type, (range, buf): (&Range<usize>, &'a Bytes)) -> Result<Self, FromSqlError> {
38        // copy/paste from postgres-protocol dependency.
39        fn adjust_range(name: &str, range: &Range<usize>, buf: &Bytes) -> Result<(usize, usize), FromSqlError> {
40            if buf[range.start] == 1u8 {
41                Ok((range.start + 1, range.end))
42            } else {
43                Err(format!("only {name} version 1 supported").into())
44            }
45        }
46
47        if range.is_empty() {
48            return <&str as FromSql>::from_sql_null(ty)
49                .map(|_| unreachable!("<&str as FromSql>::from_sql_null should always yield Result::Err branch"));
50        }
51
52        let (start, end) = match ty.name() {
53            "ltree" => adjust_range("ltree", range, buf)?,
54            "lquery" => adjust_range("lquery", range, buf)?,
55            "ltxtquery" => adjust_range("ltxtquery", range, buf)?,
56            _ => (range.start, range.end),
57        };
58
59        BytesStr::try_from(buf.slice(start..end)).map_err(Into::into)
60    }
61
62    #[inline]
63    fn accepts(ty: &Type) -> bool {
64        <&str as FromSql>::accepts(ty)
65    }
66}
67
68impl<'a> FromSqlExt<'a> for Option<BytesStr> {
69    #[inline]
70    fn from_sql_nullable_ext(ty: &Type, col: (&Range<usize>, &'a Bytes)) -> Result<Self, FromSqlError> {
71        BytesStr::from_sql_nullable_ext(ty, col).map(Some)
72    }
73
74    #[inline]
75    fn accepts(ty: &Type) -> bool {
76        <BytesStr as FromSqlExt>::accepts(ty)
77    }
78}
79
80impl<'a> FromSqlExt<'a> for Bytes {
81    #[inline]
82    fn from_sql_nullable_ext(ty: &Type, (range, buf): (&Range<usize>, &'a Bytes)) -> Result<Self, FromSqlError> {
83        if range.is_empty() {
84            return <&[u8] as FromSql>::from_sql_null(ty)
85                .map(|_| unreachable!("<&[u8] as FromSql>::from_sql_null should always yield Result::Err branch"));
86        }
87
88        Ok(buf.slice(range.start..range.end))
89    }
90
91    #[inline]
92    fn accepts(ty: &Type) -> bool {
93        <&[u8] as FromSql>::accepts(ty)
94    }
95}
96
97impl<'a> FromSqlExt<'a> for Option<Bytes> {
98    #[inline]
99    fn from_sql_nullable_ext(ty: &Type, col: (&Range<usize>, &'a Bytes)) -> Result<Self, FromSqlError> {
100        Bytes::from_sql_nullable_ext(ty, col).map(Some)
101    }
102
103    #[inline]
104    fn accepts(ty: &Type) -> bool {
105        <Bytes as FromSqlExt>::accepts(ty)
106    }
107}