wok-server 0.4.13 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +61 -0
- package/README.md +44 -29
- package/dist/cache/cache.js +98 -98
- package/dist/cache/config.js +19 -19
- package/dist/cache/index.js +27 -27
- package/dist/cache/purge-task.js +46 -46
- package/dist/cache/stat.js +47 -47
- package/dist/config/convert.js +36 -36
- package/dist/config/exception.js +14 -14
- package/dist/config/index.js +81 -81
- package/dist/http-client/index.js +136 -136
- package/dist/i18n/ar.js +17 -17
- package/dist/i18n/de.js +17 -17
- package/dist/i18n/en-us.js +17 -17
- package/dist/i18n/es.js +17 -17
- package/dist/i18n/fr.js +17 -17
- package/dist/i18n/i18n.js +231 -231
- package/dist/i18n/index.js +52 -52
- package/dist/i18n/ja.js +17 -17
- package/dist/i18n/ko.js +17 -17
- package/dist/i18n/msg.js +2 -2
- package/dist/i18n/pt.js +17 -17
- package/dist/i18n/ru.js +17 -17
- package/dist/i18n/tag.js +18 -18
- package/dist/i18n/zh-HK.js +17 -17
- package/dist/i18n/zh-TW.js +17 -17
- package/dist/i18n/zh-cn.js +17 -17
- package/dist/index.js +14 -14
- package/dist/lock/index.js +114 -114
- package/dist/log/config.js +35 -29
- package/dist/log/date.js +21 -21
- package/dist/log/file.js +198 -72
- package/dist/log/index.js +135 -105
- package/dist/log/level.js +33 -33
- package/dist/log/log.js +56 -0
- package/dist/log/store.js +19 -16
- package/dist/mongodb/collection.js +2 -2
- package/dist/mongodb/config.js +34 -34
- package/dist/mongodb/doc.js +2 -2
- package/dist/mongodb/exception.js +14 -14
- package/dist/mongodb/index.js +58 -58
- package/dist/mongodb/manager/base.js +563 -563
- package/dist/mongodb/manager/index.js +63 -63
- package/dist/mongodb/manager/tx-strict.js +84 -84
- package/dist/mongodb/manager/tx.js +30 -30
- package/dist/mongodb/migration.js +52 -52
- package/dist/mvc/access-log.js +33 -33
- package/dist/mvc/config.js +27 -27
- package/dist/mvc/exchange.js +113 -113
- package/dist/mvc/handler/index.js +7 -6
- package/dist/mvc/handler/json.js +65 -65
- package/dist/mvc/handler/restful.js +35 -35
- package/dist/mvc/handler/sse.js +65 -0
- package/dist/mvc/handler/upload.js +31 -31
- package/dist/mvc/index.js +50 -50
- package/dist/mvc/interceptor.js +2 -2
- package/dist/mvc/query.js +43 -43
- package/dist/mvc/render/file.js +132 -132
- package/dist/mvc/render/html/html.js +90 -90
- package/dist/mvc/render/html/index.js +18 -18
- package/dist/mvc/render/html/style.js +2 -2
- package/dist/mvc/render/index.js +7 -7
- package/dist/mvc/render/json.js +26 -26
- package/dist/mvc/render/text.js +16 -16
- package/dist/mvc/router.js +2 -2
- package/dist/mvc/server.js +272 -272
- package/dist/mvc/static/header.js +67 -67
- package/dist/mvc/static/index.js +6 -6
- package/dist/mvc/static/mime-type.js +84 -84
- package/dist/mvc/static/server-cache-config.js +66 -66
- package/dist/mvc/static/server-cache.js +133 -133
- package/dist/mvc/static/static-handler.js +372 -372
- package/dist/mysql/config.js +51 -51
- package/dist/mysql/exception.js +14 -14
- package/dist/mysql/index.js +87 -87
- package/dist/mysql/manager/base.js +239 -231
- package/dist/mysql/manager/index.js +107 -107
- package/dist/mysql/manager/ops/count.js +20 -20
- package/dist/mysql/manager/ops/criteria.js +356 -356
- package/dist/mysql/manager/ops/delete.js +65 -65
- package/dist/mysql/manager/ops/exist.js +26 -26
- package/dist/mysql/manager/ops/find.js +169 -130
- package/dist/mysql/manager/ops/index.js +14 -14
- package/dist/mysql/manager/ops/insert.js +106 -106
- package/dist/mysql/manager/ops/modify.js +10 -10
- package/dist/mysql/manager/ops/paginate.js +23 -23
- package/dist/mysql/manager/ops/query.js +9 -9
- package/dist/mysql/manager/ops/update.js +216 -216
- package/dist/mysql/manager/ops/utils.js +24 -24
- package/dist/mysql/manager/tx-strict.js +103 -100
- package/dist/mysql/manager/tx.js +30 -30
- package/dist/mysql/manager/utils.js +56 -56
- package/dist/mysql/migration.js +136 -136
- package/dist/mysql/table-info.js +8 -8
- package/dist/task/daily.js +59 -59
- package/dist/task/fixed-delay.js +38 -38
- package/dist/task/fixed-rate.js +42 -42
- package/dist/task/index.js +9 -9
- package/dist/task/task.js +56 -56
- package/dist/validation/exception.js +36 -36
- package/dist/validation/index.js +40 -40
- package/dist/validation/validator/array.js +34 -34
- package/dist/validation/validator/enum.js +28 -28
- package/dist/validation/validator/index.js +14 -14
- package/dist/validation/validator/length.js +40 -40
- package/dist/validation/validator/max-length.js +35 -35
- package/dist/validation/validator/max.js +29 -29
- package/dist/validation/validator/min-length.js +33 -33
- package/dist/validation/validator/min.js +29 -29
- package/dist/validation/validator/not-blank.js +33 -33
- package/dist/validation/validator/not-null.js +21 -21
- package/dist/validation/validator/plain-obj.js +32 -32
- package/dist/validation/validator/regexp.js +34 -34
- package/documentation/en/cache.md +56 -0
- package/documentation/en/config.md +96 -0
- package/documentation/en/engineering.md +256 -0
- package/documentation/en/http-client.md +32 -0
- package/documentation/en/i18n.md +143 -0
- package/documentation/en/index.md +24 -0
- package/documentation/en/lock.md +51 -0
- package/documentation/en/log.md +109 -0
- package/documentation/en/mongodb.md +256 -0
- package/documentation/en/mvc.md +688 -0
- package/documentation/en/mysql.md +552 -0
- package/documentation/en/task.md +45 -0
- package/documentation/en/test.md +56 -0
- package/documentation/en/validate.md +130 -0
- package/documentation/zh-cn/engineering.md +1 -1
- package/documentation/zh-cn/log.md +81 -8
- package/documentation/zh-cn/mvc.md +66 -24
- package/documentation/zh-cn/mysql.md +24 -23
- package/documentation/zh-cn/validate.md +2 -2
- package/package.json +3 -1
- package/skills/wok-server-api-rules/SKILL.md +350 -0
- package/skills/wok-server-cache/SKILL.md +216 -0
- package/skills/wok-server-config/SKILL.md +200 -0
- package/skills/wok-server-getting-started/SKILL.md +123 -0
- package/skills/wok-server-getting-started/references/engineering.md +169 -0
- package/skills/wok-server-http-client/SKILL.md +164 -0
- package/skills/wok-server-i18n/SKILL.md +214 -0
- package/skills/wok-server-lock/SKILL.md +144 -0
- package/skills/wok-server-log/SKILL.md +218 -0
- package/skills/wok-server-mongodb/SKILL.md +235 -0
- package/skills/wok-server-mvc/SKILL.md +251 -0
- package/skills/wok-server-mvc/references/respond-html.md +157 -0
- package/skills/wok-server-mvc/references/sse.md +121 -0
- package/skills/wok-server-mvc/references/static-files.md +47 -0
- package/skills/wok-server-mvc/references/upload.md +62 -0
- package/skills/wok-server-mvc/references/websocket.md +30 -0
- package/skills/wok-server-mysql/SKILL.md +315 -0
- package/skills/wok-server-mysql/references/multi-datasource.md +76 -0
- package/skills/wok-server-mysql/references/version-control.md +22 -0
- package/skills/wok-server-task/SKILL.md +158 -0
- package/skills/wok-server-validate/SKILL.md +167 -0
- package/src/cache/cache.ts +118 -0
- package/src/cache/config.ts +53 -0
- package/src/cache/index.ts +27 -0
- package/src/cache/purge-task.ts +53 -0
- package/src/cache/stat.ts +47 -0
- package/src/config/convert.ts +27 -0
- package/src/config/exception.ts +8 -0
- package/src/config/index.ts +92 -0
- package/src/http-client/index.ts +202 -0
- package/src/i18n/ar.ts +16 -0
- package/src/i18n/de.ts +16 -0
- package/src/i18n/en-us.ts +16 -0
- package/src/i18n/es.ts +16 -0
- package/src/i18n/fr.ts +16 -0
- package/src/i18n/i18n.ts +230 -0
- package/src/i18n/index.ts +50 -0
- package/src/i18n/ja.ts +16 -0
- package/src/i18n/ko.ts +16 -0
- package/src/i18n/msg.ts +50 -0
- package/src/i18n/pt.ts +16 -0
- package/src/i18n/ru.ts +16 -0
- package/src/i18n/tag.ts +18 -0
- package/src/i18n/zh-HK.ts +16 -0
- package/src/i18n/zh-TW.ts +16 -0
- package/src/i18n/zh-cn.ts +16 -0
- package/src/index.ts +11 -0
- package/src/lock/index.ts +164 -0
- package/src/log/config.ts +71 -0
- package/src/log/date.ts +19 -0
- package/src/log/file.ts +215 -0
- package/src/log/index.ts +136 -0
- package/src/log/level.ts +29 -0
- package/src/log/log.ts +77 -0
- package/src/log/store.ts +31 -0
- package/src/mongodb/collection.ts +25 -0
- package/src/mongodb/config.ts +69 -0
- package/src/mongodb/doc.ts +12 -0
- package/src/mongodb/exception.ts +8 -0
- package/src/mongodb/index.ts +71 -0
- package/src/mongodb/manager/base.ts +674 -0
- package/src/mongodb/manager/index.ts +80 -0
- package/src/mongodb/manager/tx-strict.ts +153 -0
- package/src/mongodb/manager/tx.ts +34 -0
- package/src/mongodb/migration.ts +66 -0
- package/src/mvc/access-log.ts +33 -0
- package/src/mvc/config.ts +70 -0
- package/src/mvc/exchange.ts +126 -0
- package/src/mvc/handler/index.ts +4 -0
- package/src/mvc/handler/json.ts +96 -0
- package/src/mvc/handler/restful.ts +39 -0
- package/src/mvc/handler/sse.ts +90 -0
- package/src/mvc/handler/upload.ts +54 -0
- package/src/mvc/index.ts +48 -0
- package/src/mvc/interceptor.ts +12 -0
- package/src/mvc/query.ts +36 -0
- package/src/mvc/render/file.ts +148 -0
- package/src/mvc/render/html/html.ts +187 -0
- package/src/mvc/render/html/index.ts +16 -0
- package/src/mvc/render/html/style.ts +1201 -0
- package/src/mvc/render/index.ts +4 -0
- package/src/mvc/render/json.ts +24 -0
- package/src/mvc/render/text.ts +14 -0
- package/src/mvc/router.ts +13 -0
- package/src/mvc/server.ts +315 -0
- package/src/mvc/static/header.ts +86 -0
- package/src/mvc/static/index.ts +3 -0
- package/src/mvc/static/mime-type.ts +81 -0
- package/src/mvc/static/server-cache-config.ts +92 -0
- package/src/mvc/static/server-cache.ts +171 -0
- package/src/mvc/static/static-handler.ts +445 -0
- package/src/mysql/config.ts +130 -0
- package/src/mysql/exception.ts +8 -0
- package/src/mysql/index.ts +88 -0
- package/src/mysql/manager/base.ts +285 -0
- package/src/mysql/manager/index.ts +112 -0
- package/src/mysql/manager/ops/count.ts +30 -0
- package/src/mysql/manager/ops/criteria.ts +412 -0
- package/src/mysql/manager/ops/delete.ts +96 -0
- package/src/mysql/manager/ops/exist.ts +41 -0
- package/src/mysql/manager/ops/find.ts +226 -0
- package/src/mysql/manager/ops/index.ts +11 -0
- package/src/mysql/manager/ops/insert.ts +120 -0
- package/src/mysql/manager/ops/modify.ts +14 -0
- package/src/mysql/manager/ops/paginate.ts +60 -0
- package/src/mysql/manager/ops/query.ts +13 -0
- package/src/mysql/manager/ops/update.ts +294 -0
- package/src/mysql/manager/ops/utils.ts +20 -0
- package/src/mysql/manager/tx-strict.ts +138 -0
- package/src/mysql/manager/tx.ts +31 -0
- package/src/mysql/manager/utils.ts +75 -0
- package/src/mysql/migration.ts +149 -0
- package/src/mysql/table-info.ts +41 -0
- package/src/task/daily.ts +70 -0
- package/src/task/fixed-delay.ts +45 -0
- package/src/task/fixed-rate.ts +49 -0
- package/src/task/index.ts +4 -0
- package/src/task/task.ts +70 -0
- package/src/validation/exception.ts +27 -0
- package/src/validation/index.ts +61 -0
- package/src/validation/validator/array.ts +32 -0
- package/src/validation/validator/enum.ts +25 -0
- package/src/validation/validator/index.ts +11 -0
- package/src/validation/validator/length.ts +42 -0
- package/src/validation/validator/max-length.ts +33 -0
- package/src/validation/validator/max.ts +26 -0
- package/src/validation/validator/min-length.ts +31 -0
- package/src/validation/validator/min.ts +26 -0
- package/src/validation/validator/not-blank.ts +31 -0
- package/src/validation/validator/not-null.ts +19 -0
- package/src/validation/validator/plain-obj.ts +30 -0
- package/src/validation/validator/regexp.ts +32 -0
- package/types/cache/cache.d.ts +52 -52
- package/types/cache/config.d.ts +32 -32
- package/types/cache/index.d.ts +2 -2
- package/types/cache/purge-task.d.ts +11 -11
- package/types/cache/stat.d.ts +26 -26
- package/types/config/convert.d.ts +6 -6
- package/types/config/exception.d.ts +7 -7
- package/types/config/index.d.ts +25 -25
- package/types/http-client/index.d.ts +71 -71
- package/types/i18n/ar.d.ts +2 -2
- package/types/i18n/de.d.ts +2 -2
- package/types/i18n/en-us.d.ts +2 -2
- package/types/i18n/es.d.ts +2 -2
- package/types/i18n/fr.d.ts +2 -2
- package/types/i18n/i18n.d.ts +102 -102
- package/types/i18n/index.d.ts +9 -9
- package/types/i18n/ja.d.ts +2 -2
- package/types/i18n/ko.d.ts +2 -2
- package/types/i18n/msg.d.ts +50 -50
- package/types/i18n/pt.d.ts +2 -2
- package/types/i18n/ru.d.ts +2 -2
- package/types/i18n/tag.d.ts +11 -11
- package/types/i18n/zh-HK.d.ts +2 -2
- package/types/i18n/zh-TW.d.ts +2 -2
- package/types/i18n/zh-cn.d.ts +2 -2
- package/types/index.d.ts +11 -11
- package/types/lock/index.d.ts +64 -64
- package/types/log/config.d.ts +35 -27
- package/types/log/date.d.ts +2 -2
- package/types/log/file.d.ts +13 -5
- package/types/log/index.d.ts +53 -34
- package/types/log/level.d.ts +14 -14
- package/types/log/log.d.ts +40 -0
- package/types/log/store.d.ts +19 -12
- package/types/mongodb/collection.d.ts +25 -25
- package/types/mongodb/config.d.ts +45 -45
- package/types/mongodb/doc.d.ts +11 -11
- package/types/mongodb/exception.d.ts +7 -7
- package/types/mongodb/index.d.ts +29 -29
- package/types/mongodb/manager/base.d.ts +188 -188
- package/types/mongodb/manager/index.d.ts +38 -38
- package/types/mongodb/manager/tx-strict.d.ts +41 -41
- package/types/mongodb/manager/tx.d.ts +21 -21
- package/types/mongodb/migration.d.ts +12 -12
- package/types/mvc/access-log.d.ts +7 -7
- package/types/mvc/config.d.ts +42 -42
- package/types/mvc/exchange.d.ts +72 -72
- package/types/mvc/handler/index.d.ts +4 -3
- package/types/mvc/handler/json.d.ts +44 -44
- package/types/mvc/handler/restful.d.ts +11 -11
- package/types/mvc/handler/sse.d.ts +34 -0
- package/types/mvc/handler/upload.d.ts +36 -36
- package/types/mvc/index.d.ts +22 -22
- package/types/mvc/interceptor.d.ts +11 -11
- package/types/mvc/query.d.ts +13 -13
- package/types/mvc/render/file.d.ts +10 -10
- package/types/mvc/render/html/html.d.ts +98 -98
- package/types/mvc/render/html/index.d.ts +11 -11
- package/types/mvc/render/html/style.d.ts +1201 -1201
- package/types/mvc/render/index.d.ts +4 -4
- package/types/mvc/render/json.d.ts +17 -17
- package/types/mvc/render/text.d.ts +10 -10
- package/types/mvc/router.d.ts +11 -11
- package/types/mvc/server.d.ts +90 -90
- package/types/mvc/static/header.d.ts +27 -27
- package/types/mvc/static/index.d.ts +3 -3
- package/types/mvc/static/mime-type.d.ts +2 -2
- package/types/mvc/static/server-cache-config.d.ts +30 -30
- package/types/mvc/static/server-cache.d.ts +76 -76
- package/types/mvc/static/static-handler.d.ts +77 -77
- package/types/mysql/config.d.ts +90 -90
- package/types/mysql/exception.d.ts +7 -7
- package/types/mysql/index.d.ts +16 -16
- package/types/mysql/manager/base.d.ts +165 -159
- package/types/mysql/manager/index.d.ts +36 -36
- package/types/mysql/manager/ops/count.d.ts +13 -13
- package/types/mysql/manager/ops/criteria.d.ts +134 -134
- package/types/mysql/manager/ops/delete.d.ts +46 -46
- package/types/mysql/manager/ops/exist.d.ts +6 -6
- package/types/mysql/manager/ops/find.d.ts +86 -70
- package/types/mysql/manager/ops/index.d.ts +10 -10
- package/types/mysql/manager/ops/insert.d.ts +18 -18
- package/types/mysql/manager/ops/modify.d.ts +3 -3
- package/types/mysql/manager/ops/paginate.d.ts +36 -36
- package/types/mysql/manager/ops/query.d.ts +3 -3
- package/types/mysql/manager/ops/update.d.ts +76 -76
- package/types/mysql/manager/ops/utils.d.ts +5 -5
- package/types/mysql/manager/tx-strict.d.ts +36 -35
- package/types/mysql/manager/tx.d.ts +15 -15
- package/types/mysql/manager/utils.d.ts +17 -17
- package/types/mysql/migration.d.ts +8 -8
- package/types/mysql/table-info.d.ts +36 -36
- package/types/task/daily.d.ts +16 -16
- package/types/task/fixed-delay.d.ts +9 -9
- package/types/task/fixed-rate.d.ts +9 -9
- package/types/task/index.d.ts +4 -4
- package/types/task/task.d.ts +34 -34
- package/types/validation/exception.d.ts +38 -38
- package/types/validation/index.d.ts +32 -32
- package/types/validation/validator/array.d.ts +5 -5
- package/types/validation/validator/enum.d.ts +8 -8
- package/types/validation/validator/index.d.ts +11 -11
- package/types/validation/validator/length.d.ts +10 -10
- package/types/validation/validator/max-length.d.ts +8 -8
- package/types/validation/validator/max.d.ts +7 -7
- package/types/validation/validator/min-length.d.ts +6 -6
- package/types/validation/validator/min.d.ts +7 -7
- package/types/validation/validator/not-blank.d.ts +7 -7
- package/types/validation/validator/not-null.d.ts +6 -6
- package/types/validation/validator/plain-obj.d.ts +7 -7
- package/types/validation/validator/regexp.d.ts +8 -8
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { PoolConnection, ResultSetHeader } from 'mysql2'
|
|
2
|
+
import { MysqlException } from '../../exception'
|
|
3
|
+
import { Table } from '../../table-info'
|
|
4
|
+
import { MixCriteria, buildQuery } from './criteria'
|
|
5
|
+
import { promiseQuery } from '../utils'
|
|
6
|
+
import { MysqlConfig } from '../../config'
|
|
7
|
+
import { processColumnValue } from './utils'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 更新
|
|
11
|
+
* @param config
|
|
12
|
+
* @param connection
|
|
13
|
+
* @param mapping
|
|
14
|
+
* @param data
|
|
15
|
+
* @returns
|
|
16
|
+
*/
|
|
17
|
+
export async function update<T>(
|
|
18
|
+
config: MysqlConfig,
|
|
19
|
+
connection: PoolConnection,
|
|
20
|
+
table: Table<T>,
|
|
21
|
+
data: T
|
|
22
|
+
): Promise<T> {
|
|
23
|
+
// 列信息
|
|
24
|
+
let columns: Array<keyof T> = [...table.columns]
|
|
25
|
+
// 创建时间不更新
|
|
26
|
+
if (table.createdDate) {
|
|
27
|
+
const { column } = table.createdDate
|
|
28
|
+
columns = columns.filter(col => col !== column)
|
|
29
|
+
}
|
|
30
|
+
if (table.updatedDate) {
|
|
31
|
+
const updatedDate = table.updatedDate.type === 'date' ? new Date() : new Date().getTime()
|
|
32
|
+
data[table.updatedDate.column] = updatedDate as any
|
|
33
|
+
columns.push(table.updatedDate.column)
|
|
34
|
+
}
|
|
35
|
+
// 构建 sql
|
|
36
|
+
const sql = `update ?? set ${columns.map(() => ' ?? = ? ').join(',')} where ?? = ?`
|
|
37
|
+
// 值
|
|
38
|
+
const values: any[] = [
|
|
39
|
+
table.tableName,
|
|
40
|
+
...columns.flatMap(col => [col, processColumnValue(data[col])]),
|
|
41
|
+
table.id,
|
|
42
|
+
data[table.id]
|
|
43
|
+
]
|
|
44
|
+
const res = await promiseQuery(config, connection, sql, values)
|
|
45
|
+
const packet = res as ResultSetHeader
|
|
46
|
+
if (packet.affectedRows !== 1) {
|
|
47
|
+
throw new MysqlException(
|
|
48
|
+
`Failed to update record, possibly due to non-existent record,table:${
|
|
49
|
+
table.tableName
|
|
50
|
+
},primary key: ${data[table.id]}`
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
return data
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 更新器
|
|
57
|
+
*/
|
|
58
|
+
export type Updater<T> = Partial<{
|
|
59
|
+
// 普通的更新赋值
|
|
60
|
+
[key in keyof T]:
|
|
61
|
+
| T[key]
|
|
62
|
+
// undefined 表示不参与更新,作用是方便编写一些特殊的逻辑,比如特定情况下不更新
|
|
63
|
+
| undefined
|
|
64
|
+
// 将字段置空,置空是不能使用 null 类型的,必须使用元组 ['setNull']
|
|
65
|
+
| ['setNull']
|
|
66
|
+
// 将字段自增
|
|
67
|
+
| ['inc', number]
|
|
68
|
+
/**
|
|
69
|
+
* 设置一个字段的值,和直接赋值是一样的,作用是解决一些特殊的情况的冲突
|
|
70
|
+
* 比如将 json 字段的值设置为 ['setNull'] ,这就会被认为是要置空,
|
|
71
|
+
* 使用 ['set',['setNull']] 就可以解决这个问题
|
|
72
|
+
*/
|
|
73
|
+
| ['set', T[key]]
|
|
74
|
+
}>
|
|
75
|
+
/**
|
|
76
|
+
* 转换更新器
|
|
77
|
+
* @param table
|
|
78
|
+
* @param updater
|
|
79
|
+
* @returns
|
|
80
|
+
*/
|
|
81
|
+
function updatorToSql<T>(table: Table<T>, updater: Updater<T>): { sql: string; values: any[] } {
|
|
82
|
+
const values: any[] = []
|
|
83
|
+
// 更新操作
|
|
84
|
+
const updateFragList: string[] = []
|
|
85
|
+
// 更新时间
|
|
86
|
+
if (table.updatedDate) {
|
|
87
|
+
const updatedDate = table.updatedDate.type === 'date' ? new Date() : new Date().getTime()
|
|
88
|
+
updateFragList.push(' ?? = ?')
|
|
89
|
+
values.push(table.updatedDate.column, updatedDate)
|
|
90
|
+
}
|
|
91
|
+
for (const column in updater) {
|
|
92
|
+
// 过滤掉id
|
|
93
|
+
if (column === table.id) {
|
|
94
|
+
continue
|
|
95
|
+
}
|
|
96
|
+
const val = updater[column]
|
|
97
|
+
// undefined 表示不参与更新,作用是方便编写一些特殊的逻辑,比如特定情况下不更新
|
|
98
|
+
if (val === undefined) {
|
|
99
|
+
continue
|
|
100
|
+
}
|
|
101
|
+
// 兼容将值设置成 null 的情况,和 ['setNull’] 等同
|
|
102
|
+
if (val === null) {
|
|
103
|
+
updateFragList.push(' ?? = NULL ')
|
|
104
|
+
values.push(column)
|
|
105
|
+
continue
|
|
106
|
+
}
|
|
107
|
+
if (Array.isArray(val)) {
|
|
108
|
+
// set null
|
|
109
|
+
if (val[0] === 'setNull') {
|
|
110
|
+
updateFragList.push(' ?? = NULL ')
|
|
111
|
+
values.push(column)
|
|
112
|
+
continue
|
|
113
|
+
}
|
|
114
|
+
if (val[0] === 'inc') {
|
|
115
|
+
updateFragList.push(' ?? = ?? + ? ')
|
|
116
|
+
values.push(column, column, val[1])
|
|
117
|
+
continue
|
|
118
|
+
}
|
|
119
|
+
if (val[0] === 'set') {
|
|
120
|
+
updateFragList.push(' ?? = ? ')
|
|
121
|
+
values.push(column, processColumnValue(val[1]))
|
|
122
|
+
continue
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
updateFragList.push(' ?? = ? ')
|
|
126
|
+
values.push(column, processColumnValue(val))
|
|
127
|
+
}
|
|
128
|
+
return { sql: updateFragList.join(','), values }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 部分更新
|
|
133
|
+
* @param connection
|
|
134
|
+
* @param mapping
|
|
135
|
+
* @param type
|
|
136
|
+
* @param data
|
|
137
|
+
*/
|
|
138
|
+
export async function partialUpdate<T>(
|
|
139
|
+
config: MysqlConfig,
|
|
140
|
+
connection: PoolConnection,
|
|
141
|
+
table: Table<T>,
|
|
142
|
+
data: Updater<T>
|
|
143
|
+
): Promise<boolean> {
|
|
144
|
+
if (!data[table.id]) {
|
|
145
|
+
throw new MysqlException(
|
|
146
|
+
`Can't do a partial update, the data to be updated does not contain a primary key,table: ${
|
|
147
|
+
table.tableName
|
|
148
|
+
},column:${JSON.stringify(data)}`
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
const id = data[table.id]
|
|
152
|
+
if (typeof id !== 'string' && typeof id !== 'number') {
|
|
153
|
+
throw new MysqlException('Primary key can only be of string or number type')
|
|
154
|
+
}
|
|
155
|
+
if (Object.keys(data).length < 2) {
|
|
156
|
+
throw new MysqlException(
|
|
157
|
+
`Can't do a partial update, data must contain at least one column outside of the primary key,table: ${
|
|
158
|
+
table.tableName
|
|
159
|
+
},column:${JSON.stringify(data)}`
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
const fieldNames = Object.keys(data)
|
|
163
|
+
for (const name of fieldNames) {
|
|
164
|
+
if (name !== table.id && !table.columns.some(col => col === name)) {
|
|
165
|
+
throw new MysqlException(
|
|
166
|
+
`Can't do a partial update,there are unconfigured columns in the data,table: ${table.tableName},unconfigured column:${name}`
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
let sql = ` update ?? `
|
|
171
|
+
const values: any[] = [table.tableName]
|
|
172
|
+
// 更新操作
|
|
173
|
+
const convertRes = updatorToSql(table, data)
|
|
174
|
+
if (!convertRes.sql) {
|
|
175
|
+
throw new MysqlException('No fields were specified to be updated!')
|
|
176
|
+
}
|
|
177
|
+
values.push(...convertRes.values)
|
|
178
|
+
sql += ` set ${convertRes.sql} where ?? = ?`
|
|
179
|
+
values.push(table.id, id)
|
|
180
|
+
|
|
181
|
+
const res = await promiseQuery(config, connection, sql, values)
|
|
182
|
+
const packet = res as ResultSetHeader
|
|
183
|
+
return packet.affectedRows === 1
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* 更新指定的一条记录
|
|
187
|
+
* @param config
|
|
188
|
+
* @param connection
|
|
189
|
+
* @param table
|
|
190
|
+
* @param query
|
|
191
|
+
* @param data
|
|
192
|
+
*/
|
|
193
|
+
export async function updateOne<T>(
|
|
194
|
+
config: MysqlConfig,
|
|
195
|
+
connection: PoolConnection,
|
|
196
|
+
table: Table<T>,
|
|
197
|
+
query: MixCriteria<T>,
|
|
198
|
+
updater: Updater<T>
|
|
199
|
+
): Promise<boolean> {
|
|
200
|
+
const values: any[] = []
|
|
201
|
+
const mysqlQuery = buildQuery(query)
|
|
202
|
+
if (!mysqlQuery) {
|
|
203
|
+
throw new MysqlException('No valid criteria specified.')
|
|
204
|
+
}
|
|
205
|
+
let sql = ` update ?? `
|
|
206
|
+
values.push(table.tableName)
|
|
207
|
+
// 更新操作
|
|
208
|
+
const convertRes = updatorToSql(table, updater)
|
|
209
|
+
if (!convertRes.sql) {
|
|
210
|
+
throw new MysqlException('No fields were specified to be updated!')
|
|
211
|
+
}
|
|
212
|
+
sql += ` set ${convertRes.sql} `
|
|
213
|
+
values.push(...convertRes.values)
|
|
214
|
+
sql += ` where ${mysqlQuery.sql} limit 1`
|
|
215
|
+
values.push(...mysqlQuery.values)
|
|
216
|
+
const res = await promiseQuery(config, connection, sql, values)
|
|
217
|
+
const packet = res as ResultSetHeader
|
|
218
|
+
return packet.affectedRows === 1
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* 更新选项
|
|
222
|
+
*/
|
|
223
|
+
export interface UpdateOpts<T> {
|
|
224
|
+
/**
|
|
225
|
+
* 表
|
|
226
|
+
*/
|
|
227
|
+
table: Table<T>
|
|
228
|
+
/**
|
|
229
|
+
* 查询条件
|
|
230
|
+
*/
|
|
231
|
+
query: MixCriteria<T>
|
|
232
|
+
/**
|
|
233
|
+
* 限制数量
|
|
234
|
+
*/
|
|
235
|
+
limit?: number
|
|
236
|
+
/**
|
|
237
|
+
* 排序规则,按先后顺序放入,每个规则是一个元组,第一个元素是字段名称,第二个元素是顺序
|
|
238
|
+
*/
|
|
239
|
+
orderBy?: Array<[keyof T, 'asc' | 'desc']>
|
|
240
|
+
/**
|
|
241
|
+
* 更新设置
|
|
242
|
+
*/
|
|
243
|
+
updater: Updater<T>
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* 更新所有匹配条件的记录
|
|
248
|
+
* @param config
|
|
249
|
+
* @param connection 连接
|
|
250
|
+
* @param table 表
|
|
251
|
+
* @param query 查询条件
|
|
252
|
+
* @param updater 更新操作,支持置空和递增(需要使用元组)
|
|
253
|
+
*/
|
|
254
|
+
export async function updateMany<T>(
|
|
255
|
+
config: MysqlConfig,
|
|
256
|
+
connection: PoolConnection,
|
|
257
|
+
opts: UpdateOpts<T>
|
|
258
|
+
): Promise<number> {
|
|
259
|
+
const values: any[] = []
|
|
260
|
+
const mysqlQuery = buildQuery(opts.query)
|
|
261
|
+
if (!mysqlQuery) {
|
|
262
|
+
throw new MysqlException('No valid criteria specified.')
|
|
263
|
+
}
|
|
264
|
+
let sql = ` update ?? `
|
|
265
|
+
values.push(opts.table.tableName)
|
|
266
|
+
// 更新操作
|
|
267
|
+
const convertRes = updatorToSql(opts.table, opts.updater)
|
|
268
|
+
if (!convertRes.sql) {
|
|
269
|
+
throw new MysqlException('No fields were specified to be updated!')
|
|
270
|
+
}
|
|
271
|
+
sql += ` set ${convertRes.sql} `
|
|
272
|
+
values.push(...convertRes.values)
|
|
273
|
+
sql += ` where ${mysqlQuery.sql} `
|
|
274
|
+
values.push(...mysqlQuery.values)
|
|
275
|
+
// 排序
|
|
276
|
+
if (opts.orderBy && opts.orderBy.length) {
|
|
277
|
+
opts.orderBy.forEach((orderBy, idx) => {
|
|
278
|
+
const [field, sort] = orderBy
|
|
279
|
+
if (idx == 0) {
|
|
280
|
+
sql += ` order by ?? ${sort} `
|
|
281
|
+
} else {
|
|
282
|
+
sql += ` , ?? ${sort} `
|
|
283
|
+
}
|
|
284
|
+
values.push(field)
|
|
285
|
+
})
|
|
286
|
+
}
|
|
287
|
+
// 数量限制
|
|
288
|
+
if (opts.limit) {
|
|
289
|
+
sql += ` limit ${opts.limit} `
|
|
290
|
+
}
|
|
291
|
+
const res = await promiseQuery(config, connection, sql, values)
|
|
292
|
+
const packet = res as ResultSetHeader
|
|
293
|
+
return packet.affectedRows
|
|
294
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 处理列的值,对特殊类型 json 进行加工处理后返回
|
|
3
|
+
* @param value
|
|
4
|
+
*/
|
|
5
|
+
export function processColumnValue(value: any) {
|
|
6
|
+
// date 类型 typeof 也是 object ,先排除
|
|
7
|
+
if (value instanceof Date) {
|
|
8
|
+
return value
|
|
9
|
+
}
|
|
10
|
+
// buffer 也是
|
|
11
|
+
if (value instanceof Buffer) {
|
|
12
|
+
return value
|
|
13
|
+
}
|
|
14
|
+
// json
|
|
15
|
+
if (typeof value === 'object') {
|
|
16
|
+
return JSON.stringify(value)
|
|
17
|
+
}
|
|
18
|
+
// 其它的情况直接返回
|
|
19
|
+
return value
|
|
20
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { PoolConnection } from 'mysql2'
|
|
2
|
+
import { MysqlConfig } from '../config'
|
|
3
|
+
import { MysqlTxSession } from './tx'
|
|
4
|
+
import { Table } from '../table-info'
|
|
5
|
+
import { MysqlException } from '../exception'
|
|
6
|
+
import {
|
|
7
|
+
DeleteManyOpts,
|
|
8
|
+
FindOpts,
|
|
9
|
+
FindSelectOpts,
|
|
10
|
+
MixCriteria,
|
|
11
|
+
MysqlPage,
|
|
12
|
+
MysqlPaginateOpts,
|
|
13
|
+
UpdateOpts,
|
|
14
|
+
Updater
|
|
15
|
+
} from './ops'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 严格 mysql 事务会话,会禁用一些操作.
|
|
19
|
+
*/
|
|
20
|
+
export class MysqlStrictTxSession extends MysqlTxSession {
|
|
21
|
+
#opsCount = 0
|
|
22
|
+
constructor(private config: MysqlConfig, conn: PoolConnection) {
|
|
23
|
+
super(config, conn)
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 为操作计数,检查是否操作次数过多.
|
|
27
|
+
*/
|
|
28
|
+
#checkAndAddOpsCount() {
|
|
29
|
+
if (this.#opsCount >= this.config.maxOpsInStrictTx) {
|
|
30
|
+
throw new MysqlException('Too many operations in a strict transaction.')
|
|
31
|
+
}
|
|
32
|
+
this.#opsCount++
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
findById<T>(table: Table<T>, id: string | number): Promise<T | null> {
|
|
36
|
+
this.#checkAndAddOpsCount()
|
|
37
|
+
return super.findById(table, id)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
findByIdIn<T>(table: Table<T>, ids: (string | number)[]): Promise<T[]> {
|
|
41
|
+
if (ids.length > 100) {
|
|
42
|
+
throw new MysqlException(
|
|
43
|
+
`The augument ids length(${ids.length}) passed to findByIdIn in a strict transaction is too large .`
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
this.#checkAndAddOpsCount()
|
|
47
|
+
return super.findByIdIn(table, ids)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
existsBy<T>(table: Table<T>, criteria?: MixCriteria<T> | undefined): Promise<boolean> {
|
|
51
|
+
this.#checkAndAddOpsCount()
|
|
52
|
+
return super.existsBy(table, criteria)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
existsById<T>(table: Table<T>, id: string | number): Promise<boolean> {
|
|
56
|
+
this.#checkAndAddOpsCount()
|
|
57
|
+
return super.existsById(table, id)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
deleteById<T>(table: Table<T>, id: string | number): Promise<boolean> {
|
|
61
|
+
this.#checkAndAddOpsCount()
|
|
62
|
+
return super.deleteById(table, id)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
deleteOne<T>(table: Table<T>, criteria: Partial<T>): Promise<boolean> {
|
|
66
|
+
this.#checkAndAddOpsCount()
|
|
67
|
+
return super.deleteOne(table, criteria)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
deleteMany<T>(opts: DeleteManyOpts<T>): Promise<number> {
|
|
71
|
+
throw new MysqlException('Prohibited to use deleteBy in a strict transaction.')
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
findAll<T>(table: Table<T>): Promise<T[]> {
|
|
75
|
+
throw new MysqlException('Prohibited to use findAll in a strict transaction.')
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
findFirst<T>(table: Table<T>, criteria?: MixCriteria<T> | undefined): Promise<T | null> {
|
|
79
|
+
this.#checkAndAddOpsCount()
|
|
80
|
+
return super.findFirst(table, criteria)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
insert<T>(table: Table<T>, data: T): Promise<T> {
|
|
84
|
+
this.#checkAndAddOpsCount()
|
|
85
|
+
return super.insert(table, data)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
insertMany<T>(table: Table<T>, list: T[]): Promise<void> {
|
|
89
|
+
throw new MysqlException('Prohibited to use insertMany in a strict transaction.')
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
update<T>(table: Table<T>, data: T): Promise<T> {
|
|
93
|
+
this.#checkAndAddOpsCount()
|
|
94
|
+
return super.update(table, data)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
updateMany<T>(opts: UpdateOpts<T>): Promise<number> {
|
|
98
|
+
throw new MysqlException('Prohibited to use updateMany in a strict transaction.')
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
updateOne<T>(
|
|
102
|
+
table: Table<T>,
|
|
103
|
+
query: Partial<T>,
|
|
104
|
+
updater: Partial<{ [key in keyof T]: ['setNull'] | ['inc', number] | T[key] | undefined }>
|
|
105
|
+
): Promise<boolean> {
|
|
106
|
+
this.#checkAndAddOpsCount()
|
|
107
|
+
return super.updateOne(table, query, updater)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
partialUpdate<T>(table: Table<T>, data: Updater<T>): Promise<boolean> {
|
|
111
|
+
this.#checkAndAddOpsCount()
|
|
112
|
+
return super.partialUpdate(table, data)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
find<T>(opts: FindOpts<T>): Promise<T[]> {
|
|
116
|
+
throw new MysqlException('Prohibited to use find in a strict transaction.')
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
findSelect<T, K extends keyof T>(opts: FindSelectOpts<T, K>): Promise<Pick<T, K>[]> {
|
|
120
|
+
throw new MysqlException('Prohibited to use findSelect in a strict transaction.')
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
count<T>(table: Table<T>, criteria?: MixCriteria<T> | undefined): Promise<number> {
|
|
124
|
+
throw new MysqlException('Prohibited to use count in a strict transaction.')
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
paginate<T>(opts: MysqlPaginateOpts<T>): Promise<MysqlPage<T>> {
|
|
128
|
+
throw new MysqlException('Prohibited to use paginate in a strict transaction.')
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
query<T>(sql: string, values?: any[] | undefined): Promise<T[]> {
|
|
132
|
+
throw new MysqlException('Prohibited to use query in a strict transaction.')
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
modify(sql: string, values?: any[] | undefined): Promise<number> {
|
|
136
|
+
throw new MysqlException('Prohibited to use modify in a strict transaction.')
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { PoolConnection } from 'mysql2'
|
|
2
|
+
import { BaseMysqlManager } from './base'
|
|
3
|
+
import { MysqlConfig } from '../config'
|
|
4
|
+
import { MysqlException } from '../exception'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* mysql 事务会话.
|
|
8
|
+
*/
|
|
9
|
+
export class MysqlTxSession extends BaseMysqlManager {
|
|
10
|
+
/**
|
|
11
|
+
* 中止标识
|
|
12
|
+
*/
|
|
13
|
+
#aborted = false
|
|
14
|
+
|
|
15
|
+
constructor(config: MysqlConfig, conn: PoolConnection) {
|
|
16
|
+
super({ config, connection: conn })
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
protected queryWithConnection<T>(queryFn: (conn: PoolConnection) => Promise<T>): Promise<T> {
|
|
20
|
+
if (this.#aborted) {
|
|
21
|
+
throw new MysqlException('Session has been aborted!')
|
|
22
|
+
}
|
|
23
|
+
return super.queryWithConnection(queryFn)
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 中止,被中止后的会话不能再进行任何操作
|
|
27
|
+
*/
|
|
28
|
+
abort() {
|
|
29
|
+
this.#aborted = true
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// 工具集,将一些驱动的方法进行 promise 封装,方便操作
|
|
2
|
+
// 程序中没有使用 mysql2/promise ,主要是在测试中发现 mysql2/promise 不是很可靠
|
|
3
|
+
// mysql2/promise 在查询方便处理的不是很好
|
|
4
|
+
// query 查询在没有记录的情况下仍然会返回列表,里面会包含列定义信息,不符合预期
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
Connection,
|
|
8
|
+
OkPacket,
|
|
9
|
+
Pool,
|
|
10
|
+
PoolConnection,
|
|
11
|
+
ProcedureCallPacket,
|
|
12
|
+
ResultSetHeader,
|
|
13
|
+
RowDataPacket,
|
|
14
|
+
format
|
|
15
|
+
} from 'mysql2'
|
|
16
|
+
import { MysqlConfig } from '../config'
|
|
17
|
+
import { getLogger } from '../../log'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 查询,适用于各种 sql 的执行
|
|
21
|
+
* @param config
|
|
22
|
+
* @param conn
|
|
23
|
+
* @param sql
|
|
24
|
+
* @param values
|
|
25
|
+
* @returns
|
|
26
|
+
*/
|
|
27
|
+
export function promiseQuery(
|
|
28
|
+
config: MysqlConfig,
|
|
29
|
+
conn: Connection,
|
|
30
|
+
sql: string,
|
|
31
|
+
values: any[] = []
|
|
32
|
+
) {
|
|
33
|
+
return new Promise<
|
|
34
|
+
| OkPacket
|
|
35
|
+
| ResultSetHeader
|
|
36
|
+
| ResultSetHeader[]
|
|
37
|
+
| RowDataPacket[]
|
|
38
|
+
| RowDataPacket[][]
|
|
39
|
+
| OkPacket[]
|
|
40
|
+
| ProcedureCallPacket
|
|
41
|
+
>((res, rej) => {
|
|
42
|
+
const start = new Date().getTime()
|
|
43
|
+
conn.query(sql, values, (err, result) => {
|
|
44
|
+
if (config.slowSqlWarn) {
|
|
45
|
+
const cost = new Date().getTime() - start
|
|
46
|
+
// 慢 sql 警告
|
|
47
|
+
if (cost > config.slowSqlMs) {
|
|
48
|
+
getLogger().warn(`[mysql slow sql] ${cost}ms ${format(sql, values)}`)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (err) {
|
|
52
|
+
rej(err)
|
|
53
|
+
} else {
|
|
54
|
+
res(result)
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 获取连接
|
|
62
|
+
* @param pool
|
|
63
|
+
* @returns
|
|
64
|
+
*/
|
|
65
|
+
export function promiseGetConnection(pool: Pool) {
|
|
66
|
+
return new Promise<PoolConnection>((res, rej) => {
|
|
67
|
+
pool.getConnection((err, conn) => {
|
|
68
|
+
if (err) {
|
|
69
|
+
rej(err)
|
|
70
|
+
} else {
|
|
71
|
+
res(conn)
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'fs'
|
|
2
|
+
import { Connection, RowDataPacket } from 'mysql2'
|
|
3
|
+
import { isAbsolute, resolve } from 'path'
|
|
4
|
+
import { getLogger } from '../log'
|
|
5
|
+
import { MysqlConfig } from './config'
|
|
6
|
+
import { MysqlException } from './exception'
|
|
7
|
+
import { promiseQuery } from './manager/utils'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 版本信息.
|
|
11
|
+
*/
|
|
12
|
+
interface MysqlVersion {
|
|
13
|
+
version: number
|
|
14
|
+
filePath: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 迁移.
|
|
19
|
+
* @param config
|
|
20
|
+
* @param conn
|
|
21
|
+
*/
|
|
22
|
+
export async function migrate(config: MysqlConfig, conn: Connection) {
|
|
23
|
+
const versionDir = config.versionControlDir
|
|
24
|
+
// 查找文件
|
|
25
|
+
const dir = isAbsolute(versionDir) ? versionDir : resolve(process.cwd(), versionDir)
|
|
26
|
+
if (!existsSync(dir)) {
|
|
27
|
+
throw new Error(`Directory ${versionDir} does not exist`)
|
|
28
|
+
}
|
|
29
|
+
const versions: MysqlVersion[] = []
|
|
30
|
+
// 忽略隐藏文件
|
|
31
|
+
const files = readdirSync(dir).filter(file => !file.startsWith('.'))
|
|
32
|
+
for (const file of files) {
|
|
33
|
+
const filePath = resolve(dir, file)
|
|
34
|
+
const stat = statSync(filePath)
|
|
35
|
+
if (!stat.isFile()) {
|
|
36
|
+
continue
|
|
37
|
+
}
|
|
38
|
+
if (!file.endsWith('.sql')) {
|
|
39
|
+
throw new Error(`版本文件名没有以 .sql 为后缀:${file}`)
|
|
40
|
+
}
|
|
41
|
+
const version = parseInt(file.substring(0, file.length - 4))
|
|
42
|
+
if (isNaN(version)) {
|
|
43
|
+
throw new Error(`Version file is not named with a number:${file}`)
|
|
44
|
+
}
|
|
45
|
+
versions.push({ version, filePath })
|
|
46
|
+
}
|
|
47
|
+
// 排序,判定顺序
|
|
48
|
+
versions.sort((o1, o2) => o1.version - o2.version)
|
|
49
|
+
for (let i = 0; i < versions.length; i++) {
|
|
50
|
+
const version = versions[i]
|
|
51
|
+
if (version.version !== i + 1) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`The SQL version number must start from 1 and increment one by one,error version:${version.version}`
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
await createVersionTableIfNotExist(config, conn)
|
|
58
|
+
|
|
59
|
+
// 在事务中执行版本管理,主要目的是为了通过锁来协调多个进程同时启动的情况
|
|
60
|
+
// 事务不能保存一个版本处理成功就完整回退,如有错误,仍然需要手动调整后再操作
|
|
61
|
+
await promiseQuery(config, conn, `SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED`)
|
|
62
|
+
await new Promise<void>((resolve, reject) => {
|
|
63
|
+
conn.beginTransaction(err => {
|
|
64
|
+
if (err) {
|
|
65
|
+
reject(err)
|
|
66
|
+
} else {
|
|
67
|
+
resolve()
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
try {
|
|
72
|
+
// 执行 sql, 判定当前版本
|
|
73
|
+
let currentVersion = await getCurrentVersion(config, conn)
|
|
74
|
+
if (typeof currentVersion !== 'number') {
|
|
75
|
+
// 插入初始版本号
|
|
76
|
+
await promiseQuery(config, conn, 'insert `db_version`(`version`) values (0)')
|
|
77
|
+
}
|
|
78
|
+
const filnalCurrentVersion = currentVersion || 0
|
|
79
|
+
const pendingVersions = versions.filter(ver => ver.version > filnalCurrentVersion)
|
|
80
|
+
if (!pendingVersions.length) {
|
|
81
|
+
getLogger().info('The SQL version is already the latest.')
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
const nextVersion = filnalCurrentVersion + 1
|
|
85
|
+
if (pendingVersions[0].version !== nextVersion) {
|
|
86
|
+
throw new MysqlException(
|
|
87
|
+
`MySQL migration error, the next version should be ${nextVersion},but current is ${pendingVersions[0].version}`
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
for (const verion of pendingVersions) {
|
|
92
|
+
getLogger().info(`Mysql migrating, version: ${verion.version}`)
|
|
93
|
+
let sql = readFileSync(verion.filePath, { encoding: 'utf-8' })
|
|
94
|
+
await promiseQuery(config, conn, sql)
|
|
95
|
+
// 版本号
|
|
96
|
+
await promiseQuery(config, conn, `UPDATE db_version SET version=${verion.version};`)
|
|
97
|
+
}
|
|
98
|
+
// 提交
|
|
99
|
+
await new Promise<void>((resolve, reject) => {
|
|
100
|
+
conn.commit(err => {
|
|
101
|
+
if (err) {
|
|
102
|
+
reject(err)
|
|
103
|
+
} else {
|
|
104
|
+
resolve()
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
getLogger().info('Mysql migration finished.')
|
|
109
|
+
} catch (e) {
|
|
110
|
+
// 异常回退,仅能回退 dml 操作,对 ddl 无效
|
|
111
|
+
await new Promise<void>((resolve, reject) => {
|
|
112
|
+
conn.rollback(err => {
|
|
113
|
+
if (err) {
|
|
114
|
+
reject(err)
|
|
115
|
+
} else {
|
|
116
|
+
resolve()
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
|
+
throw e
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function createVersionTableIfNotExist(config: MysqlConfig, conn: Connection): Promise<void> {
|
|
125
|
+
await promiseQuery(
|
|
126
|
+
config,
|
|
127
|
+
conn,
|
|
128
|
+
'CREATE TABLE IF NOT EXISTS `db_version` (' +
|
|
129
|
+
' `version` int NOT NULL,' +
|
|
130
|
+
' PRIMARY KEY (`version`)' +
|
|
131
|
+
') ENGINE = innodb DEFAULT CHARACTER SET = "utf8mb4" ' +
|
|
132
|
+
'COLLATE = "utf8mb4_unicode_ci";'
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* 获取当前版本,使用 for update 加锁,用于协调多进程并发场景
|
|
137
|
+
* @param config
|
|
138
|
+
* @param conn
|
|
139
|
+
* @returns
|
|
140
|
+
*/
|
|
141
|
+
async function getCurrentVersion(config: MysqlConfig, conn: Connection): Promise<number | null> {
|
|
142
|
+
const res = await promiseQuery(config, conn, 'select version from `db_version` for update')
|
|
143
|
+
const rows = res as RowDataPacket[]
|
|
144
|
+
if (rows.length >= 1) {
|
|
145
|
+
return rows[0].version
|
|
146
|
+
} else {
|
|
147
|
+
return null
|
|
148
|
+
}
|
|
149
|
+
}
|