Repository: blackbeam/rust-mysql-simple (language: rust)

Issue:
v18 exec/query_first/iter with from_row_opt
Hi, is there a way to execute all the auto-converting methods without panicking, but instead throwing errors if conversions fails ?
AFAIK you have to create a prepared statement, then return a Row and apply from_row_opt on that. That is much more verbose than previously. Would be nice to have the nice new auto-converting functions to error instead of panicking internally.

For my server stuff I always have this kind of defensive programming, even if something fails I rather have it error out instead of panicking. This way other stuff running in the same process isn't affected from one failure / task.


Discussion / hints:

Fix patch:
diff --git a/src/conn/query.rs b/src/conn/query.rs
index 24fd612c..b4a64984 100644
--- a/src/conn/query.rs
+++ b/src/conn/query.rs
@@ -6,11 +6,13 @@
 // option. All files in the project carrying such notice may not be copied,
 // modified, or distributed except according to those terms.
 
-use std::convert::TryInto;
+use mysql_common::row::convert::FromRowError;
+
+use std::{convert::TryInto, result::Result as StdResult};
 
 use crate::{
     conn::{queryable::AsStatement, ConnMut},
-    from_row,
+    from_row, from_row_opt,
     prelude::FromRow,
     Binary, Error, Params, QueryResult, Result, Text,
 };
@@ -53,6 +55,19 @@ pub trait TextQuery: Sized {
             .transpose()
     }
 
+    /// Same as [`TextQuery::first`] but useful when you not sure what your schema is.
+    fn first_opt<'a, 'b, 'c: 'b, T, C>(self, conn: C) -> Result<Option<StdResult<T, FromRowError>>>
+    where
+        C: TryInto<ConnMut<'a, 'b, 'c>>,
+        Error: From<<C as TryInto<ConnMut<'a, 'b, 'c>>>::Error>,
+        T: FromRow,
+    {
+        self.run(conn)?
+            .next()
+            .map(|row| row.map(from_row_opt))
+            .transpose()
+    }
+
     /// This methods corresponds to `Queryable::query`.
     fn fetch<'a, 'b, 'c: 'b, T, C>(self, conn: C) -> Result<Vec<T>>
     where
@@ -63,6 +78,16 @@ pub trait TextQuery: Sized {
         self.run(conn)?.map(|rrow| rrow.map(from_row)).collect()
     }
 
+    /// Same as [`TextQuery::fetch`] but useful when you not sure what your schema is.
+    fn fetch_opt<'a, 'b, 'c: 'b, T, C>(self, conn: C) -> Result<Vec<StdResult<T, FromRowError>>>
+    where
+        C: TryInto<ConnMut<'a, 'b, 'c>>,
+        Error: From<<C as TryInto<ConnMut<'a, 'b, 'c>>>::Error>,
+        T: FromRow,
+    {
+        self.run(conn)?.map(|rrow| rrow.map(from_row_opt)).collect()
+    }
+
     /// This methods corresponds to `Queryable::query_fold`.
     fn fold<'a, 'b, 'c: 'b, T, U, F, C>(self, conn: C, mut init: U, mut next: F) -> Result<U>
     where
@@ -78,6 +103,21 @@ pub trait TextQuery: Sized {
         Ok(init)
     }
 
+    /// Same as [`TextQuery::fold`] but useful when you not sure what your schema is.
+    fn fold_opt<'a, 'b, 'c: 'b, T, U, F, C>(self, conn: C, mut init: U, mut next: F) -> Result<U>
+    where
+        C: TryInto<ConnMut<'a, 'b, 'c>>,
+        Error: From<<C as TryInto<ConnMut<'a, 'b, 'c>>>::Error>,
+        T: FromRow,
+        F: FnMut(U, StdResult<T, FromRowError>) -> U,
+    {
+        for row in self.run(conn)? {
+            init = next(init, from_row_opt(row?));
+        }
+
+        Ok(init)
+    }
+
     /// This methods corresponds to `Queryable::query_map`.
     fn map<'a, 'b, 'c: 'b, T, U, F, C>(self, conn: C, mut map: F) -> Result<Vec<U>>
     where
