tokio_postgres:将Json序列化为Vec<i32>

tokio_postgres: serialize Json into Vec<i32>

TLDR: 给定一个查询,其中 returns 行的列包含 Json 数组:

 id |  name   | post_ids 
----+---------+----------
 1  | JohnDoe | [1,2]

使用 tokio_postgres 将其序列化到包含 post_ids: Vec<i32> 字段的 User 模型中似乎是不可能的。

#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct User {
  pub id: String,
  pub name: String,
  pub post_ids: Vec<i32>
}

尝试为 i32 实现 tokio_postgres::types::FromSql,但它只允许用于结构。

我想知道这个 Json 和 Vec<i32> 之间的通用转换方式是什么。


带有完整代码的长版本:

我有一个带有 UserPost 模型的 Postgres 数据库:

 id |  name   
----+---------
  1 | JohnDoe
 id |    title    | user_id 
----+-------------+---------
  1 | first post  |       1
  2 | second post |       1

我有一个函数可以从数据库中检索用户和帖子。我知道我可以做得更好,但这是有意为之的,因为我的问题是关于此函数返回的 posts_ids

create function user_get_one(
  user_id int
)
returns table (
  "id"      text,
  "name"    text,
  post_ids  json
) AS $$
  select
    "user"."id",
    "user"."name",
    (select to_json(array_agg("posts"."id"))
      from (
        select
          "id"
        from
          "post"
        where
          "post"."user_id" = user_id
      ) posts
    ) posts_ids
  from "user"
  where
    "user".id = user_id;
$$ language sql;

这是一个包含所有数据库的 fiddle

现在,出于测试目的,我想要一个 Rust 中的 API 连接到此数据库,调用此函数,实例化适当的模型并在控制台中打印数据,不返回任何内容:

Cargo.toml:

[package]
name = "test"
version = "0.1.0"
edition = "2018"

[dependencies]
tokio-postgres = "0.7.5"
tokio = { version = "1.14.0", features = ["full"] }

进口:

use tokio_postgres::{NoTls, Row};

模特:

#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct User {
  pub id: String,
  pub name: String,
  // pub post_ids: Vec<i32>, // Uncomment
}

impl From<Row> for User {
  fn from(row: Row) -> Self {
    Self {
      id: row.get("id"),
      name: row.get("name"),
      // post_ids: row.get("post_ids"), // Uncomment
    }
  }
}

主要功能:

#[tokio::main]
async fn main() -> () {
  let (client, connection) = tokio_postgres::connect(
    "postgresql://localhost/rc_forum?user=test_user&password=secret_password ",
    NoTls,
  )
  .await
  .unwrap();

  tokio::spawn(async move {
    if let Err(e) = connection.await {
      eprintln!("connection error: {}", e);
    }
  });

  // Now we can execute a simple statement that just returns its parameter.
  let result = client
    .query_one("select * from user_get_one()", &[&1])
    .await
    .unwrap();

  let user: User = User::from(result);

  println!("-----------");
  println!("{:#?}", user);
  println!("-----------");
}

这里是 Rust playground 中的完整代码:https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=677a4f7710f6a29c7e33d3228679881f

现在,注释掉 posts_ids 后,用户已加载并实例化;但是如果我们取消注释 posts_idspostgres_tokio 恐慌,因为无法转换类型:

error retrieving column post_ids: error deserializing column 2: 
cannot convert between the Rust type `alloc::vec::Vec<i32>` and the Postgres type `json`',

这个Json和Vec<i32>的常用转换方式是什么?

我不太了解 Rust 或 tokio_postgres,但是 I gather RUST vector 基本上是一个可以增长大小的数组 -相当于 Postgres arrayVec<i32> 是一个 RUST 4 字节整数向量 。我认为这不能采用 Postgres json 值。使用整数 (int[]) 的 Postgres 数组代替:

在做的时候,我稍微简化了你的复杂函数:

CREATE FUNCTION user_get_one(_user_id int)
  RETURNS TABLE (id text, name text, post_ids int[])
  LANGUAGE sql AS
$func$
SELECT u.id
     , u.name
     , ARRAY(SELECT p.id FROM post p
             WHERE p.user_id = _user_id) AS posts_ids
FROM   "user" u  -- very unfortunate name!
WHERE  u.id = _user_id;
$func$;

db<>fiddle here

关于 ARRAY 构造函数:

我会在子查询中添加一个 ORDER BY 子句以获得确定性结果。否则,下一次调用可能会报告具有不同排序顺序的相同数组。所以:

...
 , ARRAY(SELECT p.id FROM post p
         WHERE p.user_id = _user_id ORDER BY 1) AS posts_ids
...

如果您已经一个JSON数组,下面是转换它的方法:

旁白:不要使用 reserved words 之类的“用户”作为 Postgres 标识符。