You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Rust SQLx中将Postgres透视表关系映射为Vec<Role>

解决SQLx映射Postgres复合类型数组到Vec的问题

方法一:拆分两次查询(最直观简单)

放弃单查询聚合数组的方式,分两步获取数据:

  1. 先查询用户基础信息
  2. 根据用户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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 05:35:09