Search code examples
rustrust-sqlx

How do I implement sqlx::FromRow trait maually?


I am trying to manually implement sqlx::FrowRow instead of using derive, since some custom initialization logic is needed (ex. convert integer i16 to enum).

Default impl is generated like this:

use sqlx::FromRow;

impl FromRow for User {
    fn from_row(row: &'r R) -> Result<Self, sqlx::Error> {
        todo!()
    }
}

But it's unclear, what lifetime and type should I use.


Solution

  • The FromRow trait is extremely generic since it is used for different backend databases that may support different types. You can see how many generic constraints are needed to implement a simple struct by looking at what #[derive(FromRow)] generates (via cargo expand):

    use sqlx::FromRow;
    
    #[derive(FromRow)]
    struct User {
        name: String,
        status: i16,
    }
    
    // generates
    
    impl<'a, R: ::sqlx::Row> ::sqlx::FromRow<'a, R> for User
    where
        &'a ::std::primitive::str: ::sqlx::ColumnIndex<R>,
        String: ::sqlx::decode::Decode<'a, R::Database>,
        String: ::sqlx::types::Type<R::Database>,
        i16: ::sqlx::decode::Decode<'a, R::Database>,
        i16: ::sqlx::types::Type<R::Database>,
    {
        fn from_row(row: &'a R) -> ::sqlx::Result<Self> {
            let name: String = row.try_get("name")?;
            let status: i16 = row.try_get("status")?;
            ::std::result::Result::Ok(User { name, status })
        }
    }
    

    It has to constrain that the columns can be indexed by name as well as constrain that String and i16 are valid and can be decoded from the database.

    Doing it yourself, you're probably better off picking which database Row type you intend to use (AnyRow, MssqlRow, MySqlRow, PgRow, SqliteRow) and implement it for that. Here using PgRow for postgres:

    use sqlx::{Row, FromRow, Error};
    use sqlx::postgres::PgRow;
    
    struct User {
        name: String,
        status: i16,
    }
    
    impl<'r> FromRow<'r, PgRow> for User {
        fn from_row(row: &'r PgRow) -> Result<Self, Error> {
            let name = row.try_get("name")?;
            let status = row.try_get("status")?;
    
            Ok(User{ name, status })
        }
    }
    

    I do wonder though if there's a different recommendation for doing custom transformations (via a DTO or some other mechanism).