Skip to main content

zero_postgres/conversion/
row.rs

1//! Row decoding traits and implementations.
2
3use crate::conversion::FromWireValue;
4use crate::error::{Error, Result};
5use crate::protocol::backend::query::{DataRow, FieldDescription};
6
7/// Trait for decoding a PostgreSQL row into a Rust type.
8pub trait FromRow<'a>: Sized {
9    /// Decode a row from text format (simple protocol).
10    fn from_row_text(cols: &[FieldDescription], row: DataRow<'a>) -> Result<Self>;
11
12    /// Decode a row from binary format (extended protocol).
13    fn from_row_binary(cols: &[FieldDescription], row: DataRow<'a>) -> Result<Self>;
14}
15
16/// Decode a single column value as text.
17fn decode_column_text<'a, T: FromWireValue<'a>>(
18    field: &FieldDescription,
19    value: Option<&'a [u8]>,
20) -> Result<T> {
21    match value {
22        None => T::from_null(),
23        Some(bytes) => T::from_text(field.type_oid(), bytes),
24    }
25}
26
27/// Decode a single column value as binary.
28fn decode_column_binary<'a, T: FromWireValue<'a>>(
29    field: &FieldDescription,
30    value: Option<&'a [u8]>,
31) -> Result<T> {
32    match value {
33        None => T::from_null(),
34        Some(bytes) => T::from_binary(field.type_oid(), bytes),
35    }
36}
37
38// === Tuple implementations ===
39
40/// Implementation for empty tuple - used for statements that don't return rows
41impl FromRow<'_> for () {
42    fn from_row_text(_cols: &[FieldDescription], _row: DataRow<'_>) -> Result<Self> {
43        Ok(())
44    }
45
46    fn from_row_binary(_cols: &[FieldDescription], _row: DataRow<'_>) -> Result<Self> {
47        Ok(())
48    }
49}
50
51macro_rules! impl_from_row_tuple {
52    ($count:literal: $($idx:tt => $T:ident),+) => {
53        impl<'a, $($T: FromWireValue<'a>),+> FromRow<'a> for ($($T,)+) {
54            fn from_row_text(cols: &[FieldDescription], row: DataRow<'a>) -> Result<Self> {
55                let cols = cols.first_chunk::<$count>()
56                    .ok_or_else(|| Error::Decode("not enough columns for tuple".into()))?;
57                let mut iter = row.iter();
58                Ok(($(
59                    decode_column_text(&cols[$idx], iter.next().flatten())?,
60                )+))
61            }
62
63            fn from_row_binary(cols: &[FieldDescription], row: DataRow<'a>) -> Result<Self> {
64                let cols = cols.first_chunk::<$count>()
65                    .ok_or_else(|| Error::Decode("not enough columns for tuple".into()))?;
66                let mut iter = row.iter();
67                Ok(($(
68                    decode_column_binary(&cols[$idx], iter.next().flatten())?,
69                )+))
70            }
71        }
72    };
73}
74
75impl_from_row_tuple!(1: 0 => T1);
76impl_from_row_tuple!(2: 0 => T1, 1 => T2);
77impl_from_row_tuple!(3: 0 => T1, 1 => T2, 2 => T3);
78impl_from_row_tuple!(4: 0 => T1, 1 => T2, 2 => T3, 3 => T4);
79impl_from_row_tuple!(5: 0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5);
80impl_from_row_tuple!(6: 0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5, 5 => T6);
81impl_from_row_tuple!(7: 0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5, 5 => T6, 6 => T7);
82impl_from_row_tuple!(8: 0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5, 5 => T6, 6 => T7, 7 => T8);
83impl_from_row_tuple!(9: 0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5, 5 => T6, 6 => T7, 7 => T8, 8 => T9);
84impl_from_row_tuple!(10: 0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5, 5 => T6, 6 => T7, 7 => T8, 8 => T9, 9 => T10);
85impl_from_row_tuple!(11: 0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5, 5 => T6, 6 => T7, 7 => T8, 8 => T9, 9 => T10, 10 => T11);
86impl_from_row_tuple!(12: 0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5, 5 => T6, 6 => T7, 7 => T8, 8 => T9, 9 => T10, 10 => T11, 11 => T12);