8 个版本

0.3.0 2020年7月10日
0.2.3 2020年7月10日
0.2.2 2020年6月26日
0.1.4 2020年6月22日

#1302开发工具

每月 32 次下载

GPL-3.0 许可证

140KB
3.5K SLoC

squawk cargo-badge Rust CI

Postgres 迁移代码检查工具

为什么?

防止由数据库迁移引起的意外停机。

这看起来是一个很好的项目,可以更多地使用 Rust。

安装

注意:由于 squawk 依赖于 libpg_querysquawk 只支持 Linux 和 macOS。

npm install -g squawk-cli

cargo install squawk

# or install binaries directly via the releases page
https://github.com/sbdchd/squawk/releases

用法

❯ squawk example.sql
example.sql:2:1: warning: prefer-text-field

   2 | --
   3 | -- Create model Bar
   4 | --
   5 | CREATE TABLE "core_bar" (
   6 |     "id" serial NOT NULL PRIMARY KEY,
   7 |     "alpha" varchar(100) NOT NULL
   8 | );

  note: Changing the size of a varchar field requires an ACCESS EXCLUSIVE lock.
  help: Use a text field with a check constraint.

example.sql:9:2: warning: require-concurrent-index-creation

   9 |
  10 | CREATE INDEX "field_name_idx" ON "table_name" ("field_name");

  note: Creating an index blocks writes.
  note: Create the index CONCURRENTLY.

example.sql:11:2: warning: disallowed-unique-constraint

  11 |
  12 | ALTER TABLE table_name ADD CONSTRAINT field_name_constraint UNIQUE (field_name);

  note: Adding a UNIQUE constraint requires an ACCESS EXCLUSIVE lock which blocks reads.
  help: Create an index CONCURRENTLY and create the constraint using the index.

example.sql:13:2: warning: adding-field-with-default

  13 |
  14 | ALTER TABLE "core_recipe" ADD COLUMN "foo" integer DEFAULT 10;

  note: In Postgres versions <11 adding a field with a DEFAULT requires a table rewrite with an ACCESS EXCLUSIVE lock.
  help: Add the field as nullable, then set a default, backfill, and remove nullabilty.

squawk--help

squawk
Find problems in your SQL

USAGE:
    squawk [FLAGS] [OPTIONS] [paths]... [SUBCOMMAND]

FLAGS:
    -h, --help
            Prints help information

        --list-rules
            List all available rules

    -V, --version
            Prints version information


OPTIONS:
        --dump-ast <dump-ast>
            Output AST in JSON [possible values: Raw, Parsed]

    -e, --exclude <exclude>...
            Exclude specific warnings

            For example: --exclude=require-concurrent-index-creation,ban-drop-database
        --explain <explain>
            Provide documentation on the given rule

        --reporter <reporter>
            Style of error reporting [possible values: Tty, Gcc, Json]


ARGS:
    <paths>...
            Paths to search


SUBCOMMANDS:
    help                Prints this message or the help of the given subcommand(s)
    upload-to-github    Comment on a PR with Squawk's results

规则

可以通过 --exclude 标志禁用单个规则

squawk --exclude=adding-field-with-default,disallowed-unique-constraint example.sql

require-concurrent-index-creation

确保所有索引创建都使用 CONCURRENTLY 选项。

此规则忽略同一事务中创建的表添加的索引。

在正常索引创建期间会阻止更新。 CONCURRENTLY 避免了阻止的问题。

https://postgresql.ac.cn/docs/current/sql-createindex.html#SQL-CREATEINDEX-CONCURRENTLY

constraint-missing-not-valid

检查所有新约束是否具有 NOT VALID

默认情况下,新约束需要扫描表并阻止对表的写入。使用 NOT VALID 与后续的 VALIDATE CONSTRAINT 调用一起使用可以防止表扫描,并且验证步骤只需要 SHARE UPDATE EXCLUSIVE 锁。

https://postgresql.ac.cn/docs/current/sql-altertable.html#SQL-ALTERTABLE-NOTES

adding-field-with-default

在 PostgreSQL 版本小于 11 的情况下,添加具有 DEFAULT 的字段需要使用具有 ACCESS EXCLUSIVE 锁的表重写。

https://postgresql.ac.cn/docs/10/sql-altertable.html#SQL-ALTERTABLE-NOTES

changing-column-type

更改列类型需要获取表上的 ACCESS EXCLUSIVE 锁定,这将阻止读取操作。

更改列类型可能会破坏正在从表中读取的其他客户端。

https://postgresql.ac.cn/docs/current/sql-altertable.html#SQL-ALTERTABLE-NOTES

adding-not-nullable-field

添加 NOT NULL 约束需要表扫描,并且执行 ALTER TABLE 操作需要 ACCESS EXCLUSIVE 锁定。

通常,这会与一个 DEFAULT 配合使用,但它在低于 \11 的版本中存在问题。请参阅 adding-field-with-default 规则。

renaming-column

重命名列可能会破坏现有的客户端。

renaming-table

重命名表可能会破坏现有的客户端。

disallowed-unique-constraint

添加 UNIQUE 约束需要 ACCESS EXCLUSIVE 锁定,这将阻止读取操作。

相反,可以创建一个 CONCURRENTLY 索引,并使用索引创建约束。

https://postgresql.ac.cn/docs/current/sql-altertable.html

ban-drop-database

删除数据库可能会破坏现有的客户端。

prefer-text-field

更改 varchar 字段的长度需要 ACCESS EXCLUSIVE 锁定。

使用带有 CHECK CONSTRAINT 的文本字段可以更容易地更改最大长度。请参阅 constraint-missing-not-valid 规则。

