1use std::borrow::Cow;
39use std::fmt;
40
41#[derive(Debug, Clone)]
43pub enum RowError {
44 ColumnNotFound(String),
46 TypeConversion { column: String, message: String },
48 UnexpectedNull(String),
50}
51
52impl fmt::Display for RowError {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 match self {
55 Self::ColumnNotFound(col) => write!(f, "column '{}' not found", col),
56 Self::TypeConversion { column, message } => {
57 write!(f, "type conversion error for '{}': {}", column, message)
58 }
59 Self::UnexpectedNull(col) => write!(f, "unexpected null in column '{}'", col),
60 }
61 }
62}
63
64impl std::error::Error for RowError {}
65
66pub trait RowRef {
71 fn get_i32(&self, column: &str) -> Result<i32, RowError>;
73
74 fn get_i32_opt(&self, column: &str) -> Result<Option<i32>, RowError>;
76
77 fn get_i64(&self, column: &str) -> Result<i64, RowError>;
79
80 fn get_i64_opt(&self, column: &str) -> Result<Option<i64>, RowError>;
82
83 fn get_f64(&self, column: &str) -> Result<f64, RowError>;
85
86 fn get_f64_opt(&self, column: &str) -> Result<Option<f64>, RowError>;
88
89 fn get_bool(&self, column: &str) -> Result<bool, RowError>;
91
92 fn get_bool_opt(&self, column: &str) -> Result<Option<bool>, RowError>;
94
95 fn get_str(&self, column: &str) -> Result<&str, RowError>;
100
101 fn get_str_opt(&self, column: &str) -> Result<Option<&str>, RowError>;
103
104 fn get_string(&self, column: &str) -> Result<String, RowError> {
106 self.get_str(column).map(|s| s.to_string())
107 }
108
109 fn get_string_opt(&self, column: &str) -> Result<Option<String>, RowError> {
111 self.get_str_opt(column)
112 .map(|opt| opt.map(|s| s.to_string()))
113 }
114
115 fn get_bytes(&self, column: &str) -> Result<&[u8], RowError>;
117
118 fn get_bytes_opt(&self, column: &str) -> Result<Option<&[u8]>, RowError>;
120
121 fn get_cow_str(&self, column: &str) -> Result<Cow<'_, str>, RowError> {
123 self.get_str(column).map(Cow::Borrowed)
124 }
125}
126
127pub trait FromRowRef<'a>: Sized {
132 fn from_row_ref(row: &'a impl RowRef) -> Result<Self, RowError>;
134}
135
136pub trait FromRow: Sized {
141 fn from_row(row: &impl RowRef) -> Result<Self, RowError>;
143}
144
145impl<T: FromRow> FromRowRef<'_> for T {
147 fn from_row_ref(row: &impl RowRef) -> Result<Self, RowError> {
148 T::from_row(row)
149 }
150}
151
152pub struct RowRefIter<'a, R: RowRef, T: FromRowRef<'a>> {
154 rows: std::slice::Iter<'a, R>,
155 _marker: std::marker::PhantomData<T>,
156}
157
158impl<'a, R: RowRef, T: FromRowRef<'a>> RowRefIter<'a, R, T> {
159 pub fn new(rows: &'a [R]) -> Self {
161 Self {
162 rows: rows.iter(),
163 _marker: std::marker::PhantomData,
164 }
165 }
166}
167
168impl<'a, R: RowRef, T: FromRowRef<'a>> Iterator for RowRefIter<'a, R, T> {
169 type Item = Result<T, RowError>;
170
171 fn next(&mut self) -> Option<Self::Item> {
172 self.rows.next().map(|row| T::from_row_ref(row))
173 }
174
175 fn size_hint(&self) -> (usize, Option<usize>) {
176 self.rows.size_hint()
177 }
178}
179
180impl<'a, R: RowRef, T: FromRowRef<'a>> ExactSizeIterator for RowRefIter<'a, R, T> {}
181
182#[derive(Debug, Clone)]
187pub enum RowData<'a> {
188 Borrowed(&'a str),
190 Owned(String),
192}
193
194impl<'a> RowData<'a> {
195 pub fn as_str(&self) -> &str {
197 match self {
198 Self::Borrowed(s) => s,
199 Self::Owned(s) => s,
200 }
201 }
202
203 pub fn into_owned(self) -> String {
205 match self {
206 Self::Borrowed(s) => s.to_string(),
207 Self::Owned(s) => s,
208 }
209 }
210
211 pub const fn borrowed(s: &'a str) -> Self {
213 Self::Borrowed(s)
214 }
215
216 pub fn owned(s: impl Into<String>) -> Self {
218 Self::Owned(s.into())
219 }
220}
221
222impl<'a> From<&'a str> for RowData<'a> {
223 fn from(s: &'a str) -> Self {
224 Self::Borrowed(s)
225 }
226}
227
228impl From<String> for RowData<'static> {
229 fn from(s: String) -> Self {
230 Self::Owned(s)
231 }
232}
233
234impl<'a> AsRef<str> for RowData<'a> {
235 fn as_ref(&self) -> &str {
236 self.as_str()
237 }
238}
239
240#[macro_export]
262macro_rules! impl_from_row {
263 ($type:ident { $($field:ident : i32),* $(,)? }) => {
264 impl $crate::row::FromRow for $type {
265 fn from_row(row: &impl $crate::row::RowRef) -> Result<Self, $crate::row::RowError> {
266 Ok(Self {
267 $(
268 $field: row.get_i32(stringify!($field))?,
269 )*
270 })
271 }
272 }
273 };
274 ($type:ident { $($field:ident : $field_type:ty),* $(,)? }) => {
275 impl $crate::row::FromRow for $type {
276 fn from_row(row: &impl $crate::row::RowRef) -> Result<Self, $crate::row::RowError> {
277 Ok(Self {
278 $(
279 $field: $crate::row::_get_typed_value::<$field_type>(row, stringify!($field))?,
280 )*
281 })
282 }
283 }
284 };
285}
286
287#[doc(hidden)]
289pub fn _get_typed_value<T: FromColumn>(row: &impl RowRef, column: &str) -> Result<T, RowError> {
290 T::from_column(row, column)
291}
292
293pub trait FromColumn: Sized {
295 fn from_column(row: &impl RowRef, column: &str) -> Result<Self, RowError>;
297}
298
299impl FromColumn for i32 {
300 fn from_column(row: &impl RowRef, column: &str) -> Result<Self, RowError> {
301 row.get_i32(column)
302 }
303}
304
305impl FromColumn for i64 {
306 fn from_column(row: &impl RowRef, column: &str) -> Result<Self, RowError> {
307 row.get_i64(column)
308 }
309}
310
311impl FromColumn for f64 {
312 fn from_column(row: &impl RowRef, column: &str) -> Result<Self, RowError> {
313 row.get_f64(column)
314 }
315}
316
317impl FromColumn for bool {
318 fn from_column(row: &impl RowRef, column: &str) -> Result<Self, RowError> {
319 row.get_bool(column)
320 }
321}
322
323impl FromColumn for String {
324 fn from_column(row: &impl RowRef, column: &str) -> Result<Self, RowError> {
325 row.get_string(column)
326 }
327}
328
329impl FromColumn for Option<i32> {
330 fn from_column(row: &impl RowRef, column: &str) -> Result<Self, RowError> {
331 row.get_i32_opt(column)
332 }
333}
334
335impl FromColumn for Option<i64> {
336 fn from_column(row: &impl RowRef, column: &str) -> Result<Self, RowError> {
337 row.get_i64_opt(column)
338 }
339}
340
341impl FromColumn for Option<f64> {
342 fn from_column(row: &impl RowRef, column: &str) -> Result<Self, RowError> {
343 row.get_f64_opt(column)
344 }
345}
346
347impl FromColumn for Option<bool> {
348 fn from_column(row: &impl RowRef, column: &str) -> Result<Self, RowError> {
349 row.get_bool_opt(column)
350 }
351}
352
353impl FromColumn for Option<String> {
354 fn from_column(row: &impl RowRef, column: &str) -> Result<Self, RowError> {
355 row.get_string_opt(column)
356 }
357}
358
359impl FromColumn for Vec<u8> {
360 fn from_column(row: &impl RowRef, column: &str) -> Result<Self, RowError> {
361 row.get_bytes(column).map(|b| b.to_vec())
362 }
363}
364
365impl FromColumn for Option<Vec<u8>> {
366 fn from_column(row: &impl RowRef, column: &str) -> Result<Self, RowError> {
367 row.get_bytes_opt(column).map(|opt| opt.map(|b| b.to_vec()))
368 }
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374
375 struct MockRow {
377 data: std::collections::HashMap<String, String>,
378 }
379
380 impl RowRef for MockRow {
381 fn get_i32(&self, column: &str) -> Result<i32, RowError> {
382 self.data
383 .get(column)
384 .ok_or_else(|| RowError::ColumnNotFound(column.to_string()))?
385 .parse()
386 .map_err(|e| RowError::TypeConversion {
387 column: column.to_string(),
388 message: format!("{}", e),
389 })
390 }
391
392 fn get_i32_opt(&self, column: &str) -> Result<Option<i32>, RowError> {
393 match self.data.get(column) {
394 Some(v) if v == "NULL" => Ok(None),
395 Some(v) => v.parse().map(Some).map_err(|e| RowError::TypeConversion {
396 column: column.to_string(),
397 message: format!("{}", e),
398 }),
399 None => Ok(None),
400 }
401 }
402
403 fn get_i64(&self, column: &str) -> Result<i64, RowError> {
404 self.data
405 .get(column)
406 .ok_or_else(|| RowError::ColumnNotFound(column.to_string()))?
407 .parse()
408 .map_err(|e| RowError::TypeConversion {
409 column: column.to_string(),
410 message: format!("{}", e),
411 })
412 }
413
414 fn get_i64_opt(&self, column: &str) -> Result<Option<i64>, RowError> {
415 match self.data.get(column) {
416 Some(v) if v == "NULL" => Ok(None),
417 Some(v) => v.parse().map(Some).map_err(|e| RowError::TypeConversion {
418 column: column.to_string(),
419 message: format!("{}", e),
420 }),
421 None => Ok(None),
422 }
423 }
424
425 fn get_f64(&self, column: &str) -> Result<f64, RowError> {
426 self.data
427 .get(column)
428 .ok_or_else(|| RowError::ColumnNotFound(column.to_string()))?
429 .parse()
430 .map_err(|e| RowError::TypeConversion {
431 column: column.to_string(),
432 message: format!("{}", e),
433 })
434 }
435
436 fn get_f64_opt(&self, column: &str) -> Result<Option<f64>, RowError> {
437 match self.data.get(column) {
438 Some(v) if v == "NULL" => Ok(None),
439 Some(v) => v.parse().map(Some).map_err(|e| RowError::TypeConversion {
440 column: column.to_string(),
441 message: format!("{}", e),
442 }),
443 None => Ok(None),
444 }
445 }
446
447 fn get_bool(&self, column: &str) -> Result<bool, RowError> {
448 let v = self
449 .data
450 .get(column)
451 .ok_or_else(|| RowError::ColumnNotFound(column.to_string()))?;
452 match v.as_str() {
453 "true" | "t" | "1" => Ok(true),
454 "false" | "f" | "0" => Ok(false),
455 _ => Err(RowError::TypeConversion {
456 column: column.to_string(),
457 message: "invalid boolean".to_string(),
458 }),
459 }
460 }
461
462 fn get_bool_opt(&self, column: &str) -> Result<Option<bool>, RowError> {
463 match self.data.get(column) {
464 Some(v) if v == "NULL" => Ok(None),
465 Some(v) => match v.as_str() {
466 "true" | "t" | "1" => Ok(Some(true)),
467 "false" | "f" | "0" => Ok(Some(false)),
468 _ => Err(RowError::TypeConversion {
469 column: column.to_string(),
470 message: "invalid boolean".to_string(),
471 }),
472 },
473 None => Ok(None),
474 }
475 }
476
477 fn get_str(&self, column: &str) -> Result<&str, RowError> {
478 self.data
479 .get(column)
480 .map(|s| s.as_str())
481 .ok_or_else(|| RowError::ColumnNotFound(column.to_string()))
482 }
483
484 fn get_str_opt(&self, column: &str) -> Result<Option<&str>, RowError> {
485 match self.data.get(column) {
486 Some(v) if v == "NULL" => Ok(None),
487 Some(v) => Ok(Some(v.as_str())),
488 None => Ok(None),
489 }
490 }
491
492 fn get_bytes(&self, column: &str) -> Result<&[u8], RowError> {
493 self.data
494 .get(column)
495 .map(|s| s.as_bytes())
496 .ok_or_else(|| RowError::ColumnNotFound(column.to_string()))
497 }
498
499 fn get_bytes_opt(&self, column: &str) -> Result<Option<&[u8]>, RowError> {
500 match self.data.get(column) {
501 Some(v) if v == "NULL" => Ok(None),
502 Some(v) => Ok(Some(v.as_bytes())),
503 None => Ok(None),
504 }
505 }
506 }
507
508 #[test]
509 fn test_row_ref_get_i32() {
510 let mut data = std::collections::HashMap::new();
511 data.insert("id".to_string(), "42".to_string());
512 let row = MockRow { data };
513
514 assert_eq!(row.get_i32("id").unwrap(), 42);
515 }
516
517 #[test]
518 fn test_row_ref_get_str_zero_copy() {
519 let mut data = std::collections::HashMap::new();
520 data.insert("email".to_string(), "test@example.com".to_string());
521 let row = MockRow { data };
522
523 let email = row.get_str("email").unwrap();
524 assert_eq!(email, "test@example.com");
525 }
528
529 #[test]
530 fn test_row_data() {
531 let borrowed: RowData = RowData::borrowed("hello");
532 assert_eq!(borrowed.as_str(), "hello");
533
534 let owned: RowData = RowData::owned("world".to_string());
535 assert_eq!(owned.as_str(), "world");
536 }
537}
538
539