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
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 #[test]
259 fn test_get_string() {
260 let row = PgRow {
261 columns: vec![Some(b"hello".to_vec()), None, Some(b"world".to_vec())],
262 column_info: None,
263 };
264
265 assert_eq!(row.get_string(0), Some("hello".to_string()));
266 assert_eq!(row.get_string(1), None);
267 assert_eq!(row.get_string(2), Some("world".to_string()));
268 }
269
270 #[test]
271 fn test_get_i32() {
272 let row = PgRow {
273 columns: vec![
274 Some(b"42".to_vec()),
275 Some(b"-123".to_vec()),
276 Some(b"not_a_number".to_vec()),
277 ],
278 column_info: None,
279 };
280
281 assert_eq!(row.get_i32(0), Some(42));
282 assert_eq!(row.get_i32(1), Some(-123));
283 assert_eq!(row.get_i32(2), None);
284 }
285
286 #[test]
287 fn test_get_bool() {
288 let row = PgRow {
289 columns: vec![
290 Some(b"t".to_vec()),
291 Some(b"f".to_vec()),
292 Some(b"true".to_vec()),
293 Some(b"false".to_vec()),
294 ],
295 column_info: None,
296 };
297
298 assert_eq!(row.get_bool(0), Some(true));
299 assert_eq!(row.get_bool(1), Some(false));
300 assert_eq!(row.get_bool(2), Some(true));
301 assert_eq!(row.get_bool(3), Some(false));
302 }
303
304 #[test]
305 fn test_is_null() {
306 let row = PgRow {
307 columns: vec![Some(b"value".to_vec()), None],
308 column_info: None,
309 };
310
311 assert!(!row.is_null(0));
312 assert!(row.is_null(1));
313 assert!(row.is_null(99)); }
315}