Skip to content

Commit

Permalink
enhance database docs
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Apr 2, 2024
1 parent 2349660 commit a2271df
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 40 deletions.
4 changes: 1 addition & 3 deletions zh-CN/api/database/evaluation.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@
参见:[开发 > 数据库 > 进阶查询技巧](../../guide/database/selection.md)
:::

求值表达式是一组静态 API,主要在 [`Selection`](./selection.md) 中使用。你可以从 Koishi 中直接导入:
与查询表达式不同,求值表达式是一组静态 API。你可以从 Koishi 中直接导入:

```ts
import { Eval } from 'koishi'
// 或者使用更短的别名
import { $ } from 'koishi'
```

Expand Down
70 changes: 41 additions & 29 deletions zh-CN/guide/database/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@

对于几乎所有大型机器人项目,数据库的使用都是不可或缺的。但如果每个插件都独立处理与数据库的交互,这将导致插件之间的兼容性非常差——用户要么选择同时安装多个数据库,要么只能放弃一些功能。为此,Koishi 设计了一整套对象关系映射 (ORM) 接口,它易于扩展并广泛地运用于各种插件中,足以应对绝大部分使用场景。

## `get`:查询数据
本页中涉及的完整 API:

- [数据库操作](../../api/database/database.md)
- [查询表达式](../../api/database/query.md)
- [求值表达式](../../api/database/evaluation.md)

## `get`:查询数据 {#get}

使用 `database.get()` 方法以获取特定表中的数据。下面是一个最基本的形式:

Expand All @@ -28,13 +34,13 @@ await ctx.database.get('schedule', [1234], ['command', 'time'])
你还可以向第二个参数传入一个对象,用来查询非主键上的数据或者同时指定多列的值:

```ts
// 获取名为 schedule 的表中 assignee 为 telegram:123456 的数据行
// 获取名为 schedule 的表中 assignee 为 123456 或 456789 的数据行
await ctx.database.get('schedule', {
assignee: ['telegram:123456'],
assignee: ['123456', '456789'],
})
```

对于需要进行复杂的数据库搜索的,ORM 也提供了相对应的方法
对于需要进行复杂的数据库搜索的,你可以使用查询表达式

```ts
// 获取名为 schedule 的表中 id 大于 2 但是小于等于 5 的数据行
Expand All @@ -43,21 +49,31 @@ await ctx.database.get('schedule', {
})
```

我们甚至也支持逻辑运算
为了处理更加复杂的需求,我们还引入了求值表达式。它表现为一个接受 `row` 参数的回调函数,并通过一系列 `$` 上的运算返回一个 `Boolean` 类型的值。这种写法的表达能力更强,例如你可以让多个字段共同参与运算

```ts
import { $ } from 'koishi'

// 上述两个搜索条件的或运算
await ctx.database.get('schedule', {
$or: [
{ assignee: ['telegram:123456'] },
{ id: { $gt: 2, $lte: 5 } },
],
})
await ctx.database.get('schedule', (row) =>
$.or(
$.in(row.assignee, [
'123456',
'456789',
]),
$.and(
$.gt(row.id, 2),
$.lte(row.id, 5),
),
),
)
```

你可以在 [这里](../../api/database/query.md) 看到完整的查询表达式 API。
::: tip
虽然求值表达式在形式上是一个回调函数,但是 Koishi 并不会将数据全部拉取到内存中,而是会将这个函数的行为编译成相对应的查询语句,提交给数据库运行。因此你可以放心地使用这种写法,它并不会带来额外的性能问题 (如果你遇到查询性能瓶颈,这更有可能是数据模型本身导致的,例如缺少必要的索引)。
:::

## `create`:插入数据
## `create`:插入数据 {#create}

使用 `database.create()` 方法以插入数据。

