数据迁移
数据迁移是数据库的版本控制,帮助开发者完成日常工作中的表结构变更与数据迁移。
快速开始
生成迁移
首先,生成一个配置文件。
$ npx sutando init或者全局安装 Sutando 的命令行工具
$ npm install -g sutando
$ sutando init这会在项目目录生成一个 sutando.config.js 文件,用来设置数据库连接等信息。
// Update with your config settings.
module.exports = {
client: 'mysql2',
connection: {
host: 'localhost',
database: 'database',
user: 'root',
password: 'password'
},
// 你可以增加多个连接,只需指定连接名称即可
connections: {
pgsql: {
client: 'pg',
connection: {
host: 'localhost',
database: 'another_database',
user: 'root',
password: 'password'
}
}
},
migrations: {
table: 'migrations',
path: 'migrations'
},
models: {
path: 'models',
}
};之后你就可以使用 migrate:make 命令来生成数据库迁移。新的迁移文件默认放在你的 migrations 目录下。每个迁移文件名都包含一个时间戳来使 Sutando 确定迁移的顺序:
$ npx sutando migrate:make create_flights_tableSutando 将使用迁移文件的名称来猜测表名以及迁移是否会创建一个新表。如果 Sutando 能够从迁移文件的名称中确定表的名称,它将在生成的迁移文件中预填入指定的表,或者,你也可以直接在迁移文件中手动指定表名。
如果要为生成的迁移指定自定义路径,你可以在执行 migrate:make 命令时使用 --path 选项。给定的路径应该相对于执行命令的路径。
迁移结构
迁移类包含两个方法:up 和 down 。up 方法用于向数据库中添加新表、列或索引,而 down 方法用于撤销 up 方法执行的操作。.
在这两种方法中,可以使用 Schema 构建器来富有表现力地创建和修改表。要了解 Schema 构建器上可用的所有方法,查看其文档。例如,以下迁移会创建一个 flights 表:
const { Migration } = require('sutando');
module.exports = class extends Migration {
/**
* Run the migrations.
*/
async up(schema) {
await schema.createTable('flights', (table) => {
table.increments('id');
table.string('name');
table.string('airline');
table.timestamps();
});
}
/**
* Reverse the migrations.
*/
async down(schema) {
await schema.dropTableIfExists('flights');
}
};设置迁移连接
如果你的迁移将与应用程序默认数据库连接以外的数据库连接进行交互,你应该设置迁移的 connection 属性:
module.exports = class extends Migration {
connection = 'pgsql';
/**
* Run the migrations.
*/
async up(schema) {
// ...
}
}执行迁移
执行 migrate:run 命令,来运行所有未执行过的迁移:
$ npx sutando migrate:run如果你想查看目前已经执行了哪些迁移,可以使用 migrate:status 命令:
$ npx sutando migrate:status回滚迁移
如果要回滚最后一次迁移操作,可以使用 migrate:rollback。该命令会回滚最后「一批」的迁移,这可能包含多个迁移文件:
$ npx sutando migrate:rollback通过向 rollback 命令加上 step 参数,可以回滚指定数量的迁移。例如,以下命令将回滚最后五个迁移:
$ npx sutando migrate:rollback --step=5数据表
创建数据表
接下来我们将使用 createTable 方法创建一个新的数据表。createTable 接受两个参数:第一个参数是表名,而第二个参数是一个回调函数:
const { Migration } = require('sutando');
module.exports = class extends Migration {
/**
* Run the migrations.
*/
async up(schema) {
await schema.createTable('users', (table) => {
table.increments('id');
table.string('name');
table.string('email');
table.timestamps();
});
}
/**
* Reverse the migrations.
*/
async down(schema) {
await schema.dropTableIfExists('users');
}
};创建表时,可以使用数据库结构构建器的 列方法 来定义表的列。
检查表 / 列是否存在
你可以使用 hasTable 和 hasColumn 方法检查表或列是否存在:
if (await schema.hasTable('users')) {
// 「users」表存在...
}
if (await schema.hasColumn('users', 'email')) {
// 「users」表存在,并且有「email」列...
}此外,还可以使用其他一些属性和方法来定义表创建的其他地方。使用 MySQL 时,可以使用 engine 方法指定表的存储引擎:
await schema.createTable('users', (table) => {
table.engine('InnoDB');
// ...
});charset 和 collate 方法可用于在使用 MySQL 时为创建的表指定字符集和排序规则:
await schema.createTable('users', (table) => {
table.charset('utf8mb4');
table.collate('utf8mb4_unicode_ci');
// ...
});如果你想给数据库表添加「注释」,你可以在表实例上调用 comment 方法。目前只有 MySQL 和 Postgres 支持表注释:
await schema.createTable('calculations', (table) => {
table.comment('Business calculations');
// ...
});更新数据表
Schema 的 table 方法可用于更新现有表。与 createTable 方法一样,table 方法接受两个参数:表的名称和接收可用于向表添加列或索引的回调函数:
await schema.table('users', (table) => {
table.integer('votes');
});重命名 / 删除表
要重命名已存在的数据表,使用 renameTable 方法:
await schema.renameTable(from, to);要删除已存在的表,你可以使用 dropTable 或 dropTableIfExists 方法:
await schema.dropTable('users');
await schema.dropTableIfExists('users');字段
创建字段
Schema 的 table 方法可用于更新表。与 createTable 方法一样, table 方法接受两个参数:表名和一个回调函数,可以使用该实例向表中添加列:
await schema.table('users', (table) => {
table.integer('votes');
});可用的字段类型
Schema 构建器提供了多种方法,用来创建表中对应类型的列。下面列出了所有可用的方法:
bigIncrements
bigIncrements 方法用于在数据表中创建一个自增的 UNSIGNED BIGINT 类型(主键)的列:
table.bigIncrements('id');bigInteger
bigInteger 方法用于在数据表中创建一个 BIGINT 类型的列:
table.bigInteger('votes');binary
binary 方法用于在数据表中创建一个 BLOB 类型的列:
table.binary('photo');boolean
boolean 方法用于在数据表中创建一个 BOOLEAN 类型的列:
table.boolean('confirmed');datetime
datetime 方法用于在数据表中创建一个 DATETIME 类型的列,可选参数为精度的总位数:
table.datetime('created_at', { precision: 6 });date
date 方法用于在数据表中创建一个 DATE 类型的列:
table.date('date');decimal
decimal 方法用于在数据表中创建一个 DECIMAL 类型的列,可选参数分别为有效字数总位数、小数部分总位数:
table.decimal('amount');
table.decimal('amount', 8, 2);double
double 方法用于在数据表中创建一个 DOUBLE 类型的列,可选参数分别为有效字数总位数、小数部分总位数:
table.double('amount', 8, 2);enum
enum 方法用于在数据表中创建一个 ENUM 类型的列:
table.enum('difficulty', ['easy', 'hard']);float
float 方法用于在数据表中创建一个 FLOAT 类型的列,可选参数分别为有效字数总位数、小数部分总位数:
table.float('amount', 8, 2);geometry
geometry 方法相当于 GEOMETRY:
table.geometry('positions');increments
increments 方法创建一个自动递增相当于 UNSIGNED INTEGER 的列作为主键:
table.increments('id');integer
integer 方法用于在数据表中创建一个 INTEGER 类型的列:
table.integer('votes');json
json 方法用于在数据表中创建一个 JSON 类型的列:
table.json('options');jsonb
jsonb 方法用于在数据表中创建一个 JSONB 类型的列:
table.jsonb('options');point
point 方法用于在数据表中创建一个 POINT 类型的列:
table.point('position');smallint
smallint 方法用于在数据表中创建一个 SMALLINT 类型的列:
table.smallint('votes');string
string 方法创建一个给定长度的 VARCHAR 等效列,相当于指定长度的 VARCHAR:
table.string('name', 100);text
text 方法用于在数据表中创建一个 TEXT 类型的列:
table.text('description');time
time 方法创建一个具有可选精度(总位数)的 TIME 等效列:
table.time('sunrise', { precision: 6 });timestamp
timestamp 方法创建一个具有可选精度(总位数)的 TIMESTAMP 类型的列:
table.timestamp('sunrise', { precision: 6 });timestamps
timestamps 方法创建 created_at 和 updated_at TIMESTAMP等效列:
table.timestamps();tinyint
tinyint 方法用于在数据表中创建一个 TINYINT 类型的列:
table.tinyint('votes');uuid
uuid 方法用于在数据表中创建一个 UUID 类型的列:
table.uuid('id');字段修饰符
除了上面列出的列类型外,在向数据库表添加列时还有几个可以使用的「修饰符」。例如,如果要把列设置为要使列为「可空」,你可以使用 nullable 方法:
await schema.table('users', (table) => {
table.string('email').nullable();
})下表时所有可用的列修饰符。此列表不包括索引修饰符:
| 修饰符 | 说明 |
|---|---|
.after('column') | 将该列放在其它字段「之后」(MySQL) |
.charset('utf8mb4') | 为该列指定字符集 (MySQL) |
.collate('utf8_unicode_ci') | 为该列指定排序规则 (MySQL/PostgreSQL/SQL Server) |
.comment('my comment') | 为该列添加注释 (MySQL/PostgreSQL) |
.defaultTo(value) | 为该列指定一个「默认值」 |
.first() | 将该列放在该表「首位」 (MySQL) |
.nullable() | 允许 NULL 值插入到该列 |
.unsigned() | 设置 INTEGER 类型的字段为 UNSIGNED (MySQL) |
修改字段
alter 方法可以将现有的字段类型修改为新的类型或修改属性。比如,你可能想增加 string 字段的长度,可以使用 alter 方法把 name 字段的长度从 25 增加到 50。所以,我们可以简单的更新字段属性然后调用 alter 方法:
await schema.table('users', (table) => {
table.string('name', 50).alter();
});当修改一个列时,你必须明确包括所有你想在列定义上保留的修改器 —— 任何缺失的属性都将被丢弃。例如,为了保留 unsigned、default 和 comment 属性,你必须在修改列时明确每个属性的修改。
await schema.table('users', (table) => {
table.integer('votes').unsigned().defaultTo(1).comment('my comment').alter();
});重命名字段
要重命名一个列,你可以使用模式构建器提供的 renameColumn 方法:
await schema.table('users', (table) => {
table.renameColumn('from', 'to');
});删除字段
要删除一个列,你可以使用 dropColumn 方法。
await schema.table('users', (table) => {
table.dropColumn('votes');
});如果要删除多个列,你可以使用 dropColumns 方法。
await schema.table('users', (table) => {
table.dropColumns('votes', 'avatar', 'location');
});索引
创建索引
结构生成器支持多种类型的索引。下面的例子中新建了一个值唯一的 email 字段。我们可以将 unique 方法链式地添加到字段定义上来创建索引:
await schema.table('users', (table) => {
table.string('email').unique();
});或者,你也可以在定义完字段之后创建索引。为此,你应该调用结构生成器上的 unique 方法,此方法应该传入唯一索引的列名称:
table.unique('email');你甚至可以将数组传递给索引方法来创建一个复合(或合成)索引:
table.index(['account_id', 'created_at']);创建索引时,Sutando 会自动生成一个合理的索引名称,但你也可以传递参数来自定义索引名称:
table.index(['name', 'last_name'], 'idx_name_last_name');
table.unique('email', {
indexName: 'unique_email'
});可用的索引类型
下面是所有可用的索引方法:
| 命令 | 说明 |
|---|---|
table.primary('id'); | 添加主键 |
table.primary(['id', 'parent_id']); | 添加复合主键 |
table.unique('email'); | 添加唯一索引 |
table.index('state'); | 添加普通索引 |
删除索引
若要删除索引,将字段数组传给 dropIndex 方法,会删除根据表名、字段和键类型生成的索引名称,也可以第二个参数指定索引名称:
| 命令 | 说明 |
|---|---|
table.dropPrimary('users', 'users_id_primary'); | 从「users」表中删除主键 |
table.dropUnique('users', 'users_email_unique'); | 从「users」表中删除 unique 索引 |
table.dropIndex('geo', 'geo_state_index'); | 从「geo」表中删除基本索引 |
外键约束
Sutando 还支持创建用于在数据库层中的强制引用完整性的外键约束。例如,让我们在 posts 表上定义一个引用 users 表的 id 字段的 user_id 字段:
await schema.createTable('posts', (table) => {
table.integer('user_id').unsigned().notNullable();
table.string('title', 30);
table.string('content');
table.foreign('user_id').references('id').inTable('users');
});