7 个版本 (2 个稳定版)

2.0.0-beta.82023年4月20日
2.0.0-beta.72023年3月3日
2.0.0-beta.52021年10月26日
1.3.0 2020年12月19日
1.2.0 2020年12月16日

#962 in 命令行工具

MIT/Apache

65KB
1.5K SLoC

Journey

数据建模是一场旅行 - 使用 jrny 来管理你的旅程

重要: Journey 仍然是一个非常初级的原型;版本 >= 1 仅意味着它达到了所需的最低(且可工作的)功能,并且由于生活的责任,开发仍然是不规则的。

换句话说: 谨慎使用

概述

已经存在其他基于 SQL 的架构迁移工具(如 dbmate),但仍有一席之地。

jrny 是为那些...的人提供的选项

  • ... 认为数据库修订文件应该是一个不可变记录,并且可以 保证 表示应用到数据库上的内容

  • ... 希望有一个 保证的修订顺序,跨所有环境

  • ... 更愿意 编写 SQL 而不是将其转换为方法调用或 YAML 条目,这些条目通常更冗长且文档较少

  • ... 想要 显式控制事务,并且能够轻松忽略它们或在多个修订之间利用它们

  • ... 更喜欢 安装编译后的二进制文件 而不是管理语言和依赖项,这些依赖项在运行迁移的系统上

  • ... 喜欢单一责任的概念,特别是如果多个应用程序(可能在不同仓库中用不同语言编写)访问相同的表

  • ... 相信将 迁移与应用程序部署分离 可以鼓励人们编写非破坏性迁移,并有助于实现零停机更新

  • ... 更喜欢 避免反向迁移,尤其是在升级/降级/编辑/升级周期中,忘记添加预存的索引、检查约束等,使“更改”历史变得极其容易。 (请在此处添加您对这个主题的看法 here)

CLI 使用方法

jrny 主要被设计为预编译的独立命令行工具,但也可以 作为库使用

安装

从源码安装

假设已经安装了 cargo(最简单的方式是使用 rustup),然后只需运行

$ cargo install jrny --version 2.0.0-beta.8

    Updating crates.io index
  Downloaded jrny v2.0.0-beta.8
  Downloaded 1 crate (28.6 KB) in 0.39s
  Installing jrny v2.0.0-beta.8
   ...
   ...
   ...
   Compiling jrny v2.0.0-beta.8
    Finished release [optimized] target(s) in 2m 03s
  Installing /Users/<user>/.cargo/bin/jrny
   Installed package `jrny v2.0.0-beta.8` (executable `jrny`)

使用方法

使用 jrny 管理模式变更共有 4 步

  1. 开始
  2. 计划
  3. 审查
  4. 启动

开始旅程

项目设置很简单 - 所需的只是一个配置文件以及它旁边的空修订目录。这些可以通过手动创建或使用 jrny begin 来创建。

$ jrny begin <project-dir>

A journey has begun
  <project-dir>
  ├── <project-dir>/revisions [created]
  └── <project-dir>/jrny.toml [created]
  └── <project-dir>/jrny-env.toml [created]
  └── <project-dir>/jrny-env.example.toml [created]
  

默认的 jrny.toml 文件指定了存放修订的目录,以及记录已应用修订详情的数据库模式和“状态表”的名称。

# jrny.toml

[revisions]
directory = "revisions"

[table]
schema = "public"
name = "jrny_revision"

修订目录可以在任何时候重命名,只要 SQL 文件本身不改变,但一旦应用了任何修订,就不能再更改模式与表。否则,jrny 将看到空的状态表并尝试再次应用所有修订。

此外,还会创建 jrny-env.tomljrny-env.example.toml 文件。可选的 jrny-env.toml 环境文件用于存储特定环境的信息,包括数据库连接字符串。

# jrny-env.example.toml

[database]
# Database connection string - for permissible formats and options see:
# https://docs.rs/postgres/0.19.1/postgres/config/struct.Config.html
url = "postgresql://user:password@host:port/dbname"

配置文件和环境文件都可以自由重命名,但如果更改它们的名称(或在没有项目目录的情况下运行 jrny),则必须通过 -c [--config]-e [--environment] 分别传入它们的路径。

