6

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.

  • try removing the lifetime annotation, most probably you do not need it. – Netwave Mar 19 '21 at 10:50
  • Trait FromRow actually has specified lifetime `pub trait FromRow<'r, R: Row>: Sized` – Fellow Rustacean Mar 19 '21 at 10:57
  • @FellowRustacean that's for the case where the output just borrows row data (usually because one of the row items is decoded to a borrow e.g. `&str` or `&[u8]`) – Masklinn Mar 19 '21 at 11:16
  • @Netwave sqlx already implements FromRow for tuples, the entire point of implementing FromRow by hand would be to create a more specific structure, and thus return that. – Masklinn Mar 19 '21 at 11:17
  • 1
    @Netwave why would it be consumed by sqlx? It's `FromRow` not `ToRow`, it's the trait which converts records *out* of the database, and thus to application types. You could go through an intermediate tuple reification but... why? – Masklinn Mar 19 '21 at 11:21
  • @Masklinn, sorry, I understood it the other way around. In this case you could implement `From` for the tuple type returned by FromRow too. – Netwave Mar 19 '21 at 11:22
  • @Netwave sure but all it'd do is add verbosity and intermediate failure case e.g. now you'd need to handle failures in the conversion between a row and a tuple, and then failures in the conversion between a tuple and your own type (e.g. values which are out of range for your type). – Masklinn Mar 19 '21 at 11:25
  • @Masklinn, absolutely! – Netwave Mar 19 '21 at 11:28

1 Answers1

8

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).

kmdreko
  • 42,554
  • 6
  • 57
  • 106