xitca_postgres/row/
types.rs

1use core::{fmt, marker::PhantomData, ops::Range};
2
3use std::sync::Arc;
4
5use fallible_iterator::FallibleIterator;
6use postgres_protocol::message::backend::DataRowBody;
7use postgres_types::FromSql;
8use xitca_io::bytes::{Bytes, BytesStr};
9
10use crate::{
11    column::Column,
12    error::{Error, InvalidColumnIndex, WrongType},
13    from_sql::FromSqlExt,
14    types::Type,
15};
16
17use super::{marker, traits::RowIndexAndType};
18
19/// A row of data returned from the database by a query.
20pub type Row<'r> = GenericRow<&'r [Column], &'r mut Vec<Range<usize>>, marker::Typed>;
21
22/// A row of data returned from the database by a simple query.
23pub type RowSimple<'r> = GenericRow<&'r [Column], &'r mut Vec<Range<usize>>, marker::NoTyped>;
24
25/// [`Row`] with static lifetime bound
26pub type RowOwned = GenericRow<Arc<[Column]>, Vec<Range<usize>>, marker::Typed>;
27
28/// [`RowSimple`] with static lifetime bound
29pub type RowSimpleOwned = GenericRow<Arc<[Column]>, Vec<Range<usize>>, marker::NoTyped>;
30
31pub struct GenericRow<C, R, M> {
32    columns: C,
33    body: DataRowBody,
34    ranges: R,
35    _marker: PhantomData<M>,
36}
37
38impl<C, R, M> fmt::Debug for GenericRow<C, R, M>
39where
40    C: AsRef<[Column]>,
41{
42    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43        f.debug_struct("Row").field("columns", &self.columns.as_ref()).finish()
44    }
45}
46
47impl<C, R, M> GenericRow<C, R, M>
48where
49    C: AsRef<[Column]>,
50    R: AsRef<[Range<usize>]> + AsMut<Vec<Range<usize>>>,
51{
52    pub(crate) fn try_new(columns: C, body: DataRowBody, mut ranges: R) -> Result<Self, Error> {
53        let mut iter = body.ranges();
54
55        let ranges_mut = ranges.as_mut();
56
57        ranges_mut.clear();
58        ranges_mut.reserve(iter.size_hint().0);
59
60        while let Some(range) = iter.next()? {
61            /*
62                when unwrapping the Range an empty value is used to represent null pg value offsets inside row's raw
63                data buffer.
64                when empty range is used to slice data collection through a safe Rust API(`<&[u8]>::get(Range<usize>)`
65                in this case) it always produce Option type where the None variant can be used as final output of null
66                pg value.
67                this saves 8 bytes per range storage
68            */
69            ranges_mut.push(range.unwrap_or(Range { start: 1, end: 0 }));
70        }
71
72        Ok(Self {
73            columns,
74            body,
75            ranges,
76            _marker: PhantomData,
77        })
78    }
79
80    /// Returns information about the columns of data in the row.
81    #[inline]
82    pub fn columns(&self) -> &[Column] {
83        self.columns.as_ref()
84    }
85
86    /// Determines if the row contains no values.
87    #[inline]
88    pub fn is_empty(&self) -> bool {
89        self.len() == 0
90    }
91
92    /// Returns the number of values in the row.
93    #[inline]
94    pub fn len(&self) -> usize {
95        self.columns().len()
96    }
97
98    // Get the raw bytes for the column at the given range.
99    fn col_buffer(&self, idx: usize) -> (&Range<usize>, &Bytes) {
100        (&self.ranges.as_ref()[idx], self.body.buffer_bytes())
101    }
102
103    fn get_idx_ty<T>(
104        &self,
105        idx: impl RowIndexAndType + fmt::Display,
106        ty_check: impl FnOnce(&Type) -> bool,
107    ) -> Result<(usize, &Type), Error> {
108        let (idx, ty) = idx
109            ._from_columns(self.columns.as_ref())
110            .ok_or_else(|| InvalidColumnIndex(idx.to_string()))?;
111
112        if !ty_check(ty) {
113            return Err(Error::from(WrongType::new::<T>(ty.clone())));
114        }
115
116        Ok((idx, ty))
117    }
118}
119
120impl<C, R> GenericRow<C, R, marker::Typed>
121where
122    C: AsRef<[Column]>,
123    R: AsRef<[Range<usize>]> + AsMut<Vec<Range<usize>>>,
124{
125    /// zero copy version of [`Self::get`]
126    ///
127    /// see [`FromSqlExt`] trait for explanation
128    pub fn get_zc<'s, T>(&'s self, idx: impl RowIndexAndType + fmt::Display) -> T
129    where
130        T: FromSqlExt<'s>,
131    {
132        self.try_get_zc(idx)
133            .unwrap_or_else(|e| panic!("error retrieving column {idx}: {e}"))
134    }
135
136    /// Like [`Self::get_zc`], but returns a `Result` rather than panicking.
137    pub fn try_get_zc<'s, T>(&'s self, idx: impl RowIndexAndType + fmt::Display) -> Result<T, Error>
138    where
139        T: FromSqlExt<'s>,
140    {
141        let (idx, ty) = self.get_idx_ty::<T>(idx, T::accepts)?;
142        FromSqlExt::from_sql_nullable_ext(ty, self.col_buffer(idx)).map_err(Into::into)
143    }
144
145    /// Deserializes a value from the row.
146    ///
147    /// The value can be specified either by its numeric index in the row, or by its column name.
148    ///
149    /// # Panics
150    ///
151    /// Panics if the index is out of bounds or if the value cannot be converted to the specified type.
152    pub fn get<'s, T>(&'s self, idx: impl RowIndexAndType + fmt::Display) -> T
153    where
154        T: FromSql<'s>,
155    {
156        self.try_get(idx)
157            .unwrap_or_else(|e| panic!("error retrieving column {idx}: {e}"))
158    }
159
160    /// Like [`Self::get`], but returns a `Result` rather than panicking.
161    pub fn try_get<'s, T>(&'s self, idx: impl RowIndexAndType + fmt::Display) -> Result<T, Error>
162    where
163        T: FromSql<'s>,
164    {
165        let (idx, ty) = self.get_idx_ty::<T>(idx, T::accepts)?;
166        FromSql::from_sql_nullable(ty, self.body.buffer().get(self.ranges.as_ref()[idx].clone())).map_err(Into::into)
167    }
168}
169
170impl<C, R> GenericRow<C, R, marker::NoTyped>
171where
172    C: AsRef<[Column]>,
173    R: AsRef<[Range<usize>]> + AsMut<Vec<Range<usize>>>,
174{
175    /// zero copy version of [`Self::get`]
176    ///
177    /// see [`FromSqlExt`] trait for explanation
178    pub fn get_zc(&self, idx: impl RowIndexAndType + fmt::Display) -> Option<BytesStr> {
179        self.try_get_zc(idx)
180            .unwrap_or_else(|e| panic!("error retrieving column {idx}: {e}"))
181    }
182
183    /// Like [`Self::get_zc`], but returns a `Result` rather than panicking.
184    pub fn try_get_zc(&self, idx: impl RowIndexAndType + fmt::Display) -> Result<Option<BytesStr>, Error> {
185        let (idx, ty) = self.get_idx_ty::<BytesStr>(idx, BytesStr::accepts)?;
186        FromSqlExt::from_sql_nullable_ext(ty, self.col_buffer(idx)).map_err(Into::into)
187    }
188
189    /// Returns a value from the row.
190    ///
191    /// The value can be specified either by its numeric index in the row, or by its column name.
192    ///
193    /// # Panics
194    ///
195    /// Panics if the index is out of bounds or if the value cannot be converted to the specified type.
196    pub fn get(&self, idx: impl RowIndexAndType + fmt::Display) -> Option<&str> {
197        self.try_get(idx)
198            .unwrap_or_else(|e| panic!("error retrieving column {idx}: {e}"))
199    }
200
201    /// Like `RowSimple::get`, but returns a `Result` rather than panicking.
202    pub fn try_get(&self, idx: impl RowIndexAndType + fmt::Display) -> Result<Option<&str>, Error> {
203        let (idx, ty) = self.get_idx_ty::<&str>(idx, <&str as FromSql>::accepts)?;
204        FromSql::from_sql_nullable(ty, self.body.buffer().get(self.ranges.as_ref()[idx].clone())).map_err(Into::into)
205    }
206}
207
208fn _try_get(row: Row) {
209    let _ = row.try_get::<u32>(0);
210    let _ = row.try_get_zc::<BytesStr>("test");
211    let _ = row.try_get_zc::<Bytes>(String::from("get_raw").as_str());
212}
213
214fn _try_get_simple(row: RowSimple) {
215    let _ = row.try_get_zc(0);
216    let _ = row.get_zc("test");
217    let _ = row.try_get(String::from("get_raw").as_str());
218}