规划旅程

要创建新的 SQL 修订,运行 jrny plan [-c <path-to-config>],通过 -c 指定配置文件的路径,或者(如果省略)在当前目录中查找 jrny.toml

$ jrny plan create-users

Created revisions/001.1606743300.create-users.sql

$ jrny plan 'name with spaces' -c /path/to/my/config.toml

Created /path/to/my/revisions/002.1606743400.name with spaces.sql

这将为您创建一个(大部分是)空的 SQL 文件,您可以在其中填充令人惊叹的语句。请注意,jrny 鼓励每个修订使用事务,但您可以选择移除这些事务,尤其是如果您需要执行不需要事务的语句,或者如果您想编写跨越相同事务的多个修订文件。

$ cat /path/to/my/revisions/002.1606743400.name\ with\ spaces.sql

-- Revision: name with spaces
--
-- Add description here

begin;

-- Add SQL here

commit;

注意:建议注释掉 commit; 行,这样您就可以在数据库中运行修订而不实际持久化更改。

修订文件名遵循 [id].[timestamp].[name].sql 的模式。

时间戳是捕捉的绝佳元数据,而 jrny 为每个文件分配一个顺序 ID。这样做是为了强制执行比仅使用时间戳更严格的修订顺序,而无需在文件之间设置指针。(有关更多信息,请参阅 排序的合理性。)

ID 序列中的空缺是正常的(例如,如果您创建了两个新修订,删除了第一个,然后应用了第二个),并且只要修订未应用,就可以手动更改 ID。

审查旅程

为了总结修订状态,请运行 jrny review。如果您不在项目目录中,需要指定配置文件位置,并且您需要指定环境文件路径或直接提供数据库URL,例如

# From within project directory & default filenames
$ jrny review

# From outside the project directory *or* with a custom config filename.
#
# This will look for an environment file named `jrny-env.toml` in
# the same directory as the custom config file.
$ jrny review -c path/to/my-jrny-config.toml

# Same as above except can specify custom environment file with different name
# or in a different directory than the config file.
$ jrny review -c path/to/my-jrny-config.toml -e path/to/my-jrny-env.toml

# Specifying database URL within project directory & default config filename.
# Can be used in conjunction with custom config and/or environment file paths.
#
# If there is a default environment file in the current directory, the URL option
# will take precedence over the URL supplied by the environment file.
$ jrny review -d 'postgresql://user:password@host:5432/dbname'

这将列出所有有序修订,每个修订都包含创建时间和应用时间(如果应用到指定数据库)。

$ jrny review

The journey thus far:

  [1] my-first-revision
    Created on 30-Mar-2023 09:10:22
    Applied on 30-Mar-2023 09:11:06

  [2] another-revision
    Created on 30-Mar-2023 09:10:32
    Applied on 30-Mar-2023 09:11:06

  [3] YET-another-revision
    Created on 30-Mar-2023 09:27:58

此外,jrny 在审查期间执行多项检查以确保...

  • 已应用修订的文件在应用后未被更改
  • 已应用修订的文件未被删除
  • 挂起修订未插入到应用修订之前
  • 序列中的所有修订(挂起和已应用)具有唯一的ID
The journey thus far:

  [1] revision-that-gets-changed
    Created on 30-Mar-2023 09:31:05
    Applied on 30-Mar-2023 10:17:33
    Errors:
      - File has changed after being applied

  [2] revision-that-gets-removed
    Created on 30-Mar-2023 09:57:56
    Applied on 30-Mar-2023 10:17:33
    Errors:
      - File could not be found

  [3] a-revision-added-in-between
    Created on 30-Mar-2023 10:18:58
    Errors:
      - Later revisions have already been applied

  [4] some-revision-that-is-fine
    Created on 30-Mar-2023 09:58:19
    Applied on 30-Mar-2023 10:17:33

  [5] a-revision-that-was-fine
    Created on 30-Mar-2023 10:17:24
    Applied on 30-Mar-2023 10:17:33
    Errors:
      - Revision has a duplicate id

  [5] revision-with-duplicate-id
    Created on 30-Mar-2023 10:19:57
    Errors:
      - Revision has a duplicate id

