12个版本

0.2.1 2024年5月25日
0.2.0 2024年2月16日
0.1.11-alpha2023年10月14日
0.1.8-alpha 2023年9月27日
0.1.0 2023年11月27日

#382 in 数据库接口

Download history 202/week @ 2024-05-24 16/week @ 2024-05-31 2/week @ 2024-06-07

1,631 每月下载量
用于 scyllax-cli

MIT/Apache

97KB
2K SLoC

scyllax (sɪl-æks)

Scylla的基于SQLx和Discord的查询系统。

discord codecov CI

示例

1. 模型定义

在编写任何查询之前,您必须定义一个模型。

#[entity]
pub struct PersonEntity {
    #[entity(primary_key)]
    pub id: uuid::Uuid,
    pub email: String,
    pub created_at: i64,
}

2. 读取查询

使用 read_query 属性,定义选择查询很容易。

#[read_query(
    query = "select * from person where id = :id limit 1",
    return_type = "PersonEntity"
)]
pub struct GetPersonById {
    pub id: Uuid,
}

3. Upsert查询

使用 upsert_query 属性,定义upsert查询很容易。

#[entity]
#[upsert_query(table = "person", name = UpsertPerson)]
pub struct PersonEntity {
    #[entity(primary_key)]
    pub id: uuid::Uuid,
    pub email: String,
    pub created_at: i64,
}

4. 查询集合

Scylla要求查询在执行之前必须准备。为了在启动时准备(并检查)所有查询,创建一个查询集合并将其传递给一个执行器。

create_query_collection!(
    PersonQueries,
    [
        GetPersonById,
        GetPersonByEmail
    ],
    [
        DeletePersonById,
        UpsertPerson
    ]
);

let executor = Executor::<PersonQueries>::new(Arc::new(session)).await;

let user = executor.execute_read(GetPersonByEmail {
    email: "[email protected]".to_string(),
}).await?;

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

特性

  • 读取查询
  • 写入查询 (https://github.com/trufflehq/scyllax/pull/1)
  • 请求合并
  • 编译时选择查询验证
    • 确保结构中存在where约束
    • 确保where约束与结构类型相同
  • 运行时查询验证(结构与模式匹配)

待办事项

  • 删除 anyhow,更精细的错误

使用方法

请参阅 示例 以获取更多详细信息。

参考资料

  1. https://www.reddit.com/r/rust/comments/11ki2n7/a_look_at_how_discord_uses_rust_for_their_data/jb8dmrx/
#[read_request(
    query = "select * from foo where id = ? limit 1",
    entity_type = "Foo"
)]
struct GetFooById {
    #[shard_key]
    id: i64
}
handle.execute_read(GetFooById { ... }).await

Jake的消息

尽管如此,与Scylla Rust包装器不同,我们不需要字段以正确的顺序排列才能使我们的东西正常工作。我们做了两件聪明的事情

  1. SELECT * 实际上是一个谎言。永远不要在预编译语句中使用 SELECT *,绝对不要。CQL 存在一个协议级别的漏洞,当在客户端和服务器之间存在架构不同步条件时,进行 SELECT * 操作可能导致架构更改时的数据损坏。因此,我们采取的做法是查看实体类型结构,并将 SELECT * 转换为 SELECT col_a, col_b, col_c。这意味着如果某个列存在于架构中,但不在我们要反序列化的结构中,我们实际上不会查询它。这个漏洞的实质是,当向表中添加新列时,数据库可能开始返回该列的数据,而客户端却对此一无所知。在极端情况下,这可能导致数据反序列化错位。请参阅 https://docs.datastax.com/en/developer/java-driver/3.0/manual/statements/prepared/#avoid-preparing-select-queries - 尽管这看起来最终在原生协议 v5 中得到了修复,但我不确定 Scylla 是否还在使用它。
  1. 对于查询参数的绑定,我们本质上解析 SQL 语句,找出所有的绑定位置,然后生成代码以按正确顺序(因为它们需要在查询中定义的顺序指定)绑定字段。我们通过在 proc 宏中编译时生成执行查询序列化的代码来实现这一点,因此我们不需要承担重新排序的运行时开销。

在启动时,我们准备一切,并使用代码中对数据库中的结构进行类型检查。手动注册一切都是可以的。如果你尝试使用未注册的查询,它可以在编译时失败。

依赖项

~12–23MB
~313K SLoC