Schema 生成

Schema 生成

本指南涵盖 schema 生成的机制。你将学会如何:

  • 理解 Lucid 何时自动重新生成 database/schema.ts
  • 手动运行 schema:generate 以及何时使用它
  • 采用没有迁移历史的现有数据库
  • 从生成中排除表和 PostgreSQL schemas
  • 决定是否提交生成的文件
  • 当生成不适合你的工作流时禁用它

概述

Lucid 采用数据库优先的方法:你在迁移中描述 schema,Lucid 通过内省生成的表来生成带类型的 database/schema.ts 文件。你的模型继承生成的类并自动继承列类型。参见简介了解此设计背后的理念。

本页专注于生成管道。有关从模型中使用生成的类——列类型映射、模型级覆盖以及使用 schema 规则自定义类型——请参见 schema 类指南

何时运行生成

Lucid 在每个更改数据库 schema 的命令之后自动重新生成 database/schema.ts

  • migration:run
  • migration:rollback
  • migration:refresh
  • migration:reset
  • migration:fresh

这意味着模型无需手动干预即可与数据库保持同步。迁移应用后,schema 文件反映新的结构,你的模型在 TypeScript 下次编译时会获取这些变更。

在这些命令上传递 --no-schema-generate 以跳过重新生成。当你想要应用迁移而不修改 schema 文件时使用此选项(例如,当你有意手动编辑文件或提交自定义版本时)。

node ace migration:run --no-schema-generate

有关每个命令的完整标志列表,请参见命令参考

生成的文件

默认情况下,生成的文件位于 database/schema.ts。数据库中的每个表都成为一个 schema 类,通过将表名转换为 PascalCase 并附加 Schema 来命名。

database/schema.ts (excerpt)
import { BaseModel, column } from '@adonisjs/lucid/orm'
import { DateTime } from 'luxon'
export class UsersSchema extends BaseModel {
static table = 'users'
@column({ isPrimary: true })
declare id: number
@column()
declare email: string
@column()
declare password: string
@column.dateTime({ autoCreate: true })
declare createdAt: DateTime
@column.dateTime({ autoCreate: true, autoUpdate: true })
declare updatedAt: DateTime
}
export class PostsSchema extends BaseModel {
static table = 'posts'
@column({ isPrimary: true })
declare id: number
// ...
}

几个值得了解的约定:

  • 表名在数据库中是复数形式,在生成的文件中变为 PascalCase + SchemausersUsersSchemablog_postsBlogPostsSchema)。
  • 列名在模型属性上从 snake_case 转换为 camelCase(created_atcreatedAt)。
  • static table 属性记录原始数据库表名,以便模型在查询时解析回正确的表。
  • 文件在每次生成时都会被覆盖,因此对 database/schema.ts 的任何手动编辑都会丢失。通过 schema 规则或模型级覆盖来自定义。

手动运行 schema

schema:generate Ace 命令按需重新生成文件。在三种情况下使用它:

  1. 采用现有数据库。 当你将 Lucid 指向已经有表的数据库(而非运行迁移)时,schema:generate 生成你开始构建模型所需的 schema 类。
  2. 手动 schema 变更之后。 如果同事通过 psql 直接应用了 schema 变更,或者队友在共享开发数据库上运行了迁移,运行 schema:generate 将变更拉取到你的本地 schema 文件中。
  3. 恢复缺失的 schema 文件。 如果 database/schema.ts 被删除或变得过时(例如 Git 合并冲突),从当前数据库状态重新生成它。
node ace schema:generate

参见命令参考了解完整标志列表(--connection--compact-output)。

采用现有数据库

Schema 生成使 Lucid 可用于不是由 Lucid 迁移创建的数据库。当你将现有数据库纳入 Lucid 模型管理时,请遵循此工作流。

1. 配置数据库连接。 使用现有数据库的连接详细信息设置 config/database.ts。参见配置指南了解驱动特定选项。

config/database.ts
import env from '#start/env'
import { defineConfig } from '@adonisjs/lucid'
const dbConfig = defineConfig({
connection: 'postgres',
connections: {
postgres: {
client: 'pg',
connection: env.get('DATABASE_URL'),
},
},
})
export default dbConfig

2. 决定你要建模哪些表。 大多数遗留数据库包含你的应用不应通过 Lucid 触及的表:来自之前工具的框架迁移表、审计日志、队列或缓存表等。将它们列在 schemaGeneration.excludeTables 下,以便从生成的文件中跳过它们。