@@ -91,6 +131,24 @@ pub trait TextQuery: Sized {
             acc
         })
     }
+
+    /// Same as [`TextQuery::map`] but useful when you not sure what your schema is.
+    fn map_opt<'a, 'b, 'c: 'b, T, U, F, C>(self, conn: C, mut map: F) -> Result<Vec<U>>
+    where
+        C: TryInto<ConnMut<'a, 'b, 'c>>,
+        Error: From<<C as TryInto<ConnMut<'a, 'b, 'c>>>::Error>,
+        T: FromRow,
+        F: FnMut(StdResult<T, FromRowError>) -> U,
+    {
+        self.fold_opt(
+            conn,
+            Vec::new(),
+            |mut acc, row: StdResult<T, FromRowError>| {
+                acc.push(map(row));
+                acc
+            },
+        )
+    }
 }
 
 impl<Q: AsRef<str>> TextQuery for Q {
@@ -168,6 +226,19 @@ pub trait BinQuery: Sized {
             .transpose()
     }
 
+    /// Same as [`BinQuery::first`] but useful when you not sure what your schema is.
+    fn first_opt<'a, 'b, 'c: 'b, T, C>(self, conn: C) -> Result<Option<StdResult<T, FromRowError>>>
+    where
+        C: TryInto<ConnMut<'a, 'b, 'c>>,
+        Error: From<<C as TryInto<ConnMut<'a, 'b, 'c>>>::Error>,
+        T: FromRow,
+    {
+        self.run(conn)?
+            .next()
+            .map(|row| row.map(from_row_opt))
+            .transpose()
+    }
+
     /// This methods corresponds to `Queryable::exec`.
     fn fetch<'a, 'b, 'c: 'b, T, C>(self, conn: C) -> Result<Vec<T>>
     where
@@ -178,6 +249,16 @@ pub trait BinQuery: Sized {
         self.run(conn)?.map(|rrow| rrow.map(from_row)).collect()
     }
 
+    /// Same as [`BinQuery::fetch`] but useful when you not sure what your schema is.
+    fn fetch_opt<'a, 'b, 'c: 'b, T, C>(self, conn: C) -> Result<Vec<StdResult<T, FromRowError>>>
+    where
+        C: TryInto<ConnMut<'a, 'b, 'c>>,
+        Error: From<<C as TryInto<ConnMut<'a, 'b, 'c>>>::Error>,
+        T: FromRow,
+    {
+        self.run(conn)?.map(|rrow| rrow.map(from_row_opt)).collect()
+    }
+
     /// This methods corresponds to `Queryable::exec_fold`.
     fn fold<'a, 'b, 'c: 'b, T, U, F, C>(self, conn: C, mut init: U, mut next: F) -> Result<U>
     where
@@ -193,6 +274,21 @@ pub trait BinQuery: Sized {
         Ok(init)
     }
 
+    /// Same as [`BinQuery::fold`] but useful when you not sure what your schema is.
+    fn fold_opt<'a, 'b, 'c: 'b, T, U, F, C>(self, conn: C, mut init: U, mut next: F) -> Result<U>
+    where
+        C: TryInto<ConnMut<'a, 'b, 'c>>,
+        Error: From<<C as TryInto<ConnMut<'a, 'b, 'c>>>::Error>,
+        T: FromRow,
+        F: FnMut(U, StdResult<T, FromRowError>) -> U,
+    {
+        for row in self.run(conn)? {
+            init = next(init, from_row_opt(row?));
+        }
+
+        Ok(init)
+    }
+
     /// This methods corresponds to `Queryable::exec_map`.
     fn map<'a, 'b, 'c: 'b, T, U, F, C>(self, conn: C, mut map: F) -> Result<Vec<U>>
     where
@@ -206,6 +302,24 @@ pub trait BinQuery: Sized {
             acc
         })
     }
