如何在Rust SQLx中将Postgres透视表关系映射为Vec<Role>
解决SQLx映射Postgres复合类型数组到Vec的问题
方法一:拆分两次查询(最直观简单)
放弃单查询聚合数组的方式,分两步获取数据:
- 先查询用户基础信息
- 根据用户UID查询关联的角色列表
代码示例:
// 查询用户信息 let user: User = sqlx::query_as!( User, r#"SELECT * FROM users WHERE uid = $1"#, self.uid ) .fetch_one(db) .await?; // 查询该用户的角色列表 let roles: Vec<Role> = sqlx::query_as!( Role, r#" SELECT roles.* FROM roles JOIN users_roles ON users_roles.role_uid = roles.uid WHERE users_roles.user_uid = $1 "#, self.uid ) .fetch_all(db) .await?; // 组合成目标结构 let user_with_roles = UserWithRoles { user, roles };
这种方式无需处理复杂的数组映射逻辑,代码可读性高,适合大多数业务场景。
方法二:手动解析Postgres复合类型数组
如果坚持使用单查询聚合,可先将数组解析为元组列表,再转换为Vec<Role>:
Postgres中ARRAY_AGG(roles.*)返回的是复合类型数组,我们可以先映射为Vec<(Uuid, String)>,再转换成Role结构体。
修改查询与FromRow实现:
// 修正查询语句,移除错误的类型标注 let user_with_roles: UserWithRoles = sqlx::query( r#" SELECT users.*, ARRAY_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(db) .await?; // 重新实现FromRow 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"), // 填充剩余字段,可使用..Default::default()(需User实现Default) ..User::default() }; // 先解析为元组,再转换为Role let role_tuples: Vec<(Uuid, String)> = row.get("roles"); let roles = role_tuples.into_iter() .map(|(uid, name)| Role { uid, name }) .collect(); Ok(Self { user, roles }) } }
注意:需保证Postgres中roles表的字段顺序与(Uuid, String)一致(即先uid后name)。
方法三:为Role实现SQLx相关Trait(进阶)
若想让SQLx直接支持Vec<Role>的解析,需为Role实现PgHasArrayType、Decode和Type trait:
use sqlx::{postgres::{PgHasArrayType, PgTypeInfo}, Decode, Postgres, Type}; use uuid::Uuid; // 指定Postgres中Role对应的数组类型 impl PgHasArrayType for Role { fn array_type_info() -> PgTypeInfo { // 替换为你数据库中roles表对应的复合类型数组名,通常为"role[]" PgTypeInfo::with_name("role[]") } } // 实现单个Role复合类型的解析逻辑 impl<'r> Decode<'r, Postgres> for Role { fn decode(value: sqlx::postgres::PgValueRef<'r>) -> sqlx::Result<Self> { let (uid, name): (Uuid, String) = Decode::decode(value)?; Ok(Role { uid, name }) } } // 指定Role对应的Postgres复合类型名 impl Type<Postgres> for Role { fn type_info() -> PgTypeInfo { // 替换为你数据库中roles表对应的复合类型名,通常为"role" PgTypeInfo::with_name("role") } }
完成以上实现后,原代码中row.get("roles")即可直接获取Vec<Role>。可通过Postgres查询语句SELECT typname FROM pg_type WHERE typrelid = 'roles'::regclass;确认复合类型名称。
内容的提问来源于stack exchange,提问作者mercury
相关产品推荐
相关产品推荐

