prax_postgres/
row.rs

1//! PostgreSQL row types and deserialization.
2
3use tokio_postgres::Row;
4
5use crate::error::{PgError, PgResult};
6
7/// Extension trait for PostgreSQL rows.
8pub trait PgRow {
9    /// Get a column value by name.
10    fn get_value<T>(&self, column: &str) -> PgResult<T>
11    where
12        T: for<'a> tokio_postgres::types::FromSql<'a>;
13
14    /// Get an optional column value by name.
15    fn get_opt<T>(&self, column: &str) -> PgResult<Option<T>>
16    where
17        T: for<'a> tokio_postgres::types::FromSql<'a>;
18
19    /// Try to get a column value, returning None if the column doesn't exist.
20    fn try_get<T>(&self, column: &str) -> Option<T>
21    where
22        T: for<'a> tokio_postgres::types::FromSql<'a>;
23}
24
25impl PgRow for Row {
26    fn get_value<T>(&self, column: &str) -> PgResult<T>
27    where
28        T: for<'a> tokio_postgres::types::FromSql<'a>,
29    {
30        self.try_get(column).map_err(|e| {
31            PgError::deserialization(format!("failed to get column '{}': {}", column, e))
32        })
33    }
34
35    fn get_opt<T>(&self, column: &str) -> PgResult<Option<T>>
36    where
37        T: for<'a> tokio_postgres::types::FromSql<'a>,
38    {
39        match self.try_get(column) {
40            Ok(value) => Ok(value),
41            Err(e) => {
42                // Check if it's a null value or a missing column
43                if e.to_string().contains("null") {
44                    Ok(None)
45                } else {
46                    Err(PgError::deserialization(format!(
47                        "failed to get column '{}': {}",
48                        column, e
49                    )))
50                }
51            }
52        }
53    }
54
55    fn try_get<T>(&self, column: &str) -> Option<T>
56    where
57        T: for<'a> tokio_postgres::types::FromSql<'a>,
58    {
59        Row::try_get(self, column).ok()
60    }
61}
62
63/// Trait for deserializing a PostgreSQL row into a type.
64pub trait FromPgRow: Sized {
65    /// Deserialize from a PostgreSQL row.
66    fn from_row(row: &Row) -> PgResult<Self>;
67}
68
69/// Macro to implement FromPgRow for simple structs.
70///
71/// Usage:
72/// ```rust,ignore
73/// impl_from_row!(User {
74///     id: i32,
75///     email: String,
76///     name: Option<String>,
77/// });
78/// ```
79#[macro_export]
80macro_rules! impl_from_row {
81    ($type:ident { $($field:ident : $field_type:ty),* $(,)? }) => {
82        impl $crate::row::FromPgRow for $type {
83            fn from_row(row: &tokio_postgres::Row) -> $crate::error::PgResult<Self> {
84                use $crate::row::PgRow;
85                Ok(Self {
86                    $(
87                        $field: row.get_value(stringify!($field))?,
88                    )*
89                })
90            }
91        }
92    };
93}
94
95#[cfg(test)]
96mod tests {
97    // Row tests require integration testing with a real database
98}