+
+    /// Same as [`BinQuery::map`] but useful when you not sure what your schema is.
+    fn map_opt<'a, 'b, 'c: 'b, T, U, F, C>(self, conn: C, mut map: F) -> Result<Vec<U>>
+    where
+        C: TryInto<ConnMut<'a, 'b, 'c>>,
+        Error: From<<C as TryInto<ConnMut<'a, 'b, 'c>>>::Error>,
+        T: FromRow,
+        F: FnMut(StdResult<T, FromRowError>) -> U,
+    {
+        self.fold_opt(
+            conn,
+            Vec::new(),
+            |mut acc, row: StdResult<T, FromRowError>| {
+                acc.push(map(row));
+                acc
+            },
+        )
+    }
 }
 
 impl<Q, P> BinQuery for QueryWithParams<Q, P>
diff --git a/src/conn/queryable.rs b/src/conn/queryable.rs
index b0bd2f8a..4f2d6d65 100644
--- a/src/conn/queryable.rs
+++ b/src/conn/queryable.rs
@@ -6,11 +6,13 @@
 // option. All files in the project carrying such notice may not be copied,
 // modified, or distributed except according to those terms.
 
-use std::borrow::Cow;
+use mysql_common::row::convert::FromRowError;
+
+use std::{borrow::Cow, result::Result as StdResult};
 
 use crate::{
     conn::query_result::{Binary, Text},
-    from_row,
+    from_row, from_row_opt,
     prelude::FromRow,
     Params, QueryResult, Result, Statement,
 };
@@ -35,6 +37,15 @@ pub trait Queryable {
         self.query_map(query, from_row)
     }
 
+    /// Same as [`Queryable::query`] but useful when you not sure what your schema is.
+    fn query_opt<T, Q>(&mut self, query: Q) -> Result<Vec<StdResult<T, FromRowError>>>
+    where
+        Q: AsRef<str>,
+        T: FromRow,
+    {
+        self.query_map(query, from_row_opt)
+    }
+
     /// Performs text query and returns the firt row of the first result set.
     fn query_first<T, Q>(&mut self, query: Q) -> Result<Option<T>>
     where
@@ -43,7 +54,19 @@ pub trait Queryable {
     {
         self.query_iter(query)?
             .next()
-            .map(|row| row.map(crate::from_row))
+            .map(|row| row.map(from_row))
+            .transpose()
+    }
+
+    /// Same as [`Queryable::query_first`] but useful when you not sure what your schema is.
+    fn query_first_opt<T, Q>(&mut self, query: Q) -> Result<Option<StdResult<T, FromRowError>>>
+    where
+        Q: AsRef<str>,
+        T: FromRow,
+    {
+        self.query_iter(query)?
+            .next()
+            .map(|row| row.map(from_row_opt))
             .transpose()
     }
 
@@ -60,6 +83,19 @@ pub trait Queryable {
         })
     }
 
+    /// Same as [`Queryable::query_map`] but useful when you not sure what your schema is.
+    fn query_map_opt<T, F, Q, U>(&mut self, query: Q, mut f: F) -> Result<Vec<U>>
+    where
+        Q: AsRef<str>,
+        T: FromRow,
+        F: FnMut(StdResult<T, FromRowError>) -> U,
+    {
+        self.query_fold_opt(query, Vec::new(), |mut acc, row| {
+            acc.push(f(row));
+            acc
+        })
+    }
+
     /// Performs text query and folds the first result set to a single value.
     fn query_fold<T, F, Q, U>(&mut self, query: Q, init: U, mut f: F) -> Result<U>
     where
@@ -72,6 +108,20 @@ pub trait Queryable {
             .try_fold(init, |acc, row: Result<T>| row.map(|row| f(acc, row)))
     }
 
