Fellow Rustacean
Fellow Rustacean

Reputation: 135

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.

Upvotes: 12

Views: 8138

Answers (2)

Nicolas Marshall
Nicolas Marshall

Reputation: 4466

To add to @kmdreco's answer:

If you want to implement it in a generic way over different types of databases, you could also do

use sqlx::{Row, FromRow};

struct User {
    name: String,
    status: i16,
}

impl<R: Row> FromRow<'_, R> for User {
  fn from_row(row: &R) -> Result<Self, sqlx::Error> {
    Ok(User {
      name: row.try_get("name")?,
      status: row.try_get("status")?,
    })
  }
}

still works as of sqlx ^0.7

For non-native field types, you'll get an error saying the type needs to implement some bound, add that exact bound to your trait under where

Upvotes: 0

kmdreko
kmdreko

Reputation: 60672

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

Upvotes: 14

Related Questions