我正在使用actix-web,SQLx和PostgreSQL编写一个用Rust编写的REST API用于存储。让我们假设这是我的schema(表示为Rust结构):
struct User {
pub id: Uuid,
pub email: String
// And so on...
}
struct Customer {
pub id: Uuid,
pub user_id: Uuid,
pub name: String,
// And so on...
}
我目前的目标是实现一个端点,它返回嵌套了客户的所有用户。例如:
// GET /users
// Response from endpoint
[{
"id": "uuid-1",
"email": "test@test.com",
"customers": [{
"id": "uuid-customer-1",
"name": "Customer 1"
}, {
"id": "uuid-customer-2",
"name": "Customer 2"
}]
}]
上面的有效载荷可以使用以下结构来表示:
#[derive(Serialize)]
struct CustomerData {
pub id: Uuid,
pub name: String
}
#[derive(Serialize)]
struct UserData {
pub id: Uuid,
pub email: String,
pub customers: Vec<CustomerData>
}
使用SQLx宏query_as!
,我提出了以下解决方案尝试:
let result = sqlx::query_as!(
UserData,
r#"
SELECT U.id, U.email, array_agg((C.id, C.name)) as "customers" FROM users U
INNER JOIN customers C ON user_id = U.id
GROUP BY U.id
"#
)
.fetch_all(pool.as_ref())
.await?;
但是,这会失败,因为array_agg
返回的结果是RECORD[]
类型,SQLx显然还不支持该类型。
这个问题让我想知道:
- 有没有办法让SQLx正确地将
array_agg
的结果Map到customers
? - 假设我的实际模式在层次结构中至少有几个级别,那么这是正确的方法吗?将其拆分为几个查询并分段构造响应会“更好”吗?
1条答案
按热度按时间x9ybnkn61#
提前解释; TL;DR在底部。
经过更多的挖掘,我找到了一个解决方案,一旦你习惯了SQLx和Rust,这个解决方案实际上是非常明显的。
因此,问题是SQLx认为
ARRAY_AGG()
的返回值是RECORD[]
类型。幸运的是,SQLX允许我们通过DSL中的类型转换来判断我们期望的类型。因此,为了解决这个问题,我们首先需要在查询中将
RECORD[]
转换为Vec<CustomerData>
,可以这样做:此外,我们需要为
CustomerData
实现Traitsqlx::Type
。幸运的是,有一个宏可以做到这一点:最后但并非最不重要的是,还有一个问题需要解决:
ARRAY_AGG
返回NULL
或数组。这可以通过三种方式来解决:Option<T>
。因此,字段customers
在Struct中的类型应为Option<Vec<CustomerData>>
。... as "customers!: Vec<CustomerData>
ARRAY_AGG
返回NULL时,使用SQL返回一个空数组(可以找到其他解决方案here):如果层次结构深度不超过1,则此解决方案有效。如果嵌套得更深,则会因为SQLx的类型解析器中的bug而碰壁。关于这个问题可以在这里找到。
TL;DR:
sqlx::Type
。