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

如何用Sqlx将PostgreSQL的RECORD[]映射为Rust的Vec<_>结构体字段

解决方案

可以不用创建PostgreSQL自定义记录类型,通过JSON序列化嵌套数据的方式实现,这是最便捷的方案:

步骤1:修改SQL查询,用json_agg生成结构化JSON数组

把原来的ARRAY(SELECT ROW(...))替换成json_agg,同时用json_build_object确保JSON键与Rust结构体字段名严格对应:

SELECT
    accounts.id,
    accounts.created,
    accounts.private_key,
    accounts.public_key,
    json_agg(
        json_build_object('email', logins.email, 'oidc_id', logins.oidc_id)
    ) AS logins
FROM accounts
LEFT JOIN logins ON logins.account_id = accounts.id
GROUP BY accounts.id, accounts.created, accounts.private_key, accounts.public_key
  • 用LEFT JOIN保证无关联登录记录的账户也能被查询到(此时logins字段返回空数组[])
  • json_build_object生成与Login结构体字段匹配的键值对,避免匿名RECORD默认的f1/f2字段名导致反序列化失败

步骤2:调整Rust代码适配JSON映射

因为你的Login已派生Deserialize,Sqlx可直接将PostgreSQL的json类型映射为Vec<Login>,有两种实现方式:

方式A:在query_as!中显式指定字段类型

无需修改结构体,直接在查询宏里声明logins的目标类型:

let result = sqlx::query_as!(
    Account,
    r#"
        SELECT
            accounts.id,
            accounts.created,
            accounts.private_key,
            accounts.public_key,
            json_agg(json_build_object('email', logins.email, 'oidc_id', logins.oidc_id)) AS "logins: Vec<Login>"
        FROM accounts
        LEFT JOIN logins ON logins.account_id = accounts.id
        GROUP BY accounts.id, accounts.created, accounts.private_key, accounts.public_key
    "#
).fetch_all(pool).await?;
  • 通过"logins: Vec<Login>"的语法,告知Sqlx将该JSON字段反序列化为Vec<Login>

方式B:给结构体字段添加Sqlx类型注解

修改Account结构体的logins字段,标注它对应PostgreSQL的json类型:

use sqlx::types::Json;

#[derive(Serialize, Deserialize, sqlx::FromRow)]
struct Account {
    id: Uuid,
    created: NaiveDateTime,
    private_key: String,
    public_key: String,
    #[sqlx(type = "Json")]
    logins: Vec<Login>
}

之后查询时可省略类型声明,直接使用简化版query_as!:

let result = sqlx::query_as!(
    Account,
    r#"
        SELECT
            accounts.id,
            accounts.created,
            accounts.private_key,
            accounts.public_key,
            json_agg(json_build_object('email', logins.email, 'oidc_id', logins.oidc_id)) AS logins
        FROM accounts
        LEFT JOIN logins ON logins.account_id = accounts.id
        GROUP BY accounts.id, accounts.created, accounts.private_key, accounts.public_key
    "#
).fetch_all(pool).await?;

原方案失效原因

你之前用ARRAY(SELECT ROW(...))返回的是PostgreSQL匿名RECORD数组,这种类型没有明确的结构元数据,Sqlx无法自动映射到Vec<Login>——Sqlx需要明确知道RECORD的字段顺序、类型与结构体的对应关系,而匿名RECORD无法提供这些信息。JSON是结构化数据,只要字段名匹配,就能通过Serde自动完成反序列化。

内容的提问来源于stack exchange,提问作者Achim

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 06:30:22