1use super::PgRow;
7
8pub trait QailRow: Sized {
30 fn columns() -> &'static [&'static str];
33
34 fn from_row(row: &PgRow) -> Self;
37}
38
39impl PgRow {
40 pub fn get_string(&self, idx: usize) -> Option<String> {
43 self.columns
44 .get(idx)?
45 .as_ref()
46 .and_then(|bytes| String::from_utf8(bytes.clone()).ok())
47 }
48
49 pub fn get_i32(&self, idx: usize) -> Option<i32> {
51 let bytes = self.columns.get(idx)?.as_ref()?;
52 std::str::from_utf8(bytes).ok()?.parse().ok()
53 }
54
55 pub fn get_i64(&self, idx: usize) -> Option<i64> {
57 let bytes = self.columns.get(idx)?.as_ref()?;
58 std::str::from_utf8(bytes).ok()?.parse().ok()
59 }
60
61 pub fn get_f64(&self, idx: usize) -> Option<f64> {
63 let bytes = self.columns.get(idx)?.as_ref()?;
64 std::str::from_utf8(bytes).ok()?.parse().ok()
65 }
66
67 pub fn get_bool(&self, idx: usize) -> Option<bool> {
69 let bytes = self.columns.get(idx)?.as_ref()?;
70 let s = std::str::from_utf8(bytes).ok()?;
71 match s {
72 "t" | "true" | "1" => Some(true),
73 "f" | "false" | "0" => Some(false),
74 _ => None,
75 }
76 }
77
78 pub fn is_null(&self, idx: usize) -> bool {
80 self.columns.get(idx).map(|v| v.is_none()).unwrap_or(true)
81 }
82
83 pub fn get_bytes(&self, idx: usize) -> Option<&[u8]> {
85 self.columns.get(idx)?.as_ref().map(|v| v.as_slice())
86 }
87
88 pub fn len(&self) -> usize {
90 self.columns.len()
91 }
92
93 pub fn is_empty(&self) -> bool {
95 self.columns.is_empty()
96 }
97
98 pub fn get_uuid(&self, idx: usize) -> Option<String> {
101 let bytes = self.columns.get(idx)?.as_ref()?;
102
103 if bytes.len() == 16 {
104 use crate::protocol::types::decode_uuid;
106 decode_uuid(bytes).ok()
107 } else {
108 String::from_utf8(bytes.clone()).ok()
110 }
111 }
112
113 pub fn get_json(&self, idx: usize) -> Option<String> {
116 let bytes = self.columns.get(idx)?.as_ref()?;
117
118 if bytes.is_empty() {
119 return Some(String::new());
120 }
121
122 if bytes[0] == 1 && bytes.len() > 1 {
124 String::from_utf8(bytes[1..].to_vec()).ok()
125 } else {
126 String::from_utf8(bytes.clone()).ok()
127 }
128 }
129
130 pub fn get_timestamp(&self, idx: usize) -> Option<String> {
132 let bytes = self.columns.get(idx)?.as_ref()?;
133 String::from_utf8(bytes.clone()).ok()
134 }
135
136 pub fn get_text_array(&self, idx: usize) -> Option<Vec<String>> {
138 let bytes = self.columns.get(idx)?.as_ref()?;
139 let s = std::str::from_utf8(bytes).ok()?;
140 Some(crate::protocol::types::decode_text_array(s))
141 }
142
143 pub fn get_int_array(&self, idx: usize) -> Option<Vec<i64>> {
145 let bytes = self.columns.get(idx)?.as_ref()?;
146 let s = std::str::from_utf8(bytes).ok()?;
147 crate::protocol::types::decode_int_array(s).ok()
148 }
149
150 pub fn text(&self, idx: usize) -> String {
156 self.get_string(idx).unwrap_or_default()
157 }
158
159 pub fn text_or(&self, idx: usize, default: &str) -> String {
162 self.get_string(idx).unwrap_or_else(|| default.to_string())
163 }
164
165 pub fn int(&self, idx: usize) -> i64 {
168 self.get_i64(idx).unwrap_or(0)
169 }
170
171 pub fn float(&self, idx: usize) -> f64 {
173 self.get_f64(idx).unwrap_or(0.0)
174 }
175
176 pub fn boolean(&self, idx: usize) -> bool {
178 self.get_bool(idx).unwrap_or(false)
179 }
180
181 #[cfg(feature = "chrono")]
184 pub fn datetime(&self, idx: usize) -> Option<chrono::DateTime<chrono::Utc>> {
185 let s = self.get_timestamp(idx)?;
186 chrono::DateTime::parse_from_rfc3339(&s.replace(' ', "T"))
188 .ok()
189 .map(|dt| dt.with_timezone(&chrono::Utc))
190 .or_else(|| {
191 chrono::DateTime::parse_from_str(&s, "%Y-%m-%d %H:%M:%S%.f%#z")
193 .ok()
194 .map(|dt| dt.with_timezone(&chrono::Utc))
195 })
196 }
197
198 #[cfg(feature = "uuid")]
200 pub fn uuid_typed(&self, idx: usize) -> Option<uuid::Uuid> {
201 self.get_uuid(idx).and_then(|s| uuid::Uuid::parse_str(&s).ok())
202 }
203
204 pub fn column_index(&self, name: &str) -> Option<usize> {
208 self.column_info.as_ref()?.name_to_index.get(name).copied()
209 }
210
211 pub fn get_string_by_name(&self, name: &str) -> Option<String> {
213 self.get_string(self.column_index(name)?)
214 }
215
216 pub fn get_i32_by_name(&self, name: &str) -> Option<i32> {
218 self.get_i32(self.column_index(name)?)
219 }
220
221 pub fn get_i64_by_name(&self, name: &str) -> Option<i64> {
223 self.get_i64(self.column_index(name)?)
224 }
225
226 pub fn get_f64_by_name(&self, name: &str) -> Option<f64> {
228 self.get_f64(self.column_index(name)?)
229 }
230
231 pub fn get_bool_by_name(&self, name: &str) -> Option<bool> {
233 self.get_bool(self.column_index(name)?)
234 }
235
236 pub fn get_uuid_by_name(&self, name: &str) -> Option<String> {
238 self.get_uuid(self.column_index(name)?)
239 }
240
241 pub fn get_json_by_name(&self, name: &str) -> Option<String> {
243 self.get_json(self.column_index(name)?)
244 }
245
246 pub fn is_null_by_name(&self, name: &str) -> bool {
248 self.column_index(name)
249 .map(|idx| self.is_null(idx))
250 .unwrap_or(true)
251 }
252
253 pub fn get_timestamp_by_name(&self, name: &str) -> Option<String> {
255 self.get_timestamp(self.column_index(name)?)
256 }
257
258 pub fn get_text_array_by_name(&self, name: &str) -> Option<Vec<String>> {
260 self.get_text_array(self.column_index(name)?)
261 }
262
263 pub fn get_int_array_by_name(&self, name: &str) -> Option<Vec<i64>> {
265 self.get_int_array(self.column_index(name)?)
266 }
267
268 pub fn text_by_name(&self, name: &str) -> String {
275 self.get_string_by_name(name).unwrap_or_default()
276 }
277
278 pub fn boolean_by_name(&self, name: &str) -> bool {
280 self.get_bool_by_name(name).unwrap_or(false)
281 }
282
283 pub fn int_by_name(&self, name: &str) -> i64 {
285 self.get_i64_by_name(name).unwrap_or(0)
286 }
287
288 pub fn float_by_name(&self, name: &str) -> f64 {
290 self.get_f64_by_name(name).unwrap_or(0.0)
291 }
292
293 #[cfg(feature = "chrono")]
295 pub fn datetime_by_name(&self, name: &str) -> Option<chrono::DateTime<chrono::Utc>> {
296 self.datetime(self.column_index(name)?)
297 }
298
299 #[cfg(feature = "uuid")]
301 pub fn uuid_typed_by_name(&self, name: &str) -> Option<uuid::Uuid> {
302 self.uuid_typed(self.column_index(name)?)
303 }
304}
305
306#[cfg(test)]
307mod tests {
308 use super::*;
309
310 #[test]
311 fn test_get_string() {
312 let row = PgRow {
313 columns: vec![Some(b"hello".to_vec()), None, Some(b"world".to_vec())],
314 column_info: None,
315 };
316
317 assert_eq!(row.get_string(0), Some("hello".to_string()));
318 assert_eq!(row.get_string(1), None);
319 assert_eq!(row.get_string(2), Some("world".to_string()));
320 }
321
322 #[test]
323 fn test_get_i32() {
324 let row = PgRow {
325 columns: vec![
326 Some(b"42".to_vec()),
327 Some(b"-123".to_vec()),
328 Some(b"not_a_number".to_vec()),
329 ],
330 column_info: None,
331 };
332
333 assert_eq!(row.get_i32(0), Some(42));
334 assert_eq!(row.get_i32(1), Some(-123));
335 assert_eq!(row.get_i32(2), None);
336 }
337
338 #[test]
339 fn test_get_bool() {
340 let row = PgRow {
341 columns: vec![
342 Some(b"t".to_vec()),
343 Some(b"f".to_vec()),
344 Some(b"true".to_vec()),
345 Some(b"false".to_vec()),
346 ],
347 column_info: None,
348 };
349
350 assert_eq!(row.get_bool(0), Some(true));
351 assert_eq!(row.get_bool(1), Some(false));
352 assert_eq!(row.get_bool(2), Some(true));
353 assert_eq!(row.get_bool(3), Some(false));
354 }
355
356 #[test]
357 fn test_is_null() {
358 let row = PgRow {
359 columns: vec![Some(b"value".to_vec()), None],
360 column_info: None,
361 };
362
363 assert!(!row.is_null(0));
364 assert!(row.is_null(1));
365 assert!(row.is_null(99)); }
367}