The journey has problems:
  - 1 revision has been changed after being applied
  - 2 revisions have duplicate ids
  - 1 revision file could not be found
  - 1 pending revision has been inserted before revisions already applied

这些检查也不一定是互斥的,也就是说,单个修订可能具有多个错误,例如,在应用后已被更改并且具有重复的ID,如果序列也被更改。

注意:即使添加(或删除)空白或注释,审查也会失败;目前还没有尝试在生成用于确定文件是否在应用后被更改的校验和之前清除这些内容。

开始旅程吧!

要应用所有挂起修订,请运行 jrny embark

jrny review 类似,应用修订会在当前目录中查找默认配置和环境文件,但这两者都可以被覆盖,并且可以直接提供数据库URL。

在应用任何挂起修订之前,将审查修订,如果文件已更改,不再存在于磁盘上等,则 jrny 将发出错误并退出,不应用任何新修订。

否则,jrny 将简单地列出已应用的修订名称...

$ jrny embark

Applying 1 revision(s)

  003.1680182878.YET-another-revision.sql

...或一条指示未找到挂起修订的消息。

$ jrny embark

No revisions to apply

此外,您可以使用 --through-t 仅应用到指定ID的挂起修订。

例如,给定以下审查

$ jrny review

The journey thus far:

  [1] my-first-revision
    Created on 30-Mar-2023 09:10:22
    Applied on 30-Mar-2023 09:11:06

  [2] another-revision
    Created on 30-Mar-2023 09:10:32

  [3] YET-another-revision
    Created on 30-Mar-2023 09:27:58

  [4] shocker-a-revision
    Created on 19-Apr-2023 15:42:29

  [5] surprise-another-revision
    Created on 19-Apr-2023 15:42:36

如果您只想运行到 YET-another-revision,只需传递ID 3

$ jrny embark --through 3

Applying 2 revision(s), skipping 2

  002.1680181832.another-revision.sql
  008.1681952321.YET another revision.sql

库使用

jrny 命令行工具是围绕几个结构体和函数的薄包装,如果需要以更程序化的方式管理修订,也可以将这些结构体和函数导入Rust应用程序。

然而,库函数不假设配置和环境,您必须根据需要显式创建这些对象,这在目前来说并不太方便。

以下是一个完整的(基本)示例

use std::env;
use std::path::PathBuf;

use jrny::context as ctx;


fn main() {
    // Initialize a new `jrny` setup in the `./jrny-test` subdirectory.
    //
    // Note: In addition to creating the necessary revisions directory, this *also*
    // creates the `jrny.toml`, etc files that, when using `jrny` as a library,
    // are entirely unnecessary.
    //
    // See: https://github.com/kevlarr/jrny/issues/35
    jrny::begin(&PathBuf::from("jrny-test")).unwrap();

    // The rest of the commands will need to know the project configuration
    // and potentially other environment details as well.
    let cfg = ctx::Config {
        revisions: ctx::RevisionsSettings {
            directory: PathBuf::from("jrny-test/revisions"),
        },
        table: ctx::TableSettings {
            schema: "public".to_owned(),
            name: "jrny_revision".to_owned(),
        },
    };
    let env = ctx::Environment::from_database_url(&env::var("DATABASE_URL").unwrap());

    // Create a new empty migration
    jrny::plan(&cfg, "my first migration", None).unwrap();

    // Create another migration with some contents
    jrny::plan(&cfg, "a more useful migration", Some("
        create table my_cool_table (
            id bigint
                primary key
                generated always as identity
        )
    ")).unwrap();

    // Review the migrations
    jrny::review(&cfg, &env).unwrap();

    // Run the migrations
    jrny::embark(&cfg, &env).unwrap();
}

计划改进或“缺少的功能”

请参阅增强功能以获取计划中新功能的运行列表。

更重要的是,目前没有测试覆盖率;修复这个问题是v2.0.0 里程碑的一部分。

依赖项

约11–25MB
约361K SLoC