1use std::error::Error as StdError;
29
30use prax_query::row::{RowError, RowRef, into_row_error};
31use tokio_postgres::Row;
32use tokio_postgres::types::{FromSql, Kind, Type};
33
34struct AnyText(String);
43
44impl<'a> FromSql<'a> for AnyText {
45 fn from_sql(_ty: &Type, raw: &'a [u8]) -> Result<Self, Box<dyn StdError + Sync + Send>> {
46 Ok(AnyText(std::str::from_utf8(raw)?.to_owned()))
47 }
48 fn accepts(_ty: &Type) -> bool {
49 true
50 }
51}
52
53struct NullProbe;
66
67impl<'a> FromSql<'a> for NullProbe {
68 fn from_sql(_ty: &Type, _raw: &'a [u8]) -> Result<Self, Box<dyn StdError + Sync + Send>> {
69 Ok(NullProbe)
70 }
71 fn accepts(_ty: &Type) -> bool {
72 true
73 }
74}
75
76#[repr(transparent)]
84pub struct PgRow(Row);
85
86impl PgRow {
87 pub fn into_inner(self) -> Row {
89 self.0
90 }
91}
92
93impl std::ops::Deref for PgRow {
94 type Target = Row;
95 fn deref(&self) -> &Self::Target {
96 &self.0
97 }
98}
99
100impl From<Row> for PgRow {
101 fn from(row: Row) -> Self {
102 PgRow(row)
103 }
104}
105
106impl RowRef for PgRow {
107 fn get_i32(&self, c: &str) -> Result<i32, RowError> {
108 into_row_error(c, self.try_get::<_, i32>(c))
109 }
110 fn get_i32_opt(&self, c: &str) -> Result<Option<i32>, RowError> {
111 into_row_error(c, self.try_get::<_, Option<i32>>(c))
112 }
113 fn get_i64(&self, c: &str) -> Result<i64, RowError> {
114 into_row_error(c, self.try_get::<_, i64>(c))
115 }
116 fn get_i64_opt(&self, c: &str) -> Result<Option<i64>, RowError> {
117 into_row_error(c, self.try_get::<_, Option<i64>>(c))
118 }
119 fn get_f64(&self, c: &str) -> Result<f64, RowError> {
120 into_row_error(c, self.try_get::<_, f64>(c))
121 }
122 fn get_f64_opt(&self, c: &str) -> Result<Option<f64>, RowError> {
123 into_row_error(c, self.try_get::<_, Option<f64>>(c))
124 }
125 fn get_bool(&self, c: &str) -> Result<bool, RowError> {
126 into_row_error(c, self.try_get::<_, bool>(c))
127 }
128 fn get_bool_opt(&self, c: &str) -> Result<Option<bool>, RowError> {
129 into_row_error(c, self.try_get::<_, Option<bool>>(c))
130 }
131 fn get_str(&self, c: &str) -> Result<&str, RowError> {
132 into_row_error(c, self.try_get::<_, &str>(c))
133 }
134 fn get_str_opt(&self, c: &str) -> Result<Option<&str>, RowError> {
135 into_row_error(c, self.try_get::<_, Option<&str>>(c))
136 }
137 fn get_string(&self, c: &str) -> Result<String, RowError> {
154 let columns = self.0.columns();
155 if let Some(col) = columns.iter().find(|col| col.name() == c) {
156 let ty = col.type_();
157 if *ty == Type::UUID {
158 return into_row_error(c, self.try_get::<_, ::uuid::Uuid>(c))
159 .map(|u| u.to_string());
160 }
161 if matches!(ty.kind(), Kind::Enum(_)) {
162 return into_row_error(c, self.try_get::<_, AnyText>(c)).map(|t| t.0);
163 }
164 }
165 into_row_error(c, self.try_get::<_, &str>(c)).map(|s| s.to_string())
166 }
167 fn get_string_opt(&self, c: &str) -> Result<Option<String>, RowError> {
168 let columns = self.0.columns();
169 if let Some(col) = columns.iter().find(|col| col.name() == c) {
170 let ty = col.type_();
171 if *ty == Type::UUID {
172 return into_row_error(c, self.try_get::<_, Option<::uuid::Uuid>>(c))
173 .map(|opt| opt.map(|u| u.to_string()));
174 }
175 if matches!(ty.kind(), Kind::Enum(_)) {
176 return into_row_error(c, self.try_get::<_, Option<AnyText>>(c))
177 .map(|opt| opt.map(|t| t.0));
178 }
179 }
180 into_row_error(c, self.try_get::<_, Option<&str>>(c)).map(|opt| opt.map(|s| s.to_string()))
181 }
182 fn is_null(&self, c: &str) -> Result<bool, RowError> {
193 into_row_error(c, self.try_get::<_, Option<NullProbe>>(c)).map(|opt| opt.is_none())
194 }
195
196 fn get_bytes(&self, c: &str) -> Result<&[u8], RowError> {
197 into_row_error(c, self.try_get::<_, &[u8]>(c))
198 }
199 fn get_bytes_opt(&self, c: &str) -> Result<Option<&[u8]>, RowError> {
200 into_row_error(c, self.try_get::<_, Option<&[u8]>>(c))
201 }
202 fn get_datetime_utc(&self, c: &str) -> Result<chrono::DateTime<chrono::Utc>, RowError> {
203 into_row_error(c, self.try_get::<_, chrono::DateTime<chrono::Utc>>(c))
204 }
205 fn get_datetime_utc_opt(
206 &self,
207 c: &str,
208 ) -> Result<Option<chrono::DateTime<chrono::Utc>>, RowError> {
209 into_row_error(
210 c,
211 self.try_get::<_, Option<chrono::DateTime<chrono::Utc>>>(c),
212 )
213 }
214 fn get_naive_datetime(&self, c: &str) -> Result<chrono::NaiveDateTime, RowError> {
215 into_row_error(c, self.try_get::<_, chrono::NaiveDateTime>(c))
216 }
217 fn get_naive_datetime_opt(&self, c: &str) -> Result<Option<chrono::NaiveDateTime>, RowError> {
218 into_row_error(c, self.try_get::<_, Option<chrono::NaiveDateTime>>(c))
219 }
220 fn get_naive_date(&self, c: &str) -> Result<chrono::NaiveDate, RowError> {
221 into_row_error(c, self.try_get::<_, chrono::NaiveDate>(c))
222 }
223 fn get_naive_date_opt(&self, c: &str) -> Result<Option<chrono::NaiveDate>, RowError> {
224 into_row_error(c, self.try_get::<_, Option<chrono::NaiveDate>>(c))
225 }
226 fn get_naive_time(&self, c: &str) -> Result<chrono::NaiveTime, RowError> {
227 into_row_error(c, self.try_get::<_, chrono::NaiveTime>(c))
228 }
229 fn get_naive_time_opt(&self, c: &str) -> Result<Option<chrono::NaiveTime>, RowError> {
230 into_row_error(c, self.try_get::<_, Option<chrono::NaiveTime>>(c))
231 }
232 fn get_uuid(&self, c: &str) -> Result<uuid::Uuid, RowError> {
233 into_row_error(c, self.try_get::<_, uuid::Uuid>(c))
234 }
235 fn get_uuid_opt(&self, c: &str) -> Result<Option<uuid::Uuid>, RowError> {
236 into_row_error(c, self.try_get::<_, Option<uuid::Uuid>>(c))
237 }
238 fn get_json(&self, c: &str) -> Result<serde_json::Value, RowError> {
239 into_row_error(c, self.try_get::<_, serde_json::Value>(c))
240 }
241 fn get_json_opt(&self, c: &str) -> Result<Option<serde_json::Value>, RowError> {
242 into_row_error(c, self.try_get::<_, Option<serde_json::Value>>(c))
243 }
244 fn get_decimal(&self, c: &str) -> Result<rust_decimal::Decimal, RowError> {
245 Err(RowError::TypeConversion {
246 column: c.to_string(),
247 message: "decimal columns require tokio-postgres with \
248 `with-rust_decimal-*` feature, which this workspace does not \
249 currently enable. Cast NUMERIC columns to TEXT in your SQL \
250 (e.g. amount::text) and decode as String, or upgrade \
251 tokio-postgres."
252 .to_string(),
253 })
254 }
255 fn get_decimal_opt(&self, c: &str) -> Result<Option<rust_decimal::Decimal>, RowError> {
256 Err(RowError::TypeConversion {
259 column: c.to_string(),
260 message: "decimal columns require tokio-postgres with \
261 `with-rust_decimal-*` feature, which this workspace does not \
262 currently enable. Cast NUMERIC columns to TEXT in your SQL \
263 (e.g. amount::text) and decode as String, or upgrade \
264 tokio-postgres."
265 .to_string(),
266 })
267 }
268}