熟悉数据库
笔者对Postgres数据库以及sqlx等相关概念不是很熟悉,对客户端而言,唯一熟悉的数据库大概就是SQLite和一些简单的SQL语法,再高级一点,就是Sqlite中的cursor的应用,所以笔者花了大概三周的时间,学习了Postgres数据库基础。
但这是一个庞大的课题,我作为一个入门者,确实没有太多有用的知识来分享,我的切入点是,在Appflow Cloud这个项目中,遇到的不懂的点,以及对应概念的自我认知,目标是可以进行正常开发。
第一步 Postgres简单概念
- 概念,我很喜欢这个词汇,他总能概括的表述自己,简单明了,易于举一反三;
Postgres是什么
Postgres是一个开源的数据库,相较于流行的MYSQL,它更复杂,有更多的规则,类似权限管理, 用户管理。更复杂的函数,有自己的语言plpgsql,同时支持C++,Java,Python作为函数的可选语言。支持NoSql, 例如Jsonb等,同时性能也比MySQL要好。同时对大型数据库的支持也很好,支持Physical Replication和Logical Replication.
Appflow使用Postgres的原因笔者感觉是因为性能和NoSql的支持,协同文档类数据,这些是比较重要的特性,后续随着理解的深入,笔者会填充这部分原因。
Postgres基础配置
APPFLOWY_HISTORY_DATABASE_URL=postgres://postgres:password@postgres:5432/postgres
- postgres:// 为协议
- postgres 用户名,为postgres默认用户名
- password 密码
- @postgres:5432 地址+端口,其中postgres代表localhost
- postgres 为连接数据库名称
APPFLOWY_AI_DATABASE_URL=postgresql+psycopg://postgres:password@postgres:5432/postgres
其中postgresql+psycopg是PostgreSQL driver for Python。python项目提供的驱动。
查看数据库
我们可以使用psql进入Postgres查看表信息,也可以使用可视化工具查看。
postgres=# \dt
List of relations
Schema | Name | Type | Owner
--------+--------------------------------------+-------------------+----------
public | _sqlx_migrations | table | postgres
public | af_blob_metadata | table | postgres
public | af_chat | table | postgres
public | af_chat_messages | table | postgres
public | af_collab | partitioned table | postgres
....... 省略
public | af_user | table | postgres
public | af_workspace | table | postgres
public | af_workspace_invitation | table | postgres
public | af_workspace_member | table | postgres
(35 rows)
熟悉rust数据库工具sqlx
数据库(DB File)和 DBMS(数据库管理系统)是两个不同的概念,rust也很难直接使用psql读取数据,所以需要选择一个项目数据库的管理工具,Appflow使用的就是sqlx.
Sqlx特点
- 异步,且支持不同运行时(async-std/tokio/actix)
- 预编译
- 纯Rust,安全,效率高
- 数据库无关(Database Agnostic)
Appflow中可能用到的优势
- Row streaming,数据异步按需读取
- Automatic statement preparation and caching 自动语句缓存
- Asynchronous notifications using LISTEN and NOTIFY for PostgreSQL. 支持异步通知
Sqlx的使用
第一次编译完成,我们仍然会遇到SQL语句的红色提示,我们需要执行
# 安装cli
cargo install sqlx-cli --no-default-features --features native-tls,postgres
# 保存查询元数据以供离线使用,这个似懂非懂
cargo sqlx prepare --check [--workspace]
.sqlx是存储prepare的数据的地方,概念上叫workspace
// query-1b1ff4352abb6dad982279ee99c8dccb3621b55a838998c1b9803982ae10f622.json
{
"db_name": "PostgreSQL",
"query": " SELECT uid, uuid FROM af_user",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "uid",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "uuid",
"type_info": "Uuid"
}
],
"parameters": {
"Left": []
},
"nullable": [
false,
false
]
},
"hash": "1b1ff4352abb6dad982279ee99c8dccb3621b55a838998c1b9803982ae10f622"
}
看起来像是某个查询的一次Json格式结构化, 原始的Rust代码为, 其中AFUserIdRow 对应了columns的信息
pub fn select_all_uid_uuid<'a, E: Executor<'a, Database = Postgres> + 'a>(
executor: E,
) -> BoxStream<'a, sqlx::Result<AFUserIdRow>> {
sqlx::query_as!(AFUserIdRow, r#" SELECT uid, uuid FROM af_user"#,).fetch(executor)
}
#[derive(Debug, FromRow)]
pub struct AFUserIdRow {
pub uid: i64,
pub uuid: Uuid,
}
migrations
数据库相关的修改,需要频繁的改动数据库,这点所有数据库都是相同的,都需要migrations, sqlx的默认目录是根目录的migrations, 在docker启动中,优先执行其中的bash脚本,随后按日期排序的.sql文件。
# 新增迁移
sqlx migrate add test
Creating migrations/20240715115519_test.sql
# 执行迁移
sqlx migrate run
# 回退迁移
sqlx migrate revert
Rust使用Postgres
最简单的使用
- 从环境中获取 DATABASE_URL
- 然后建立连接池
- 插入一条数据并返回id
#[tokio::main(flavor = "current_thread")]
async fn main() -> anyhow::Result<()> {
let pool = PgPool::connect(&env::var("DATABASE_URL")?).await?;
}
async fn add_todo(pool: &PgPool, description: String) -> anyhow::Result<i64> {
let rec = sqlx::query!(
r#"
INSERT INTO todos ( description )
VALUES ( $1 )
RETURNING id
"#,
description
)
.fetch_one(pool)
.await?;
Ok(rec.id)
}
Appflow Cloud中典型使用
pub fn select_all_uid_uuid<'a, E: Executor<'a, Database = Postgres> + 'a>(
executor: E,
) -> BoxStream<'a, sqlx::Result<AFUserIdRow>> {
sqlx::query_as!(AFUserIdRow, r#" SELECT uid, uuid FROM af_user"#,).fetch(executor)
}
#[derive(Debug, FromRow)]
pub struct AFUserIdRow {
pub uid: i64,
pub uuid: Uuid,
}
上述代码有中,
- BoxStream:流的一种
- AFUserIdRow中derive(FromRow)表示这个结构可以转换,对应查询
Record, - query_as! 是查询转换宏
- fetch 是执行查询操作
这是一个非常典型的调用过程,我们在项目当中会经常遇到。
Tiny Project, 模仿写一个简单的开发过程
最简单的 Todo
- 建立数据库
- 建用户表/任务表
- 新建Rust项目
- 配置Server服务
- 配置Docker
- 运行/大功告成