Skip to main content

postgresql/
row.rs

1//! Result rows and the [`FromRow`] trait.
2
3use std::sync::Arc;
4
5use bytes::Bytes;
6use postgresql_protocol::{backend::FieldDescription, oid::Oid};
7
8use crate::{decode::FromSql, error::Error, json::Json};
9
10/// Metadata for one result column.
11#[derive(Debug, Clone)]
12pub struct Column {
13    /// Column name.
14    pub name: String,
15    /// Column type OID.
16    pub type_oid: Oid,
17    /// Type size in bytes, or negative for variable length.
18    pub type_size: i16,
19    /// Type modifier.
20    pub type_mod: i32,
21    /// Source table OID, or `0`.
22    pub table_oid: u32,
23    /// Source attribute number, or `0`.
24    pub column_attr: i16,
25}
26
27/// The column metadata shared by every row of one result set.
28#[derive(Debug)]
29pub struct Columns {
30    columns: Vec<Column>,
31}
32
33impl Columns {
34    /// Builds shared column metadata from protocol field descriptions.
35    pub(crate) fn from_fields(fields: &[FieldDescription]) -> Arc<Self> {
36        let columns: Vec<Column> = fields
37            .iter()
38            .map(|f| Column {
39                name: f.name.clone(),
40                type_oid: f.type_oid,
41                type_size: f.type_size,
42                type_mod: f.type_mod,
43                table_oid: f.table_oid,
44                column_attr: f.column_attr,
45            })
46            .collect();
47        Arc::new(Columns {
48            columns,
49        })
50    }
51
52    /// The columns, in order.
53    pub fn as_slice(&self) -> &[Column] {
54        &self.columns
55    }
56
57    /// The number of columns.
58    pub fn len(&self) -> usize {
59        self.columns.len()
60    }
61
62    /// `true` if the result set has no columns.
63    pub fn is_empty(&self) -> bool {
64        self.columns.is_empty()
65    }
66
67    /// Resolves a column name to its index.
68    ///
69    /// Uses a linear scan: result sets rarely have enough columns for a hash
70    /// map allocation to pay off.
71    pub fn index_of(&self, name: &str) -> Option<usize> {
72        self.columns.iter().position(|column| column.name == name)
73    }
74}
75
76/// One result row.
77///
78/// Column values are borrowed slices of the received frame, so a row can be
79/// cheaply cloned and kept beyond its originating query.
80#[derive(Debug, Clone)]
81pub struct Row {
82    columns: Arc<Columns>,
83    values: Box<[Option<Bytes>]>,
84    zone: time::TimeZone,
85}
86
87impl Row {
88    pub(crate) fn new(columns: Arc<Columns>, values: Box<[Option<Bytes>]>, zone: time::TimeZone) -> Self {
89        Row {
90            columns,
91            values,
92            zone,
93        }
94    }
95
96    /// Parses a `DataRow` payload (starting with the column count).
97    ///
98    /// The payload comes straight from the server and is validated before it
99    /// is trusted: a negative or oversized column count, a column length that
100    /// runs past the frame, or a count that does not match the statement's
101    /// result description is an error, never a panic.
102    pub(crate) fn parse(columns: Arc<Columns>, payload: &Bytes, zone: time::TimeZone) -> Result<Self, Error> {
103        if payload.len() < 2 {
104            return Err(Error::Decode("data row is truncated".into()));
105        }
106        let raw_count = i16::from_be_bytes(payload[0..2].try_into().unwrap());
107        let count = usize::try_from(raw_count)
108            .map_err(|_| Error::Decode(format!("data row has an invalid column count {raw_count}")))?;
109        if count != columns.len() {
110            return Err(Error::Decode(format!(
111                "data row has {count} columns but the statement returns {}",
112                columns.len()
113            )));
114        }
115        let mut pos = 2usize;
116
117        let mut values = Vec::with_capacity(count);
118        for _ in 0..count {
119            if pos + 4 > payload.len() {
120                return Err(Error::Decode("data row is truncated".into()));
121            }
122            let raw_len = i32::from_be_bytes(payload[pos..pos + 4].try_into().unwrap());
123            pos += 4;
124            if raw_len == -1 {
125                values.push(None);
126            } else {
127                let len = usize::try_from(raw_len)
128                    .map_err(|_| Error::Decode(format!("data row column has an invalid length {raw_len}")))?;
129                if pos + len > payload.len() {
130                    return Err(Error::Decode("data row column exceeds frame".into()));
131                }
132                values.push(Some(payload.slice(pos..pos + len)));
133                pos += len;
134            }
135        }
136        Ok(Row::new(columns, values.into_boxed_slice(), zone))
137    }
138
139    /// The column metadata.
140    pub fn columns(&self) -> &[Column] {
141        self.columns.as_slice()
142    }
143
144    /// The number of columns.
145    pub fn len(&self) -> usize {
146        self.values.len()
147    }
148
149    /// `true` if the row has no columns.
150    pub fn is_empty(&self) -> bool {
151        self.values.is_empty()
152    }
153
154    /// Returns `true` if the column at `index` is `NULL`.
155    pub fn is_null(&self, index: usize) -> bool {
156        self.values.get(index).map(|v| v.is_none()).unwrap_or(true)
157    }
158
159    /// Decodes the column named `name`.
160    ///
161    /// # Errors
162    ///
163    /// Returns [`Error::ColumnNotFound`] if the name is unknown,
164    /// [`Error::UnexpectedNull`] if the column is `NULL` and `T` is not
165    /// [`Option`], or a decode error if the bytes are malformed.
166    pub fn try_get<T: FromSql>(&self, name: &str) -> Result<T, Error> {
167        let index = self
168            .columns
169            .index_of(name)
170            .ok_or_else(|| Error::ColumnNotFound(name.to_string()))?;
171        self.try_get_by_index(index)
172    }
173
174    /// Decodes the column at `index`.
175    ///
176    /// # Errors
177    ///
178    /// Returns [`Error::ColumnNotFound`] if the index is out of range,
179    /// [`Error::UnexpectedNull`] if the column is `NULL` and `T` is not
180    /// [`Option`], [`Error::Decode`] if the column's type cannot be decoded as
181    /// `T`, or a decode error if the bytes are malformed.
182    pub fn try_get_by_index<T: FromSql>(&self, index: usize) -> Result<T, Error> {
183        let column = self
184            .columns
185            .as_slice()
186            .get(index)
187            .ok_or_else(|| Error::ColumnNotFound(format!("index {index}")))?;
188        if !T::accepts(column.type_oid) {
189            return Err(Error::Decode(format!(
190                "column `{}` has type OID {} which cannot be decoded as the requested type",
191                column.name, column.type_oid
192            )));
193        }
194        match self.values.get(index).and_then(|v| v.as_ref()) {
195            None => T::from_sql_null().map_err(|_| Error::UnexpectedNull(column.name.clone())),
196            Some(bytes) => T::from_sql_zoned(column.type_oid, bytes, self.zone),
197        }
198    }
199
200    fn value(&self, name: &str) -> Result<Option<&Bytes>, Error> {
201        let index = self
202            .columns
203            .index_of(name)
204            .ok_or_else(|| Error::ColumnNotFound(name.to_string()))?;
205        Ok(self.values.get(index).and_then(|v| v.as_ref()))
206    }
207
208    /// Returns the named column as a borrowed UTF-8 string, without allocating.
209    ///
210    /// # Errors
211    ///
212    /// Returns [`Error::ColumnNotFound`] if the name is unknown,
213    /// [`Error::UnexpectedNull`] if the column is `NULL`, or a decode error if
214    /// the bytes are not valid UTF-8.
215    pub fn get_str<'a>(&'a self, name: &str) -> Result<&'a str, Error> {
216        match self.value(name)? {
217            None => Err(Error::UnexpectedNull(name.to_string())),
218            Some(bytes) => Ok(std::str::from_utf8(bytes)?),
219        }
220    }
221
222    /// Returns the named column as borrowed bytes, without allocating.
223    ///
224    /// Returns `None` for SQL `NULL`.
225    ///
226    /// # Errors
227    ///
228    /// Returns [`Error::ColumnNotFound`] if the name is unknown.
229    pub fn get_bytes<'a>(&'a self, name: &str) -> Result<Option<&'a [u8]>, Error> {
230        Ok(self.value(name)?.map(|bytes| bytes.as_ref()))
231    }
232}
233
234/// A type that can be built from a [`Row`].
235///
236/// Implemented for scalars, tuples, arrays, and — via
237/// `#[derive(postgresql::FromRow)]` — structs with named fields.
238pub trait FromRow: Sized {
239    /// Builds a value from a row.
240    fn from_row(row: &Row) -> Result<Self, Error>;
241}
242
243macro_rules! impl_from_row_scalar {
244    ($($ty:ty),* $(,)?) => {
245        $(
246            impl FromRow for $ty {
247                fn from_row(row: &Row) -> Result<Self, Error> {
248                    row.try_get_by_index(0)
249                }
250            }
251        )*
252    };
253}
254
255impl_from_row_scalar!(
256    i16,
257    i32,
258    i64,
259    f32,
260    f64,
261    bool,
262    String,
263    Vec<u8>,
264    uuid::Uuid,
265    time::DateTime,
266    serde_json::Value,
267    ipnetwork::IpNetwork,
268    crate::interval::Interval,
269);
270
271impl<T: FromSql> FromRow for Option<T> {
272    fn from_row(row: &Row) -> Result<Self, Error> {
273        row.try_get_by_index(0)
274    }
275}
276
277impl FromRow for Row {
278    fn from_row(row: &Row) -> Result<Self, Error> {
279        Ok(row.clone())
280    }
281}
282
283impl<T: FromSql> FromRow for Vec<T> {
284    fn from_row(row: &Row) -> Result<Self, Error> {
285        row.try_get_by_index(0)
286    }
287}
288
289impl<T: serde::de::DeserializeOwned> FromRow for Json<T> {
290    fn from_row(row: &Row) -> Result<Self, Error> {
291        row.try_get_by_index(0)
292    }
293}
294
295macro_rules! impl_from_row_tuple {
296    ($($name:ident),+) => {
297        #[allow(unused_assignments)]
298        impl<$($name: FromSql),+> FromRow for ($($name,)+) {
299            fn from_row(row: &Row) -> Result<Self, Error> {
300                let mut index = 0usize;
301                Ok((
302                    $({
303                        let value = row.try_get_by_index::<$name>(index)?;
304                        index += 1;
305                        value
306                    },)+
307                ))
308            }
309        }
310    };
311}
312
313impl_from_row_tuple!(A);
314impl_from_row_tuple!(A, B);
315impl_from_row_tuple!(A, B, C);
316impl_from_row_tuple!(A, B, C, D);
317impl_from_row_tuple!(A, B, C, D, E);
318impl_from_row_tuple!(A, B, C, D, E, F);
319impl_from_row_tuple!(A, B, C, D, E, F, G);
320impl_from_row_tuple!(A, B, C, D, E, F, G, H);
321
322#[cfg(test)]
323mod tests {
324    use bytes::BytesMut;
325    use postgresql_protocol::backend::FieldDescription;
326
327    use super::*;
328
329    fn columns(names: &[(&str, Oid)]) -> Arc<Columns> {
330        let fields: Vec<FieldDescription> = names
331            .iter()
332            .map(|(name, oid)| FieldDescription {
333                name: (*name).to_string(),
334                table_oid: 0,
335                column_attr: 0,
336                type_oid: *oid,
337                type_size: -1,
338                type_mod: -1,
339                format: 1,
340            })
341            .collect();
342        Columns::from_fields(&fields)
343    }
344
345    fn frame(parts: &[Option<&[u8]>]) -> Bytes {
346        let mut buf = BytesMut::new();
347        buf.extend_from_slice(&(parts.len() as i16).to_be_bytes());
348        for part in parts {
349            match part {
350                None => buf.extend_from_slice(&(-1i32).to_be_bytes()),
351                Some(bytes) => {
352                    buf.extend_from_slice(&(bytes.len() as i32).to_be_bytes());
353                    buf.extend_from_slice(bytes);
354                }
355            }
356        }
357        buf.freeze()
358    }
359
360    #[test]
361    fn parses_values_and_nulls() {
362        let cols = columns(&[("a", 25), ("b", 23)]);
363        let payload = frame(&[Some(b"hi"), None]);
364        let row = Row::parse(cols, &payload, time::TimeZone::UTC).unwrap();
365        assert_eq!(row.get_str("a").unwrap(), "hi");
366        assert!(row.is_null(1));
367        assert_eq!(row.len(), 2);
368    }
369
370    #[test]
371    fn negative_column_count_is_rejected() {
372        let cols = columns(&[("a", 25)]);
373        let mut payload = BytesMut::new();
374        payload.extend_from_slice(&(-1i16).to_be_bytes());
375        payload.extend_from_slice(&0i32.to_be_bytes());
376        let err = Row::parse(cols, &payload.freeze(), time::TimeZone::UTC).unwrap_err();
377        assert!(matches!(err, Error::Decode(_)), "{err}");
378    }
379
380    #[test]
381    fn column_count_must_match_the_statement() {
382        // Fewer fields than the description: this is the shape of
383        // RUSTSEC-2026-0178 (an out-of-bounds panic in `Row::get`).
384        let cols = columns(&[("a", 25), ("b", 25)]);
385        let payload = frame(&[Some(b"only one")]);
386        let err = Row::parse(cols, &payload, time::TimeZone::UTC).unwrap_err();
387        assert!(matches!(err, Error::Decode(_)), "{err}");
388    }
389
390    #[test]
391    fn truncated_rows_are_rejected() {
392        let cols = columns(&[("a", 25)]);
393        // Column length runs past the frame.
394        let mut payload = BytesMut::new();
395        payload.extend_from_slice(&1i16.to_be_bytes());
396        payload.extend_from_slice(&100i32.to_be_bytes());
397        payload.extend_from_slice(b"short");
398        assert!(Row::parse(cols.clone(), &payload.freeze(), time::TimeZone::UTC).is_err());
399
400        // Column length is negative but not the NULL marker.
401        let mut payload = BytesMut::new();
402        payload.extend_from_slice(&1i16.to_be_bytes());
403        payload.extend_from_slice(&(-2i32).to_be_bytes());
404        assert!(Row::parse(cols.clone(), &payload.freeze(), time::TimeZone::UTC).is_err());
405
406        // Missing length prefix entirely.
407        let mut payload = BytesMut::new();
408        payload.extend_from_slice(&1i16.to_be_bytes());
409        payload.extend_from_slice(&[1, 2]);
410        assert!(Row::parse(cols, &payload.freeze(), time::TimeZone::UTC).is_err());
411    }
412
413    #[test]
414    fn values_are_zero_copy_slices_of_the_frame() {
415        let cols = columns(&[("a", 25)]);
416        let payload = frame(&[Some(b"hello")]);
417        let row = Row::parse(cols, &payload, time::TimeZone::UTC).unwrap();
418        let text = row.get_str("a").unwrap();
419        // The accessor borrows from the frame, it does not copy.
420        assert!(payload.as_ptr_range().contains(&text.as_ptr()));
421    }
422
423    #[derive(serde::Serialize, serde::Deserialize, postgresql_derive::Json, Debug, PartialEq)]
424    struct JsonUser {
425        name: String,
426    }
427
428    #[test]
429    fn json_types_decode_a_whole_column() {
430        let mut jsonb = Vec::new();
431        jsonb.push(1u8); // jsonb format version
432        jsonb.extend_from_slice(br#"{"name":"carol"}"#);
433        let payload = frame(&[Some(&jsonb)]);
434
435        let cols = columns(&[("user", postgresql_protocol::oid::JSONBOID)]);
436        let row = Row::parse(cols, &payload, time::TimeZone::UTC).unwrap();
437
438        let derived = <JsonUser as FromRow>::from_row(&row).unwrap();
439        assert_eq!(
440            derived,
441            JsonUser {
442                name: "carol".to_string()
443            }
444        );
445
446        let wrapped = <Json<JsonUser> as FromRow>::from_row(&row).unwrap();
447        assert_eq!(
448            wrapped.into_inner(),
449            JsonUser {
450                name: "carol".to_string()
451            }
452        );
453    }
454
455    #[test]
456    fn decoding_into_the_wrong_type_is_an_error() {
457        let cols = columns(&[("n", 23)]);
458        let payload = frame(&[Some(&1i32.to_be_bytes())]);
459        let row = Row::parse(cols, &payload, time::TimeZone::UTC).unwrap();
460        // `bool` would otherwise silently reinterpret the first byte.
461        assert!(row.try_get::<bool>("n").is_err());
462        assert!(row.try_get::<String>("n").is_err());
463        assert_eq!(row.try_get::<i32>("n").unwrap(), 1);
464    }
465}