Expand All @@ -67,9 +83,9 @@ await ctx.database.get('schedule', {
await ctx.database.create('schedule', data)
```

如果你想要批量插入数据,可以使用下面介绍的 `database.upsert()` 方法。
如果你想要批量插入数据,可以使用下面介绍的 [`database.upsert()`](#upsert) 方法。

## `set`:修改数据
## `set`:修改数据 {#set}

`database.set()` 方法需要传入三个参数:表名、查询条件和要修改的数据。

Expand All @@ -86,26 +102,23 @@ await ctx.database.set('schedule', 1234, {
```ts
// 让所有日期为今天的数据行的 count 字段在原有基础上增加 1
await ctx.database.set('foo', { date: new Date() }, (row) => ({
// { $add: [a, b] } 相当于 a + b
// { $: field } 相当于对当前行的 field 字段取值
count: $.add(row.count, 1),
}))
```

你可以在 [这里](../../api/database/evaluation.md) 看到完整的求值表达式 API。

## `upsert`:修改或插入数据
## `upsert`:修改或插入数据 {#upsert}

`database.upsert()` 的逻辑稍微有些不同,需要你传入一个数组:

```ts
// 用一个数组来对数据进行更新,你需要确保每一个元素都拥有这个数据表的主键
// 修改时只会用每一行中出现的键进行覆盖,不会影响未定义的字段
await ctx.database.upsert('foo', [
await ctx.database.upsert('foo', (row) => [
{ id: 1, foo: 'hello' },
{ id: 2, foo: 'world' },
// 这里同样支持求值表达式,$concat 可用于连接字符串
{ id: 3, bar: { $concat: ['koi', 'shi'] } },
// 如果此列存在,则会按照 $.concat() 的行为进行修改
// 如果此列不存在,row.bar 则会使用默认值
{ id: 3, bar: $.concat(row.bar, ['koishi']) },
])
```

Expand All @@ -122,20 +135,19 @@ await ctx.database.upsert('foo', [
| ---- | ---- | ---- | ---- |
| 1 | hello | baz | 该行已经存在,只更新了 foo 字段 |
| 2 | world | bar | 插入了新行,其中 foo 字段取自传入的数据,bar 字段取自默认值 |
| 3 | null | koishi | 插入了新行,其中 bar 字段取自传入的数据,foo 字段取自默认值 |
| 3 | null | barkoishi | 插入了新行,其中 bar 字段取自传入的数据,foo 字段取自默认值 |

如果想以非主键来索引要修改的数据,可以使用第三个参数:

```ts
// @errors: 2304
// 以非主键为基准对数据表进行更新,你需要确保每一个元素都拥有 telegram 属性
await ctx.database.upsert('user', rows, 'telegram')

// 以复合键为基准对数据表进行更新,你需要确保每一个元素都拥有 platform 和 id 属性
await ctx.database.upsert('channel', rows, ['platform', 'id'])
```

## `remove`:删除数据
## `remove`:删除数据 {#remove}

使用 `database.remove()` 方法以删除特定表中的数据。

Expand All @@ -145,7 +157,7 @@ await ctx.database.upsert('channel', rows, ['platform', 'id'])
await ctx.database.remove('schedule', [id])
```

## 获取改动行数
## 获取改动行数 {#write-result}

`set`, `upsert``remove` 操作都会返回一个 `WriteResult` 对象,它包含了这次操作的结果。你可以通过 `matched` 属性来获取匹配的数据行数 (注意不是修改的函数),通过 `inserted` 属性来获取插入的数据行数 (仅限 `upsert` 操作)。

Expand All @@ -162,7 +174,7 @@ if (!result.matched) {
}
```

## 对比 set 和 upsert
## 对比 set 和 upsert {#set-upsert}

`set``upsert` 方法都可以用于修改已经存在的数据,它们的区别如下表所示:

Expand All @@ -171,7 +183,7 @@ if (!result.matched) {
| 作用范围 | 支持复杂的查询表达式 | 只能限定特定字段的值 |
| 插入行为 | 如果数据不存在则不会进行任何操作 | 如果数据不存在则会插入新行 |

## 对比 create 和 upsert
## 对比 create 和 upsert {#create-upsert}

`create``upsert` 方法都可以用于插入新的数据,它们的区别如下表所示:

Expand Down
14 changes: 6 additions & 8 deletions zh-CN/guide/database/selection.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ ctx.database.get('foo', { id: { $gt: 5 } })
// 等价于
ctx.database
.select('foo')
.where({ id: { $gt: 5 } })
.where(row => $.gt(row.id, 5))
.execute()
```

Expand All @@ -31,7 +31,7 @@ ctx.database

## 求值表达式

`.orderBy()``.where()` 方法都支持传入一个函数,这个函数会接受一个 `row` 参数,表示当前正在处理的数据行。你可以在这个函数中返回一个值,这个值会被用于排序或筛选。
`.orderBy()``.where()` 等方法都支持传入一个函数,这个函数会接受一个 `row` 参数,表示当前正在处理的数据行。你可以在这个函数中返回一个值,这个值会被用于排序或筛选。

```ts
// 返回 id 大于 5 的数据行,并按 id 升序排列
Expand All @@ -42,8 +42,6 @@ ctx.database
.execute()
```

这里的 `$.gt()` 是一个求值表达式。你可以在 [这里](../../api/database/evaluation.md) 看到完整的求值表达式 API。

## 字段映射

`.project()` 方法可以用于映射查询结果。它接受一个对象,对象的键表示要映射的字段名,值表示映射的表达式。下面是一个例子:
Expand Down Expand Up @@ -143,15 +141,15 @@ ctx.database
```ts
// 返回的数据包含 foo, bar 两个属性
ctx.database
.join(['foo', 'bar'] as const, (foo, bar) => $.eq(foo.id, bar.id))
.join(['foo', 'bar'], (foo, bar) => $.eq(foo.id, bar.id))
.execute()
```

普通的 `.orderBy()``.where()` 方法不支持字段中带有 `.` 符号。因此在使用连接查询时,你需要使用[求值表达式](#求值表达式)

```ts
ctx.database
.join(['foo', 'bar'] as const, (foo, bar) => $.eq(foo.id, bar.id))
.join(['foo', 'bar'], (foo, bar) => $.eq(foo.id, bar.id))
.orderBy(row => row.foo.id)
.execute()
```
Expand All @@ -161,7 +159,7 @@ ctx.database
```ts
// 返回的数据包含 t1, t2 两个属性
ctx.database
.join({ t1: 'foo', t2: 'bar', }, row => $.eq(row.t1.id, row.t2.id))
.join({ t1: 'foo', t2: 'bar' }, row => $.eq(row.t1.id, row.t2.id))
.orderBy(row => row.t1.id)
.execute();
.execute()
```

0 comments on commit a2271df

Please sign in to comment.