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
59        while let Some(range) = iter.next()? {
60            /*
61                when unwrapping the Range an empty value is used to represent null pg value offsets inside row's raw
62                data buffer.
63                when empty range is used to slice data collection through a safe Rust API(`<&[u8]>::get(Range<usize>)`
64                in this case) it always produce Option type where the None variant can be used as final output of null
65                pg value.
66                this saves 8 bytes per range storage
67            */
68            ranges_mut.push(range.unwrap_or(Range { start: 1, end: 0 }));
69        }
70
71        Ok(Self {
72            columns,
73            body,
74            ranges,
75            _marker: PhantomData,
76        })
77    }
78
79    /// Returns information about the columns of data in the row.
80    #[inline]
81    pub fn columns(&self) -> &[Column] {
82        self.columns.as_ref()
83    }
84
85    /// Determines if the row contains no values.
86    #[inline]
87    pub fn is_empty(&self) -> bool {
88        self.len() == 0
89    }
90
91    /// Returns the number of values in the row.
92    #[inline]
93    pub fn len(&self) -> usize {
94        self.columns().len()
95    }
96
97    // Get the raw bytes for the column at the given range.
98    fn col_buffer(&self, idx: usize) -> (&Range<usize>, &Bytes) {
99        (&self.ranges.as_ref()[idx], self.body.buffer_bytes())
100    }
101
102    fn get_idx_ty<T>(
103        &self,
104        idx: impl RowIndexAndType + fmt::Display,
105        ty_check: impl FnOnce(&Type) -> bool,
106    ) -> Result<(usize, &Type), Error> {
107        let (idx, ty) = idx
108            ._from_columns(self.columns.as_ref())
109            .ok_or_else(|| InvalidColumnIndex(idx.to_string()))?;
110
111        if !ty_check(ty) {
112            return Err(Error::from(WrongType::new::<T>(ty.clone())));
113        }
114
115        Ok((idx, ty))
116    }
117}
118
119impl<M> GenericRow<Arc<[Column]>, Vec<Range<usize>>, M> {
120    pub(crate) fn try_new_owned(columns: &Arc<[Column]>, body: DataRowBody) -> Result<Self, Error> {
121        GenericRow::try_new(columns.clone(), body, Vec::with_capacity(columns.len()))
122    }
123}
124
125impl<C, R> GenericRow<C, R, marker::Typed>
126where
127    C: AsRef<[Column]>,
128    R: AsRef<[Range<usize>]> + AsMut<Vec<Range<usize>>>,
129{
130    /// zero copy version of [`Self::get`]
131    ///
132    /// see [`FromSqlExt`] trait for explanation
133    pub fn get_zc<'s, T>(&'s self, idx: impl RowIndexAndType + fmt::Display) -> T
134    where
135        T: FromSqlExt<'s>,
136    {
137        self.try_get_zc(idx)
138            .unwrap_or_else(|e| panic!("error retrieving column {idx}: {e}"))
139    }
140
141    /// Like [`Self::get_zc`], but returns a `Result` rather than panicking.
142    pub fn try_get_zc<'s, T>(&'s self, idx: impl RowIndexAndType + fmt::Display) -> Result<T, Error>
143    where
144        T: FromSqlExt<'s>,
145    {
146        let (idx, ty) = self.get_idx_ty::<T>(idx, T::accepts_ext)?;
147        FromSqlExt::from_sql_nullable_ext(ty, self.col_buffer(idx)).map_err(Into::into)
148    }
149
150    /// Deserializes a value from the row.
151    ///
152    /// The value can be specified either by its numeric index in the row, or by its column name.
153    ///
154    /// # Panics
155    ///
156    /// Panics if the index is out of bounds or if the value cannot be converted to the specified type.
157    pub fn get<'s, T>(&'s self, idx: impl RowIndexAndType + fmt::Display) -> T
158    where
159        T: FromSql<'s>,
160    {
161        self.try_get(idx)
162            .unwrap_or_else(|e| panic!("error retrieving column {idx}: {e}"))
163    }
164
165    /// Like [`Self::get`], but returns a `Result` rather than panicking.
166    pub fn try_get<'s, T>(&'s self, idx: impl RowIndexAndType + fmt::Display) -> Result<T, Error>
167    where
168        T: FromSql<'s>,
169    {
170        let (idx, ty) = self.get_idx_ty::<T>(idx, T::accepts)?;
171        FromSql::from_sql_nullable(ty, self.body.buffer().get(self.ranges.as_ref()[idx].clone())).map_err(Into::into)
172    }
173}
174
175impl<C, R> GenericRow<C, R, marker::NoTyped>
176where
177    C: AsRef<[Column]>,
178    R: AsRef<[Range<usize>]> + AsMut<Vec<Range<usize>>>,
179{
180    /// zero copy version of [`Self::get`]
181    ///
182    /// see [`FromSqlExt`] trait for explanation
183    pub fn get_zc(&self, idx: impl RowIndexAndType + fmt::Display) -> Option<BytesStr> {
184        self.try_get_zc(idx)
185            .unwrap_or_else(|e| panic!("error retrieving column {idx}: {e}"))
186    }
187
188    /// Like [`Self::get_zc`], but returns a `Result` rather than panicking.
189    pub fn try_get_zc(&self, idx: impl RowIndexAndType + fmt::Display) -> Result<Option<BytesStr>, Error> {
190        let (idx, ty) = self.get_idx_ty::<BytesStr>(idx, BytesStr::accepts_ext)?;
191        FromSqlExt::from_sql_nullable_ext(ty, self.col_buffer(idx)).map_err(Into::into)
192    }
193
194    /// Returns a value from the row.
195    ///
196    /// The value can be specified either by its numeric index in the row, or by its column name.
197    ///
198    /// # Panics
199    ///
200    /// Panics if the index is out of bounds or if the value cannot be converted to the specified type.
201    pub fn get(&self, idx: impl RowIndexAndType + fmt::Display) -> Option<&str> {
202        self.try_get(idx)
203            .unwrap_or_else(|e| panic!("error retrieving column {idx}: {e}"))
204    }
205
206    /// Like `RowSimple::get`, but returns a `Result` rather than panicking.
207    pub fn try_get(&self, idx: impl RowIndexAndType + fmt::Display) -> Result<Option<&str>, Error> {
208        let (idx, ty) = self.get_idx_ty::<&str>(idx, <&str>::accepts)?;
209        FromSql::from_sql_nullable(ty, self.body.buffer().get(self.ranges.as_ref()[idx].clone())).map_err(Into::into)
210    }
211}
212
213fn _try_get(row: Row) {
214    let _ = row.try_get::<u32>(0);
215    let _ = row.try_get_zc::<BytesStr>("test");
216    let _ = row.try_get_zc::<Bytes>(String::from("get_raw").as_str());
217}
218
219fn _try_get_simple(row: RowSimple) {
220    let _ = row.try_get_zc(0);
221    let _ = row.get_zc("test");
222    let _ = row.try_get(String::from("get_raw").as_str());
223}