Appflowy学习系列-熟悉Postgres数据库

398 阅读4分钟

熟悉数据库

笔者对Postgres数据库以及sqlx等相关概念不是很熟悉,对客户端而言,唯一熟悉的数据库大概就是SQLite和一些简单的SQL语法,再高级一点,就是Sqlite中的cursor的应用,所以笔者花了大概三周的时间,学习了Postgres数据库基础。

但这是一个庞大的课题,我作为一个入门者,确实没有太多有用的知识来分享,我的切入点是,在Appflow Cloud这个项目中,遇到的不懂的点,以及对应概念的自我认知,目标是可以进行正常开发。

第一步 Postgres简单概念

  • 概念,我很喜欢这个词汇,他总能概括的表述自己,简单明了,易于举一反三;

Postgres是什么

Postgres是一个开源的数据库,相较于流行的MYSQL,它更复杂,有更多的规则,类似权限管理, 用户管理。更复杂的函数,有自己的语言plpgsql,同时支持C++,Java,Python作为函数的可选语言。支持NoSql, 例如Jsonb等,同时性能也比MySQL要好。同时对大型数据库的支持也很好,支持Physical ReplicationLogical 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

最简单的使用

  1. 从环境中获取 DATABASE_URL
  2. 然后建立连接池
  3. 插入一条数据并返回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

  1. 建立数据库
  2. 建用户表/任务表
  3. 新建Rust项目
  4. 配置Server服务
  5. 配置Docker
  6. 运行/大功告成