+    /// Same as [`Queryable::query_fold`] but useful when you not sure what your schema is.
+    fn query_fold_opt<T, F, Q, U>(&mut self, query: Q, init: U, mut f: F) -> Result<U>
+    where
+        Q: AsRef<str>,
+        T: FromRow,
+        F: FnMut(U, StdResult<T, FromRowError>) -> U,
+    {
+        self.query_iter(query)?
+            .map(|row| row.map(from_row_opt::<T>))
+            .try_fold(init, |acc, row: Result<StdResult<T, FromRowError>>| {
+                row.map(|row| f(acc, row))
+            })
+    }
+
     /// Performs text query and drops the query result.
     fn query_drop<Q>(&mut self, query: Q) -> Result<()>
     where
@@ -115,7 +165,17 @@ pub trait Queryable {
         P: Into<Params>,
         T: FromRow,
     {
-        self.exec_map(stmt, params, crate::from_row)
+        self.exec_map(stmt, params, from_row)
+    }
+
+    /// Same as [`Queryable::exec`] but useful when you not sure what your schema is.
+    fn exec_opt<T, S, P>(&mut self, stmt: S, params: P) -> Result<Vec<StdResult<T, FromRowError>>>
+    where
+        S: AsStatement,
+        P: Into<Params>,
+        T: FromRow,
+    {
+        self.exec_map(stmt, params, from_row_opt)
     }
 
     /// Exectues the given `stmt` and returns the first row of the first result set.
@@ -131,6 +191,23 @@ pub trait Queryable {
             .transpose()
     }
 
+    /// Same as [`Queryable::exec_first`] but useful when you not sure what your schema is.
+    fn exec_first_opt<T, S, P>(
+        &mut self,
+        stmt: S,
+        params: P,
+    ) -> Result<Option<StdResult<T, FromRowError>>>
+    where
+        S: AsStatement,
+        P: Into<Params>,
+        T: FromRow,
+    {
+        self.exec_iter(stmt, params)?
+            .next()
+            .map(|row| row.map(from_row_opt))
+            .transpose()
+    }
+
     /// Exectues the given `stmt` and maps each row of the first result set.
     fn exec_map<T, S, P, F, U>(&mut self, stmt: S, params: P, mut f: F) -> Result<Vec<U>>
     where
@@ -145,6 +222,20 @@ pub trait Queryable {
         })
     }
 
+    /// Same as [`Queryable::exec_map`] but useful when you not sure what your schema is.
+    fn exec_map_opt<T, S, P, F, U>(&mut self, stmt: S, params: P, mut f: F) -> Result<Vec<U>>
+    where
+        S: AsStatement,
+        P: Into<Params>,
+        T: FromRow,
+        F: FnMut(StdResult<T, FromRowError>) -> U,
+    {
+        self.exec_fold_opt(stmt, params, Vec::new(), |mut acc, row| {
+            acc.push(f(row));
+            acc
+        })
+    }
+
     /// Exectues the given `stmt` and folds the first result set to a signel value.
     fn exec_fold<T, S, P, U, F>(&mut self, stmt: S, params: P, init: U, mut f: F) -> Result<U>
     where
@@ -158,6 +249,19 @@ pub trait Queryable {
         output
     }
 
+    /// Same as [`Queryable::exec_fold`] but useful when you not sure what your schema is.
+    fn exec_fold_opt<T, S, P, U, F>(&mut self, stmt: S, params: P, init: U, mut f: F) -> Result<U>
+    where
+        S: AsStatement,
+        P: Into<Params>,
+        T: FromRow,
+        F: FnMut(U, StdResult<T, FromRowError>) -> U,
+    {
+        let mut result = self.exec_iter(stmt, params)?;
+        let output = result.try_fold(init, |init, row| row.map(|row| f(init, from_row_opt(row))));
+        output
+    }
+
     /// Exectues the given `stmt` and drops the result.
     fn exec_drop<S, P>(&mut self, stmt: S, params: P) -> Result<()>
     where


Test patch: