Skip to main content

prax_postgres/
row_ref.rs

1//! Bridge between `tokio_postgres::Row` and `prax_query::row::RowRef`.
2//!
3//! `RowRef` is defined in `prax-query` and `Row` is defined in `tokio-postgres`;
4//! both are foreign to this crate, so Rust's orphan rules forbid a direct
5//! `impl RowRef for Row`. We wrap the row in a `#[repr(transparent)]` newtype
6//! (`PgRow`) and implement the trait on the wrapper.
7//!
8//! ## Dual API surface
9//!
10//! `PgRow` derefs to the wrapped `Row`, so callers can use either API:
11//! * The generic `RowRef` interface (`pg_row.get_i32("id")`) — portable across
12//!   drivers and what generated `FromRow` impls use.
13//! * The native `tokio_postgres::Row` interface (`pg_row.try_get::<_, i32>("id")`)
14//!   — full access to the driver's type system for columns `RowRef` does not
15//!   model (arrays, range types, etc.).
16//!
17//! ## `rust_decimal` limitation
18//!
19//! `tokio-postgres` 0.7 has no `with-rust_decimal-*` feature gate and
20//! `rust_decimal::Decimal` therefore has no `FromSql` impl through the driver.
21//! `PgRow::get_decimal` and `PgRow::get_decimal_opt` fall back to the trait's
22//! default implementations, which return a `RowError::TypeConversion` marked
23//! "decimal not supported by this row type". Until we add a bridging
24//! `FromSql`/`ToSql` impl (or switch to a driver that exposes the feature),
25//! callers that need decimal values should cast the column to text
26//! (`amount::text`) and parse in application code.
27
28use prax_query::row::{RowError, RowRef, into_row_error};
29use tokio_postgres::Row;
30
31/// Newtype wrapper around `tokio_postgres::Row` that implements
32/// `prax_query::row::RowRef`.
33///
34/// The inner row is private; use [`PgRow::into_inner`] when ownership is
35/// needed (e.g., forwarding to a tokio-postgres API that consumes `Row`).
36/// For read-only access, the `Deref<Target = Row>` impl lets you call
37/// any `Row` method directly.
38#[repr(transparent)]
39pub struct PgRow(Row);
40
41impl PgRow {
42    /// Move the wrapped `tokio_postgres::Row` out of this wrapper.
43    pub fn into_inner(self) -> Row {
44        self.0
45    }
46}
47
48impl std::ops::Deref for PgRow {
49    type Target = Row;
50    fn deref(&self) -> &Self::Target {
51        &self.0
52    }
53}
54
55impl From<Row> for PgRow {
56    fn from(row: Row) -> Self {
57        PgRow(row)
58    }
59}
60
61impl RowRef for PgRow {
62    fn get_i32(&self, c: &str) -> Result<i32, RowError> {
63        into_row_error(c, self.try_get::<_, i32>(c))
64    }
65    fn get_i32_opt(&self, c: &str) -> Result<Option<i32>, RowError> {
66        into_row_error(c, self.try_get::<_, Option<i32>>(c))
67    }
68    fn get_i64(&self, c: &str) -> Result<i64, RowError> {
69        into_row_error(c, self.try_get::<_, i64>(c))
70    }
71    fn get_i64_opt(&self, c: &str) -> Result<Option<i64>, RowError> {
72        into_row_error(c, self.try_get::<_, Option<i64>>(c))
73    }
74    fn get_f64(&self, c: &str) -> Result<f64, RowError> {
75        into_row_error(c, self.try_get::<_, f64>(c))
76    }
77    fn get_f64_opt(&self, c: &str) -> Result<Option<f64>, RowError> {
78        into_row_error(c, self.try_get::<_, Option<f64>>(c))
79    }
80    fn get_bool(&self, c: &str) -> Result<bool, RowError> {
81        into_row_error(c, self.try_get::<_, bool>(c))
82    }
83    fn get_bool_opt(&self, c: &str) -> Result<Option<bool>, RowError> {
84        into_row_error(c, self.try_get::<_, Option<bool>>(c))
85    }
86    fn get_str(&self, c: &str) -> Result<&str, RowError> {
87        into_row_error(c, self.try_get::<_, &str>(c))
88    }
89    fn get_str_opt(&self, c: &str) -> Result<Option<&str>, RowError> {
90        into_row_error(c, self.try_get::<_, Option<&str>>(c))
91    }
92    fn get_bytes(&self, c: &str) -> Result<&[u8], RowError> {
93        into_row_error(c, self.try_get::<_, &[u8]>(c))
94    }
95    fn get_bytes_opt(&self, c: &str) -> Result<Option<&[u8]>, RowError> {
96        into_row_error(c, self.try_get::<_, Option<&[u8]>>(c))
97    }
98    fn get_datetime_utc(&self, c: &str) -> Result<chrono::DateTime<chrono::Utc>, RowError> {
99        into_row_error(c, self.try_get::<_, chrono::DateTime<chrono::Utc>>(c))
100    }
101    fn get_datetime_utc_opt(
102        &self,
103        c: &str,
104    ) -> Result<Option<chrono::DateTime<chrono::Utc>>, RowError> {
105        into_row_error(
106            c,
107            self.try_get::<_, Option<chrono::DateTime<chrono::Utc>>>(c),
108        )
109    }
110    fn get_naive_datetime(&self, c: &str) -> Result<chrono::NaiveDateTime, RowError> {
111        into_row_error(c, self.try_get::<_, chrono::NaiveDateTime>(c))
112    }
113    fn get_naive_datetime_opt(&self, c: &str) -> Result<Option<chrono::NaiveDateTime>, RowError> {
114        into_row_error(c, self.try_get::<_, Option<chrono::NaiveDateTime>>(c))
115    }
116    fn get_naive_date(&self, c: &str) -> Result<chrono::NaiveDate, RowError> {
117        into_row_error(c, self.try_get::<_, chrono::NaiveDate>(c))
118    }
119    fn get_naive_date_opt(&self, c: &str) -> Result<Option<chrono::NaiveDate>, RowError> {
120        into_row_error(c, self.try_get::<_, Option<chrono::NaiveDate>>(c))
121    }
122    fn get_naive_time(&self, c: &str) -> Result<chrono::NaiveTime, RowError> {
123        into_row_error(c, self.try_get::<_, chrono::NaiveTime>(c))
124    }
125    fn get_naive_time_opt(&self, c: &str) -> Result<Option<chrono::NaiveTime>, RowError> {
126        into_row_error(c, self.try_get::<_, Option<chrono::NaiveTime>>(c))
127    }
128    fn get_uuid(&self, c: &str) -> Result<uuid::Uuid, RowError> {
129        into_row_error(c, self.try_get::<_, uuid::Uuid>(c))
130    }
131    fn get_uuid_opt(&self, c: &str) -> Result<Option<uuid::Uuid>, RowError> {
132        into_row_error(c, self.try_get::<_, Option<uuid::Uuid>>(c))
133    }
134    fn get_json(&self, c: &str) -> Result<serde_json::Value, RowError> {
135        into_row_error(c, self.try_get::<_, serde_json::Value>(c))
136    }
137    fn get_json_opt(&self, c: &str) -> Result<Option<serde_json::Value>, RowError> {
138        into_row_error(c, self.try_get::<_, Option<serde_json::Value>>(c))
139    }
140    fn get_decimal(&self, c: &str) -> Result<rust_decimal::Decimal, RowError> {
141        Err(RowError::TypeConversion {
142            column: c.to_string(),
143            message: "decimal columns require tokio-postgres with \
144                      `with-rust_decimal-*` feature, which this workspace does not \
145                      currently enable. Cast NUMERIC columns to TEXT in your SQL \
146                      (e.g. amount::text) and decode as String, or upgrade \
147                      tokio-postgres."
148                .to_string(),
149        })
150    }
151    fn get_decimal_opt(&self, c: &str) -> Result<Option<rust_decimal::Decimal>, RowError> {
152        // NULL can't be distinguished from the underlying "unsupported" state
153        // at this layer; surface the same actionable message.
154        Err(RowError::TypeConversion {
155            column: c.to_string(),
156            message: "decimal columns require tokio-postgres with \
157                      `with-rust_decimal-*` feature, which this workspace does not \
158                      currently enable. Cast NUMERIC columns to TEXT in your SQL \
159                      (e.g. amount::text) and decode as String, or upgrade \
160                      tokio-postgres."
161                .to_string(),
162        })
163    }
164}