config/database.ts
{
postgres: {
client: 'pg',
connection: env.get('DATABASE_URL'),
schemaGeneration: {
excludeTables: [
'knex_migrations',
'knex_migrations_lock',
'pgboss_jobs',
'audit_log',
],
},
},
}

3. 生成 schema 文件。 运行 schema:generate 来内省数据库并写入 database/schema.ts

node ace schema:generate

打开生成的文件并审查类。你未排除的每个表都显示为一个带有列声明的 *Schema 类。

4. 为你想要使用的表创建模型。 你不需要为每个表都创建模型——只需要你的应用与之交互的那些。为每个表生成一个继承相应 schema 类的模型。

node ace make:model User
node ace make:model Post
app/models/user.ts
import { UsersSchema } from '#database/schema'
export default class User extends UsersSchema {}

5. 决定 schema 将如何演进。 从这里有两条路径:

  • 继续在 Lucid 之外管理 schema。 继续通过你现有的工具(原始 SQL、另一个服务中的 ORM、DBA 工作流)应用 schema 变更。每当 schema 变更时运行 node ace schema:generate 以刷新 database/schema.ts
  • 将 schema 管理迁移到 Lucid 迁移中。 将所有未来的 schema 变更编写为 Lucid 迁移。第一次 migration:run 将应用你的新迁移并使用结果重新生成 schema 文件。历史 schema 不受影响,因为 Lucid 不需要现有的迁移历史即可工作。

这两条路径也可以混合使用:为在其他地方管理的表手写 schema,为通过 Lucid 添加的新表编写迁移。

6. 处理约定不匹配。 不遵循 Lucid 默认值的表和列(snake_case 列、复数表名、单列主键、整数或 UUID 标识符)需要 schema 规则或模型级覆盖。参见使用 schema 规则自定义类型模型级覆盖了解相关模式。

自定义生成的输出

schemaGeneration 配置块接受一个 rulesPaths 数组,指向自定义列和表输出方式的 TypeScript 模块。规则可以覆盖特定列的 TypeScript 类型、更改推断的主键、自定义命名等。

config/database.ts
{
schemaGeneration: {
enabled: true,
outputPath: 'database/schema.ts',
rulesPaths: ['database/schema_rules.ts'],
},
}
database/schema_rules.ts
export default {
// ... 规则定义
}

规则格式和类型自定义模式在 schema 类指南中有详细介绍,因为这些选择从根本上关系到你的模型继承的类型。

排除表和 schemas

两个配置选项控制 Lucid 在生成期间考虑哪些表。

excludeTables 按名称跳过特定表。用于你的应用不应通过 Lucid 访问的表(框架元数据、在其他地方维护的审计表、由不同工具管理的队列表)。

schemaGeneration: {
excludeTables: ['knex_migrations', 'knex_migrations_lock', 'pgboss_jobs'],
}

schemas 将生成限制在特定的 PostgreSQL schemas。没有它,生成器会扫描连接有权访问的每个 schema。

schemaGeneration: {
schemas: ['public', 'app'],
}

完整的 schemaGeneration 配置参考在配置指南中。

版本控制

database/schema.ts 提交到你的仓库。即使该文件会被重新生成,将其视为受版本控制的有几个好处:

  • 无需构建步骤的 IDE 智能提示。 编辑器在文件出现在磁盘上的那一刻就能获取带类型的 schema 类。不提交的话,每个开发者都必须运行 schema:generate(或应用迁移)才能编写模型。
  • Schema 变更可在 Pull Request 中审查。 添加列的迁移会在 database/schema.ts 中产生相应的 diff。审查者可以在迁移代码旁边看到 schema 影响,这能捕获仅看迁移时隐藏的错误。
  • 确定性 CI。 CI 工作流可以立即进行类型检查,无需先运行迁移。生产构建也是如此。
  • 漂移检测。 当提交的 schema 和重新生成的 schema 不一致时,你就知道有人在迁移管道之外更改了数据库。添加一个运行 schema:generate 并在产生 diff 时失败的 CI 步骤来自动捕获这种情况。

database/schema.ts 作为普通文件添加到你的仓库。文件的自动生成头清楚地表明该文件不应手动编辑。

禁用生成

schemaGeneration.enabled 设置为 false 以同时禁用迁移后的自动重新生成和 schema:generate 命令。这很少见;仅当你完全手动维护 database/schema.ts 或你的项目根本不使用 Lucid 模型时才使用。

schemaGeneration: {
enabled: false,
}

当生成被禁用时,schema 相关命令会报告为空操作。只要有手动维护的 database/schema.ts 存在,模型仍然可以工作;没有它,你的模型没有 schema 类可以继承。