I have a user obj
#[derive(Serialize, Deserialize, FromRow)]
pub struct User {
pub uid: Uuid,
pub name: String,
pub username: Option<String>,
pub email: Option<String>,
pub provider: String,
*.... other fields*
}
And a role obj
#[derive(Serialize, Deserialize, FromRow)]
pub struct Role {
pub uid: Uuid,
pub name: String,
}
I want to get a user with its roles Vec<Role>
like :
struct User {
user: User,
roles: Vec<Role>
}
this is my Postgres SQL
let user_with_roles: UserWithRoles = sqlx::query(
r#"
SELECT users.*, ARRAY_AGG(roles.*) AS roles of type Vec<Role>
FROM users
JOIN users_roles ON users_roles.user_uid = users.uid
JOIN roles ON users_roles.role_uid = roles.uid
WHERE users.uid = $1
GROUP BY users.uid;
"#,
)
.bind(self.uid)
.fetch_one(db)
.await?;
And this from_row
#[derive(Serialize, Deserialize)]
pub struct UserWithRoles {
pub user: User,
pub roles: Vec<Role>,
}
impl FromRow<'_, PgRow> for UserWithRoles {
fn from_row(row: &PgRow) -> sqlx::Result<Self> {
let user = User {
uid: row.get("uid"),
name: row.get("name"),
username: row.get("username"),
email: row.get("email"),
provider: row.get("provider"),
*.... other fields*
};
let roles: Vec<Role> = row.get("roles");
Ok(Self { user, roles })
}
}
I have this err :
error[E0277]: the trait bound `Role: PgHasArrayType` is not satisfied
--> src/users.rs:168:36
|
168 | ... = row.get("roles");
| ^^^ the trait `PgHasArrayType` is not implemented for `Role`
|
= help: the following other types implement trait `PgHasArrayType`:
bool
i8
i16
i32
i64
u8
f32
f64
and 41 others
= note: required for `Vec<Role>` to implement `sqlx::Type<Postgres>
How to make mapping for Vec<Role>
in rust SQLx crate ?
as I understand SQLx does not understand the array returned from Postgres. and I tried from_row
to map each to other BUT failed .
After 3 days of search and a lot of trait impl suggestions which are complex for me and do not work .. because I think this is a common need and should done easily ..
This will work for complex relations that has pivot tabla ( one to many ) or ( many to many ).
the solution is very simple :
Vec
you please.Here is the code :
pub async fn with_roles(&self, state: &AppState) -> Result<UserWithRoles> {
let row = sqlx::query(
r#"
SELECT users.*, JSON_AGG(roles.*) AS roles
FROM users
JOIN users_roles ON users_roles.user_uid = users.uid
JOIN roles ON users_roles.role_uid = roles.uid
WHERE users.uid = $1
GROUP BY users.uid;
"#,
)
.bind(self.uid)
.fetch_one(&state.db)
.await?;
let user = User {
uid: row.get("uid"),
name: row.get("name"),
username: row.get("username"),
email: row.get("email"),
provider: row.get("provider"),
password: None,
access_token: None,
refresh_token: None,
created_at: row.get("created_at"),
updated_at: row.get("updated_at"),
deleted_at: row.get("deleted_at"),
last_sign: row.get("last_sign"),
};
// Here is the trick ---v
let roles: Vec<Role> = serde_json::from_value(row.get("roles")).unwrap();
Ok(UserWithRoles { user, roles })
}
Code for the Sruct
I am mapping for
#[derive(Serialize, Deserialize, Debug)]
pub struct UserWithRoles {
pub user: User,
pub roles: Vec<Role>,
}
Edit: Another better solution:
is to impl FromRow
after adding Derive(Default, FromRow)
to the User
and Role
structs.
Note that I have changed Vec to be Option<Vec> which is better because sometimes User has no roles. I also used Json type from Sqlx.
Default trait is needed especially if you have Options in your struct.
impl FromRow<'_, PgRow> for UserWithRoles {
fn from_row(row: &PgRow) -> sqlx::Result<Self> {
let user: User = User::from_row(row).expect("deser user failed");
let roles = row
.try_get::<Json<Vec<Role>>, _>("roles")
.map(|x| if x.is_empty() { None } else { Some(x.0) })
.unwrap_or(None);
println!("{:?}", roles);
Ok(Self { user, roles })
}
}
This feature is needed :
serde_json = { version = "1.0.114", features = ["raw_value"] }
Note we do not use query_as!
macro because - as per docs - it does not depend on FromRow
.
Note that: Mapping SQL results is not as easy as I thought. But here I introduced some ideas, may it helps.