Skip to content

Commit

Permalink
model migration
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Aug 22, 2023
1 parent c275b79 commit 5006450
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 55 deletions.
2 changes: 1 addition & 1 deletion .vitepress/config/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@
"text": "数据库",
"items": [
{
"text": "使用数据库",
"text": "基本用法",
"link": "/guide/database/"
},
{
Expand Down
24 changes: 19 additions & 5 deletions zh-CN/api/database/model.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# 数据模型 (Model)

::: tip
参见:[开发 > 数据库 > 扩展数据模型](../../guide/database/model.md)
:::

## 数据类型

数据类型会被用于 [`model.extend()`](#model-extend-name-fields-config) 方法中,其定义如下:
数据类型会被用于 [`model.extend()`](#model-extend) 方法中,其定义如下:

```ts
export interface Field<T> {
Expand All @@ -11,6 +15,7 @@ export interface Field<T> {
nullable?: boolean
initial?: T
comment?: string
legacy?: string[]
}
```

Expand All @@ -28,7 +33,7 @@ export interface Field<T> {
| 名称 | TS 类型 | 默认长度 | 默认初始值 | 说明 |
| :-: | :-: | :-: | :-: | :-: |
| char | `string` | 64 | `''` | 定长的字符串 |
| string | `string` | 256 | `''` | 变长的字符串 |
| string | `string` | 255 | `''` | 变长的字符串 |
| text | `string` | 65535 | `''` | 变长的字符串 |

### 时间类型
Expand Down Expand Up @@ -60,8 +65,17 @@ export interface Field<T> {

扩展一个新的数据表。

### model.create(name)
### model.create(name, data)

- **name:** `string` 数据表名
- **data:** `any` 数据

创建一条新的数据,折叠嵌套属性,并填充必要的默认值。

### model.migrate(name, fields, callback)

### model.resolveQuery(query)
- **name:** `string` 数据表名
- **fields:** `Field.Config` 要迁移的字段信息
- **callback:** `(db: Database) => Promise<void>` 迁移的回调函数

### model.resolveModifier(modifier)
设置 [整表迁移](../../guide/database/model.md#整表迁移) 的操作。
8 changes: 4 additions & 4 deletions zh-CN/guide/database/index.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 使用数据库
# 基本用法

::: tip
`ctx.database` 并非内置服务,因此如果你的插件需要使用数据库功能,需要[声明依赖](../plugin/service.md#using-属性)
Expand All @@ -21,8 +21,8 @@ await ctx.database.get('schedule', [1234, 5678])
对于复杂的数据表,如果你只需要获取少数字段,你可以通过第三个参数手动指定要获取的字段:

```ts
// 返回的数组中每个元素只会包含 command, lastCall 属性
await ctx.database.get('schedule', [1234], ['command', 'lastCall'])
// 返回的数组中每个元素只会包含 command, time 属性
await ctx.database.get('schedule', [1234], ['command', 'time'])
```

你还可以向第二个参数传入一个对象,用来查询非主键上的数据或者同时指定多列的值:
Expand Down Expand Up @@ -77,7 +77,7 @@ await ctx.database.create('schedule', row)
// 第二个参数也可以使用上面介绍的查询表达式
await ctx.database.set('schedule', 1234, {
assignee: 'onebot:123456',
lastCall: new Date(),
time: new Date(),
})
```

Expand Down
131 changes: 86 additions & 45 deletions zh-CN/guide/database/model.md
Original file line number Diff line number Diff line change
@@ -1,92 +1,103 @@
# 扩展数据模型

如果你的插件需要声明新的字段或者表,你可以通过 `ctx.model` 来对数据模型进行扩展。请注意,数据模型的扩展一定要在使用前完成,不然后续数据库操作可能会失败
Koishi 的架构允许任何插件对数据库的结构进行扩展。你就可以在不修改 Koishi 或其他插件源码的情况下,为数据库添加新的字段或者表。这些功能都是通过 `ctx.model` 提供的

## 扩展字段
请注意:数据模型的扩展一定要在使用前完成,不然后续数据库操作可能会失败。

向内置的 User 表中注入字段的方式如下:
## 扩展表和字段

```ts
// @errors: 1117
// TypeScript 用户需要进行类型合并
declare module 'koishi' {
interface User {
foo: string
}
}

ctx.model.extend('user', {
// 向用户表中注入字符串字段 foo
foo: 'string',
// 你还可以配置默认值为 'bar'
foo: { type: 'string', initial: 'bar' },
})
```

向 Channel 注入字段同理。

## 扩展表

利用 `ctx.model.extend()` 的第三个参数,我们就可以定义新的数据表了:
可以使用 `model.extend()` 方法扩展一个新的数据表,其中的第一个参数是表名,第二个参数包含了各字段的类型声明。下面的代码向数据库中扩展了一个名为 `schedule` 的表:

```ts
// @errors: 2440
// TypeScript 用户需要进行类型合并
declare module 'koishi' {
interface Tables {
schedule: Schedule
}
}

// 这里是新增表的接口类型
export interface Schedule {
id: number
assignee: string
time: Date
lastCall: Date
interval: number
command: string
session: Session.Payload
}

ctx.model.extend('schedule', {
// 各字段类型
// 各字段的类型声明
id: 'unsigned',
assignee: 'string',
time: 'timestamp',
lastCall: 'timestamp',
interval: 'integer',
command: 'text',
session: 'json',
}, {
// 使用自增的主键值
autoInc: true,
})
```

## 创建索引

我们还可以为数据库声明索引:
`model.extend()` 同样也可以向已经存在的表中注入新的字段,使用方法与上面完全一致。例如,下面的代码向内置的 `User` 表中注入了 `foo` 字段:

```ts
declare module 'koishi' {
interface Tables {
foo: Foo
interface User {
foo: string
}
}

interface Foo {
name: string
bar: string
baz: string
uid: string
}
ctx.model.extend('user', {
// 向用户表中注入字符串字段 foo
foo: 'string',
})
```

## 声明数据类型

上面的数据类型均直接使用字符串来定义。对于更复杂的需求,你也可以选择传入一个对象:

```ts
ctx.model.extend('user', {
foo: {
type: 'string',
// 占据的字节长度
length: 65535,
// 该字段的默认值
initial: 'bar',
// 是否允许为空
nullable: false,
},
})
```

当你直接使用 `string` 作为类型时,其默认字节长度为 255,默认初始值为 `''`。不同字段的默认值也有所区别,你可以在 [这里](../../api/database/model.md) 查看完整的数据类型列表。

### 字段迁移

如果你想要修改一个已有的字段 (只修改名称,不修改逻辑),你并不能单纯地将源码中的字段名改成新名称。如果这样做,数据仍然会停留在旧的字段中,它们实质上已经丢失了,却仍然占据的数据库的空间。此时你需要将旧的字段一并声明到表中:

```ts
ctx.model.extend('user', {
foo: {
type: 'string',
legacy: ['bar', 'baz'],
},
})
```

// ---cut---
这样一来,Koishi 就知道 `foo`, `bar`, `baz` 这三个字段实际上对应是同一列数据,并在启动时自动将旧字段中的数据迁移到 `foo` 字段中。

## 声明索引 <badge type="warning">实验性</badge>

`model.extend()` 还接受一个可选的三参数,在这里你可以对表的索引进行设置:

```ts
// 注意这里配置的是第三个参数,也就是之前 autoInc 所在的参数
ctx.model.extend('foo', {}, {
// 主键,默认为 'id'
// 主键将会被用于 Query 的简写形式,如果传入的是原始类型或数组则会自行理解成主键的值
primary: 'name',
// 自增主键值
autoInc: true,
// 唯一键,这应该是一个列表
// 这个列表中的字段对应的值在创建和修改的时候都不允许与其他行重复
unique: ['bar', 'baz'],
Expand All @@ -97,3 +108,33 @@ ctx.model.extend('foo', {}, {
},
})
```

## 整表迁移 <badge type="warning">实验性</badge>

::: warning
整表迁移的性能较差,建议谨慎设计数据库结构而不是依赖迁移。
:::

前面介绍的 [字段迁移](#字段迁移) 仅仅适用于修改字段名称的情况。如果你的插件需要重构表的数据结构,这种方法就不适用了。此时你可以使用 `model.migrate()` 方法来进行整表迁移:

```ts
ctx.model.extend('qux', {
id: 'unsigned',
text: 'string',
})

ctx.model.extend('qux2', {
id: 'unsigned',
flag: 'boolean',
})

// 如果 qux 中存在 flag 列,则对这部分数据进行迁移
ctx.model.migrate('qux', {
flag: 'boolean',
}, async (database) => {
const data = await database.get('qux', {}, ['id', 'flag'])
await database.upsert('qux2', data)
})
```

上面的例子展示了如何将 `qux` 表中的 `flag` 数据迁移到 `qux2` 表中。迁移完成后,`qux` 表中的 `flag` 列将会被删除,而其他列则会保留。如果你希望删除旧表,可以在回调函数的最后加上一句 `database.drop('qux')`

0 comments on commit 5006450

Please sign in to comment.