prefer-robust-stmts

本规则的目的是使迁移在部分失败时更加健壮。

例如,可能有一个包含两个步骤的迁移。首先,迁移向表中添加一个字段,然后它创建一个并发索引。

由于第二部分是并发的,它不能在事务中运行,因此迁移的第一部分可以成功,而第二部分可能会失败,这意味着第一部分不会被回滚。

然后当再次运行迁移时,它将在添加字段时失败,因为它已经存在。

为了满足此规则,可以使用如 IF NOT EXISTS 的守卫,或将所有语句包裹在事务中。

Bot 设置

Squawk 作为 CLI 工具使用,但也可以使用 upload-to-github 子命令在 GitHub Pull Requests 上创建注释。

以下是 squawk 使用存储库中的 example.sql 创建的示例注释

https://github.com/sbdchd/squawk/pull/14#issuecomment-647009446

创建一个新的应用

Squawk 需要一个相应的 GitHub App,以便它可以与 GitHub 通信。

  1. 创建应用

    名称 类型 原因
    拉取请求 写入 在 PR 上发表评论

    点击创建并复制 "关于" 部分下的 App ID

    URL 应该是: https://github.com/settings/apps/$YOUR_APP_NAME

  2. 转到页面底部的 "私钥" 部分,并点击 "生成私钥"

    几秒钟后,私钥将自动下载。请保存此密钥,我们稍后会用到。

    现在我们有了 App IDPrivate Key,现在我们需要安装应用

  3. 安装应用程序并获取安装ID

    前往 https://github.com/settings/apps/$YOUR_APP_NAME/installations 并点击 "安装"。

    GitHub 应该会将您重定向到 https://github.com/settings/installations/$INSTALL_ID 页面,其中 $INSTALL_ID 是一个数字。

    保存此ID以备后用。

    现在我们有我们的 SQUAWK_GITHUB_APP_IDSQUAWK_GITHUB_PRIVATE_KEYSQUAWK_GITHUB_INSTALL_ID

    Squawk 需要与拉取请求相关的值:SQUAWK_GITHUB_REPO_NAMESQUAWK_GITHUB_REPO_OWNER、和 SQUAWK_GITHUB_PR_NUMBER

    这些值的查找方式取决于您如何运行 Squawk,但下一步我假设您正在以 CircleCI 作业的形式运行 Squawk。

  4. 查找拉取请求变量

    CircleCI

    https://circleci.com/docs/2.0/env-vars/#built-in-environment-variables

    CIRCLE_PULL_REQUEST 包含我们需要的值

    示例: https://github.com/recipeyak/recipeyak/pull/567

    现在我们需要分割这个链接以获取仓库名称、仓库所有者和拉取请求 ID。

    借助一些帮助

    echo "https://github.com/recipeyak/recipeyak/pull/567" | awk -F/ '{print $4 " " $5 " " $7}'
    
    recipeyak recipeyak 567
    
    SQUAWK_GITHUB_REPO_OWNER=$(echo $CIRCLE_PULL_REQUEST | awk -F/ '{print $4}')
    SQUAWK_GITHUB_REPO_NAME=$(echo $CIRCLE_PULL_REQUEST | awk -F/ '{print $5}')
    SQUAWK_GITHUB_PR_NUMBER=$(echo $CIRCLE_PULL_REQUEST | awk -F/ '{print $7}')
    
  5. 结论

    汇总所有信息,我们应该有以下环境变量

    SQUAWK_GITHUB_APP_ID= # fill in with id found in step 5
    SQUAWK_GITHUB_INSTALL_ID= # fill in with id found in step 7
    # downloaded via step 6, your key will have a different name
    SQUAWK_GITHUB_PRIVATE_KEY=$(cat ./cool-bot-name.private-key.pem)
    # can also use the SQUAWK_GITHUB_PRIVATE_KEY_BASE64 instead ^
    SQUAWK_GITHUB_REPO_OWNER=$(echo $CIRCLE_PULL_REQUEST | awk -F/ '{print $4}')
    SQUAWK_GITHUB_REPO_NAME=$(echo $CIRCLE_PULL_REQUEST | awk -F/ '{print $5}')
    SQUAWK_GITHUB_PR_NUMBER=$(echo $CIRCLE_PULL_REQUEST | awk -F/ '{print $7}')
    

    我们可以在运行 Squawk 之前将这些变量传递到环境中,或者将它们转换为命令行标志。您觉得哪种方式更方便。

    示例运行如下(假设已设置环境变量)

    squawk upload-to-github example.sql
    

    这会创建如下评论

    https://github.com/sbdchd/squawk/pull/14#issuecomment-647009446

现有技术

开发

cargo install
cargo run
./s/test
./s/lint
./s/fmt

发布新版本

  1. 更新 CHANGELOG.md
  2. 在所有依赖项的 Cargo.toml 以及 CLI 的 Cargo.toml 中提升版本
  3. 在 GitHub 上创建新的发布 - CI 会自动附加二进制文件
  4. package.json 中提升版本并遵循 npm 步骤
  5. 以 DAG 方式将每个 crate 发布到 cargo

工作原理

Squawk 将对 libpg_query-sys 的调用封装在安全接口中,并将 JSON 解析为更容易处理的结构。libpg_query-sys 然后 使用 bindgen 绑定到 libpg_query,它本身使用一些 C 代码将 Postgres 的 SQL 解析器封装起来,并将解析后的抽象语法树 (AST) 输出为 JSON 字符串。

Squawk 然后在解析后的 AST 上运行规则函数,收集并格式化输出规则违规。

依赖关系

~60MB
~1M SLoC