befly 3.10.18 → 3.11.1
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.md +83 -307
- package/dist/befly.config.d.ts +7 -0
- package/{befly.config.ts → dist/befly.config.js} +11 -36
- package/dist/befly.js +15621 -0
- package/dist/befly.min.js +21 -0
- package/dist/checks/checkApi.d.ts +1 -0
- package/{checks/checkApi.ts → dist/checks/checkApi.js} +12 -30
- package/dist/checks/checkHook.d.ts +1 -0
- package/dist/checks/checkHook.js +86 -0
- package/dist/checks/checkMenu.d.ts +7 -0
- package/{checks/checkMenu.ts → dist/checks/checkMenu.js} +18 -53
- package/dist/checks/checkPlugin.d.ts +1 -0
- package/dist/checks/checkPlugin.js +86 -0
- package/dist/checks/checkTable.d.ts +6 -0
- package/{checks/checkTable.ts → dist/checks/checkTable.js} +17 -41
- package/dist/configs/presetFields.d.ts +4 -0
- package/{configs/presetFields.ts → dist/configs/presetFields.js} +1 -1
- package/dist/configs/presetRegexp.d.ts +145 -0
- package/{utils/regex.ts → dist/configs/presetRegexp.js} +8 -31
- package/dist/hooks/auth.d.ts +7 -0
- package/{hooks/auth.ts → dist/hooks/auth.js} +8 -10
- package/dist/hooks/cors.d.ts +11 -0
- package/{hooks/cors.ts → dist/hooks/cors.js} +5 -13
- package/dist/hooks/parser.d.ts +14 -0
- package/{hooks/parser.ts → dist/hooks/parser.js} +31 -45
- package/dist/hooks/permission.d.ts +14 -0
- package/{hooks/permission.ts → dist/hooks/permission.js} +16 -25
- package/dist/hooks/validator.d.ts +11 -0
- package/{hooks/validator.ts → dist/hooks/validator.js} +9 -14
- package/dist/index.d.ts +26 -0
- package/{main.ts → dist/index.js} +61 -100
- package/dist/lib/asyncContext.d.ts +21 -0
- package/dist/lib/asyncContext.js +27 -0
- package/dist/lib/cacheHelper.d.ts +95 -0
- package/{lib/cacheHelper.ts → dist/lib/cacheHelper.js} +45 -105
- package/dist/lib/cacheKeys.d.ts +23 -0
- package/{lib/cacheKeys.ts → dist/lib/cacheKeys.js} +5 -10
- package/dist/lib/cipher.d.ts +153 -0
- package/{lib/cipher.ts → dist/lib/cipher.js} +23 -44
- package/dist/lib/connect.d.ts +91 -0
- package/{lib/connect.ts → dist/lib/connect.js} +47 -88
- package/dist/lib/dbDialect.d.ts +87 -0
- package/{lib/dbDialect.ts → dist/lib/dbDialect.js} +32 -112
- package/dist/lib/dbHelper.d.ts +204 -0
- package/{lib/dbHelper.ts → dist/lib/dbHelper.js} +82 -241
- package/dist/lib/dbUtils.d.ts +68 -0
- package/{lib/dbUtils.ts → dist/lib/dbUtils.js} +51 -126
- package/dist/lib/jwt.d.ts +13 -0
- package/{lib/jwt.ts → dist/lib/jwt.js} +11 -32
- package/dist/lib/logger.d.ts +42 -0
- package/dist/lib/logger.js +1144 -0
- package/dist/lib/redisHelper.d.ts +185 -0
- package/{lib/redisHelper.ts → dist/lib/redisHelper.js} +97 -141
- package/dist/lib/sqlBuilder.d.ts +160 -0
- package/{lib/sqlBuilder.ts → dist/lib/sqlBuilder.js} +132 -278
- package/dist/lib/sqlCheck.d.ts +23 -0
- package/{lib/sqlCheck.ts → dist/lib/sqlCheck.js} +24 -41
- package/dist/lib/validator.d.ts +45 -0
- package/{lib/validator.ts → dist/lib/validator.js} +44 -61
- package/dist/loader/loadApis.d.ts +12 -0
- package/{loader/loadApis.ts → dist/loader/loadApis.js} +10 -20
- package/dist/loader/loadHooks.d.ts +7 -0
- package/dist/loader/loadHooks.js +35 -0
- package/dist/loader/loadPlugins.d.ts +8 -0
- package/{loader/loadPlugins.ts → dist/loader/loadPlugins.js} +14 -26
- package/dist/paths.d.ts +93 -0
- package/{paths.ts → dist/paths.js} +6 -19
- package/dist/plugins/cache.d.ts +16 -0
- package/{plugins/cache.ts → dist/plugins/cache.js} +7 -12
- package/dist/plugins/cipher.d.ts +12 -0
- package/{plugins/cipher.ts → dist/plugins/cipher.js} +4 -6
- package/dist/plugins/config.d.ts +12 -0
- package/dist/plugins/config.js +8 -0
- package/dist/plugins/db.d.ts +16 -0
- package/{plugins/db.ts → dist/plugins/db.js} +11 -17
- package/dist/plugins/jwt.d.ts +12 -0
- package/dist/plugins/jwt.js +12 -0
- package/dist/plugins/logger.d.ts +32 -0
- package/{plugins/logger.ts → dist/plugins/logger.js} +5 -8
- package/dist/plugins/redis.d.ts +16 -0
- package/{plugins/redis.ts → dist/plugins/redis.js} +9 -12
- package/dist/plugins/tool.d.ts +81 -0
- package/{plugins/tool.ts → dist/plugins/tool.js} +9 -30
- package/dist/router/api.d.ts +14 -0
- package/dist/router/api.js +107 -0
- package/dist/router/static.d.ts +9 -0
- package/{router/static.ts → dist/router/static.js} +20 -34
- package/dist/scripts/ensureDist.d.ts +1 -0
- package/dist/scripts/ensureDist.js +296 -0
- package/dist/sync/syncApi.d.ts +3 -0
- package/{sync/syncApi.ts → dist/sync/syncApi.js} +35 -55
- package/dist/sync/syncCache.d.ts +2 -0
- package/{sync/syncCache.ts → dist/sync/syncCache.js} +1 -6
- package/dist/sync/syncDev.d.ts +6 -0
- package/{sync/syncDev.ts → dist/sync/syncDev.js} +29 -62
- package/dist/sync/syncMenu.d.ts +14 -0
- package/{sync/syncMenu.ts → dist/sync/syncMenu.js} +65 -125
- package/dist/sync/syncTable.d.ts +151 -0
- package/{sync/syncTable.ts → dist/sync/syncTable.js} +172 -379
- package/{types → dist/types}/api.d.ts +12 -51
- package/dist/types/api.js +4 -0
- package/{types → dist/types}/befly.d.ts +32 -227
- package/dist/types/befly.js +4 -0
- package/{types → dist/types}/cache.d.ts +7 -15
- package/dist/types/cache.js +4 -0
- package/dist/types/cipher.d.ts +27 -0
- package/dist/types/cipher.js +7 -0
- package/{types → dist/types}/common.d.ts +8 -33
- package/dist/types/common.js +5 -0
- package/{types → dist/types}/context.d.ts +3 -5
- package/dist/types/context.js +4 -0
- package/{types → dist/types}/crypto.d.ts +0 -3
- package/dist/types/crypto.js +4 -0
- package/dist/types/database.d.ts +138 -0
- package/dist/types/database.js +4 -0
- package/dist/types/hook.d.ts +17 -0
- package/dist/types/hook.js +6 -0
- package/dist/types/jwt.d.ts +75 -0
- package/dist/types/jwt.js +4 -0
- package/dist/types/logger.d.ts +59 -0
- package/dist/types/logger.js +6 -0
- package/dist/types/plugin.d.ts +16 -0
- package/dist/types/plugin.js +6 -0
- package/dist/types/redis.d.ts +71 -0
- package/dist/types/redis.js +4 -0
- package/{types/roleApisCache.ts → dist/types/roleApisCache.d.ts} +0 -2
- package/dist/types/roleApisCache.js +8 -0
- package/dist/types/sync.d.ts +92 -0
- package/dist/types/sync.js +4 -0
- package/dist/types/table.d.ts +34 -0
- package/dist/types/table.js +4 -0
- package/dist/types/validate.d.ts +67 -0
- package/dist/types/validate.js +4 -0
- package/dist/utils/calcPerfTime.d.ts +4 -0
- package/{utils/calcPerfTime.ts → dist/utils/calcPerfTime.js} +3 -3
- package/dist/utils/convertBigIntFields.d.ts +11 -0
- package/{utils/convertBigIntFields.ts → dist/utils/convertBigIntFields.js} +5 -9
- package/dist/utils/cors.d.ts +8 -0
- package/{utils/cors.ts → dist/utils/cors.js} +1 -3
- package/dist/utils/disableMenusGlob.d.ts +13 -0
- package/{utils/disableMenusGlob.ts → dist/utils/disableMenusGlob.js} +9 -29
- package/dist/utils/fieldClear.d.ts +11 -0
- package/{utils/fieldClear.ts → dist/utils/fieldClear.js} +15 -33
- package/dist/utils/getClientIp.d.ts +6 -0
- package/{utils/getClientIp.ts → dist/utils/getClientIp.js} +1 -7
- package/dist/utils/importDefault.d.ts +1 -0
- package/dist/utils/importDefault.js +29 -0
- package/dist/utils/isDirentDirectory.d.ts +2 -0
- package/{utils/isDirentDirectory.ts → dist/utils/isDirentDirectory.js} +3 -8
- package/dist/utils/loadMenuConfigs.d.ts +29 -0
- package/{utils/loadMenuConfigs.ts → dist/utils/loadMenuConfigs.js} +66 -52
- package/dist/utils/mergeAndConcat.d.ts +7 -0
- package/dist/utils/mergeAndConcat.js +72 -0
- package/dist/utils/processAtSymbol.d.ts +4 -0
- package/{utils/processFields.ts → dist/utils/processAtSymbol.js} +5 -9
- package/dist/utils/processInfo.d.ts +24 -0
- package/{utils/process.ts → dist/utils/processInfo.js} +2 -18
- package/dist/utils/response.d.ts +20 -0
- package/{utils/response.ts → dist/utils/response.js} +28 -49
- package/dist/utils/scanAddons.d.ts +17 -0
- package/{utils/scanAddons.ts → dist/utils/scanAddons.js} +7 -41
- package/dist/utils/scanConfig.d.ts +26 -0
- package/{utils/scanConfig.ts → dist/utils/scanConfig.js} +28 -66
- package/dist/utils/scanCoreBuiltins.d.ts +3 -0
- package/dist/utils/scanCoreBuiltins.js +65 -0
- package/dist/utils/scanFiles.d.ts +30 -0
- package/{utils/scanFiles.ts → dist/utils/scanFiles.js} +44 -71
- package/dist/utils/scanSources.d.ts +10 -0
- package/dist/utils/scanSources.js +46 -0
- package/dist/utils/sortModules.d.ts +28 -0
- package/{utils/sortModules.ts → dist/utils/sortModules.js} +26 -66
- package/dist/utils/util.d.ts +84 -0
- package/dist/utils/util.js +262 -0
- package/package.json +26 -34
- package/.gitignore +0 -0
- package/bunfig.toml +0 -3
- package/checks/checkHook.ts +0 -48
- package/checks/checkPlugin.ts +0 -48
- package/configs/presetRegexp.ts +0 -225
- package/docs/README.md +0 -98
- package/docs/api/api.md +0 -1921
- package/docs/guide/examples.md +0 -926
- package/docs/guide/quickstart.md +0 -354
- package/docs/hooks/auth.md +0 -38
- package/docs/hooks/cors.md +0 -28
- package/docs/hooks/hook.md +0 -838
- package/docs/hooks/parser.md +0 -19
- package/docs/hooks/rateLimit.md +0 -47
- package/docs/infra/redis.md +0 -628
- package/docs/plugins/cipher.md +0 -61
- package/docs/plugins/database.md +0 -189
- package/docs/plugins/plugin.md +0 -986
- package/docs/reference/addon.md +0 -510
- package/docs/reference/config.md +0 -573
- package/docs/reference/logger.md +0 -495
- package/docs/reference/sync.md +0 -478
- package/docs/reference/table.md +0 -763
- package/docs/reference/validator.md +0 -620
- package/lib/asyncContext.ts +0 -43
- package/lib/logger.ts +0 -811
- package/loader/loadHooks.ts +0 -51
- package/plugins/config.ts +0 -13
- package/plugins/jwt.ts +0 -15
- package/router/api.ts +0 -130
- package/tsconfig.json +0 -8
- package/types/database.d.ts +0 -541
- package/types/hook.d.ts +0 -25
- package/types/jwt.d.ts +0 -118
- package/types/logger.d.ts +0 -65
- package/types/plugin.d.ts +0 -19
- package/types/redis.d.ts +0 -83
- package/types/sync.d.ts +0 -398
- package/types/table.d.ts +0 -216
- package/types/validate.d.ts +0 -69
- package/utils/arrayKeysToCamel.ts +0 -18
- package/utils/configTypes.ts +0 -3
- package/utils/genShortId.ts +0 -12
- package/utils/importDefault.ts +0 -21
- package/utils/keysToCamel.ts +0 -22
- package/utils/keysToSnake.ts +0 -22
- package/utils/pickFields.ts +0 -19
- package/utils/scanSources.ts +0 -64
- package/utils/sqlLog.ts +0 -37
|
@@ -2,115 +2,93 @@
|
|
|
2
2
|
* SQL 构造器 - TypeScript 版本
|
|
3
3
|
* 提供链式 API 构建 SQL 查询
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
import type { WhereConditions, WhereOperator, OrderDirection, SqlQuery, InsertData, UpdateData, SqlValue } from "../types/common.ts";
|
|
7
|
-
|
|
8
|
-
import { SqlCheck } from "./sqlCheck.ts";
|
|
9
|
-
|
|
5
|
+
import { SqlCheck } from "./sqlCheck";
|
|
10
6
|
const SqlBuilderError = {
|
|
11
|
-
QUOTE_IDENT_NEED_STRING: (identifier
|
|
7
|
+
QUOTE_IDENT_NEED_STRING: (identifier) => `quoteIdent 需要字符串类型标识符 (identifier: ${String(identifier)})`,
|
|
12
8
|
IDENT_EMPTY: "SQL 标识符不能为空",
|
|
13
|
-
|
|
14
|
-
FIELD_EXPR_NOT_ALLOWED: (field: string) => `字段包含函数/表达式,请使用 selectRaw/whereRaw (field: ${field})`,
|
|
15
|
-
|
|
9
|
+
FIELD_EXPR_NOT_ALLOWED: (field) => `字段包含函数/表达式,请使用 selectRaw/whereRaw (field: ${field})`,
|
|
16
10
|
FROM_EMPTY: "FROM 表名不能为空",
|
|
17
|
-
FROM_NEED_NON_EMPTY: (table
|
|
11
|
+
FROM_NEED_NON_EMPTY: (table) => `FROM 表名必须是非空字符串 (table: ${String(table)})`,
|
|
18
12
|
FROM_REQUIRED: "FROM 表名是必需的",
|
|
19
13
|
COUNT_NEED_FROM: "COUNT 需要 FROM 表名",
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
TABLE_QUOTE_NOT_PAIRED: (tableName: string) => `table 标识符引用不完整,请使用成对的 \`...\` 或 "..." (table: ${tableName})`,
|
|
25
|
-
|
|
14
|
+
TABLE_REF_TOO_MANY_PARTS: (table) => `不支持的表引用格式(包含过多片段)。请使用 fromRaw 显式传入复杂表达式 (table: ${table})`,
|
|
15
|
+
TABLE_REF_SCHEMA_TOO_DEEP: (table) => `不支持的表引用格式(schema 层级过深)。请使用 fromRaw (table: ${table})`,
|
|
16
|
+
SCHEMA_QUOTE_NOT_PAIRED: (schema) => `schema 标识符引用不完整,请使用成对的 \`...\` 或 "..." (schema: ${schema})`,
|
|
17
|
+
TABLE_QUOTE_NOT_PAIRED: (tableName) => `table 标识符引用不完整,请使用成对的 \`...\` 或 "..." (table: ${tableName})`,
|
|
26
18
|
SELECT_FIELDS_INVALID: "SELECT 字段必须是字符串或数组",
|
|
27
|
-
SELECT_RAW_NEED_NON_EMPTY: (expr
|
|
28
|
-
FROM_RAW_NEED_NON_EMPTY: (tableExpr
|
|
29
|
-
WHERE_RAW_NEED_NON_EMPTY: (sql
|
|
19
|
+
SELECT_RAW_NEED_NON_EMPTY: (expr) => `selectRaw 需要非空字符串 (expr: ${String(expr)})`,
|
|
20
|
+
FROM_RAW_NEED_NON_EMPTY: (tableExpr) => `fromRaw 需要非空字符串 (tableExpr: ${String(tableExpr)})`,
|
|
21
|
+
WHERE_RAW_NEED_NON_EMPTY: (sql) => `whereRaw 需要非空字符串 (sql: ${String(sql)})`,
|
|
30
22
|
WHERE_VALUE_REQUIRED: "where(field, value) 不允许省略 value。若需传入原始 WHERE,请使用 whereRaw",
|
|
31
|
-
|
|
32
|
-
JOIN_NEED_STRING: (table: unknown, on: unknown) => `JOIN 表名和条件必须是字符串 (table: ${String(table)}, on: ${String(on)})`,
|
|
33
|
-
|
|
23
|
+
JOIN_NEED_STRING: (table, on) => `JOIN 表名和条件必须是字符串 (table: ${String(table)}, on: ${String(on)})`,
|
|
34
24
|
ORDER_BY_NEED_ARRAY: 'orderBy 必须是字符串数组,格式为 "字段#方向"',
|
|
35
|
-
ORDER_BY_ITEM_NEED_HASH: (item
|
|
36
|
-
ORDER_BY_FIELD_EMPTY: (item
|
|
37
|
-
ORDER_BY_DIR_INVALID: (direction
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
INSERT_NEED_DATA: (table: unknown, data: unknown) => `INSERT 需要数据 (table: ${String(table)}, data: ${JSON.stringify(data)})`,
|
|
45
|
-
INSERT_NEED_AT_LEAST_ONE_FIELD: (table: string) => `插入数据必须至少有一个字段 (table: ${table})`,
|
|
46
|
-
|
|
25
|
+
ORDER_BY_ITEM_NEED_HASH: (item) => `orderBy 字段必须是 "字段#方向" 格式的字符串(例如:"name#ASC", "id#DESC") (item: ${String(item)})`,
|
|
26
|
+
ORDER_BY_FIELD_EMPTY: (item) => `orderBy 中字段名不能为空 (item: ${item})`,
|
|
27
|
+
ORDER_BY_DIR_INVALID: (direction) => `ORDER BY 方向必须是 ASC 或 DESC (direction: ${direction})`,
|
|
28
|
+
LIMIT_MUST_NON_NEGATIVE: (count) => `LIMIT 数量必须是非负数 (count: ${String(count)})`,
|
|
29
|
+
OFFSET_MUST_NON_NEGATIVE: (offset) => `OFFSET 必须是非负数 (offset: ${String(offset)})`,
|
|
30
|
+
OFFSET_COUNT_MUST_NON_NEGATIVE: (count) => `OFFSET 必须是非负数 (count: ${String(count)})`,
|
|
31
|
+
INSERT_NEED_TABLE: (table) => `INSERT 需要表名 (table: ${String(table)})`,
|
|
32
|
+
INSERT_NEED_DATA: (table, data) => `INSERT 需要数据 (table: ${String(table)}, data: ${JSON.stringify(data)})`,
|
|
33
|
+
INSERT_NEED_AT_LEAST_ONE_FIELD: (table) => `插入数据必须至少有一个字段 (table: ${table})`,
|
|
47
34
|
UPDATE_NEED_TABLE: "UPDATE 需要表名",
|
|
48
35
|
UPDATE_NEED_OBJECT: "UPDATE 需要数据对象",
|
|
49
36
|
UPDATE_NEED_AT_LEAST_ONE_FIELD: "更新数据必须至少有一个字段",
|
|
50
37
|
UPDATE_NEED_WHERE: "为安全起见,UPDATE 需要 WHERE 条件",
|
|
51
|
-
|
|
52
38
|
DELETE_NEED_TABLE: "DELETE 需要表名",
|
|
53
39
|
DELETE_NEED_WHERE: "为安全起见,DELETE 需要 WHERE 条件",
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
TO_DELETE_IN_NEED_ID_FIELD: (idField: unknown) => `toDeleteInSql 需要非空 idField (idField: ${String(idField)})`,
|
|
40
|
+
TO_DELETE_IN_NEED_TABLE: (table) => `toDeleteInSql 需要非空表名 (table: ${String(table)})`,
|
|
41
|
+
TO_DELETE_IN_NEED_ID_FIELD: (idField) => `toDeleteInSql 需要非空 idField (idField: ${String(idField)})`,
|
|
57
42
|
TO_DELETE_IN_NEED_IDS: "toDeleteInSql 需要 ids 数组",
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
TO_UPDATE_CASE_NEED_ID_FIELD: (idField: unknown) => `toUpdateCaseByIdSql 需要非空 idField (idField: ${String(idField)})`,
|
|
43
|
+
TO_UPDATE_CASE_NEED_TABLE: (table) => `toUpdateCaseByIdSql 需要非空表名 (table: ${String(table)})`,
|
|
44
|
+
TO_UPDATE_CASE_NEED_ID_FIELD: (idField) => `toUpdateCaseByIdSql 需要非空 idField (idField: ${String(idField)})`,
|
|
61
45
|
TO_UPDATE_CASE_NEED_ROWS: "toUpdateCaseByIdSql 需要 rows 数组",
|
|
62
46
|
TO_UPDATE_CASE_NEED_FIELDS: "toUpdateCaseByIdSql 需要 fields 数组"
|
|
63
|
-
}
|
|
64
|
-
|
|
47
|
+
};
|
|
65
48
|
/**
|
|
66
49
|
* SQL 构建器类
|
|
67
50
|
*/
|
|
68
51
|
export class SqlBuilder {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
constructor(options?: { quoteIdent?: (identifier: string) => string }) {
|
|
52
|
+
_select = [];
|
|
53
|
+
_from = "";
|
|
54
|
+
_where = [];
|
|
55
|
+
_joins = [];
|
|
56
|
+
_orderBy = [];
|
|
57
|
+
_groupBy = [];
|
|
58
|
+
_having = [];
|
|
59
|
+
_limit = null;
|
|
60
|
+
_offset = null;
|
|
61
|
+
_params = [];
|
|
62
|
+
_quoteIdent;
|
|
63
|
+
constructor(options) {
|
|
82
64
|
if (options && options.quoteIdent) {
|
|
83
65
|
this._quoteIdent = options.quoteIdent;
|
|
84
|
-
}
|
|
85
|
-
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
this._quoteIdent = (identifier) => {
|
|
86
69
|
if (typeof identifier !== "string") {
|
|
87
70
|
throw new Error(SqlBuilderError.QUOTE_IDENT_NEED_STRING(identifier));
|
|
88
71
|
}
|
|
89
|
-
|
|
90
72
|
const trimmed = identifier.trim();
|
|
91
73
|
if (!trimmed) {
|
|
92
74
|
throw new Error(SqlBuilderError.IDENT_EMPTY);
|
|
93
75
|
}
|
|
94
|
-
|
|
95
76
|
// 默认行为(MySQL 风格):允许特殊字符,但对反引号进行转义
|
|
96
77
|
const escaped = trimmed.replace(/`/g, "``");
|
|
97
78
|
return `\`${escaped}\``;
|
|
98
79
|
};
|
|
99
80
|
}
|
|
100
81
|
}
|
|
101
|
-
|
|
102
|
-
private _isQuotedIdent(value: string): boolean {
|
|
82
|
+
_isQuotedIdent(value) {
|
|
103
83
|
return SqlCheck.isQuotedIdentPaired(value);
|
|
104
84
|
}
|
|
105
|
-
|
|
106
|
-
private _startsWithQuote(value: string): boolean {
|
|
85
|
+
_startsWithQuote(value) {
|
|
107
86
|
return SqlCheck.startsWithQuote(value);
|
|
108
87
|
}
|
|
109
|
-
|
|
110
88
|
/**
|
|
111
89
|
* 重置构建器状态
|
|
112
90
|
*/
|
|
113
|
-
reset()
|
|
91
|
+
reset() {
|
|
114
92
|
this._select = [];
|
|
115
93
|
this._from = "";
|
|
116
94
|
this._where = [];
|
|
@@ -123,32 +101,27 @@ export class SqlBuilder {
|
|
|
123
101
|
this._params = [];
|
|
124
102
|
return this;
|
|
125
103
|
}
|
|
126
|
-
|
|
127
104
|
/**
|
|
128
105
|
* 转义字段名
|
|
129
106
|
*/
|
|
130
|
-
|
|
107
|
+
_escapeField(field) {
|
|
131
108
|
if (typeof field !== "string") {
|
|
132
109
|
return field;
|
|
133
110
|
}
|
|
134
|
-
|
|
135
111
|
field = field.trim();
|
|
136
|
-
|
|
137
112
|
// 防止不完整引用被误认为“已安全引用”
|
|
138
113
|
SqlCheck.assertPairedQuotedIdentIfStartsWithQuote(field, "字段标识符");
|
|
139
|
-
|
|
140
114
|
// 如果是 * 或已经被引用,直接返回
|
|
141
115
|
if (field === "*" || this._isQuotedIdent(field)) {
|
|
142
116
|
return field;
|
|
143
117
|
}
|
|
144
|
-
|
|
145
118
|
try {
|
|
146
119
|
SqlCheck.assertNoExprField(field);
|
|
147
|
-
}
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
148
122
|
// 保持 SqlBuilder 报错文案统一
|
|
149
123
|
throw new Error(SqlBuilderError.FIELD_EXPR_NOT_ALLOWED(field));
|
|
150
124
|
}
|
|
151
|
-
|
|
152
125
|
// 处理别名(AS关键字)
|
|
153
126
|
if (field.toUpperCase().includes(" AS ")) {
|
|
154
127
|
const parts = field.split(/\s+AS\s+/i);
|
|
@@ -158,99 +131,85 @@ export class SqlBuilder {
|
|
|
158
131
|
SqlCheck.assertSafeAlias(aliasPart);
|
|
159
132
|
return `${this._escapeField(fieldPart)} AS ${aliasPart}`;
|
|
160
133
|
}
|
|
161
|
-
|
|
162
134
|
// 处理表名.字段名的情况(多表联查)
|
|
163
135
|
if (field.includes(".")) {
|
|
164
136
|
const parts = field.split(".");
|
|
165
137
|
return parts
|
|
166
138
|
.map((part) => {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
139
|
+
part = part.trim();
|
|
140
|
+
if (part === "*" || this._isQuotedIdent(part)) {
|
|
141
|
+
return part;
|
|
142
|
+
}
|
|
143
|
+
SqlCheck.assertPairedQuotedIdentIfStartsWithQuote(part, "字段标识符");
|
|
144
|
+
return this._quoteIdent(part);
|
|
145
|
+
})
|
|
174
146
|
.join(".");
|
|
175
147
|
}
|
|
176
|
-
|
|
177
148
|
// 处理单个字段名
|
|
178
149
|
return this._quoteIdent(field);
|
|
179
150
|
}
|
|
180
|
-
|
|
181
|
-
private _validateIdentifierPart(part: string, kind: "table" | "schema" | "alias" | "field"): void {
|
|
151
|
+
_validateIdentifierPart(part, kind) {
|
|
182
152
|
SqlCheck.assertSafeIdentifierPart(part, kind);
|
|
183
153
|
}
|
|
184
|
-
|
|
185
154
|
/**
|
|
186
155
|
* 转义表名
|
|
187
156
|
*/
|
|
188
|
-
|
|
157
|
+
_escapeTable(table) {
|
|
189
158
|
if (typeof table !== "string") {
|
|
190
159
|
return table;
|
|
191
160
|
}
|
|
192
|
-
|
|
193
161
|
table = table.trim();
|
|
194
|
-
|
|
195
162
|
// 防止不完整引用被误认为“已安全引用”
|
|
196
163
|
if (this._startsWithQuote(table) && !this._isQuotedIdent(table)) {
|
|
197
164
|
// 注意:这里可能是 `table` alias 的形式,整体不成对,但 namePart 可能成对。
|
|
198
165
|
// 因此这里只做“整体是单段引用”的判断,具体在后续 namePart 分支里校验。
|
|
199
166
|
}
|
|
200
|
-
|
|
201
167
|
if (this._isQuotedIdent(table)) {
|
|
202
168
|
return table;
|
|
203
169
|
}
|
|
204
|
-
|
|
205
170
|
const parts = table.split(/\s+/).filter((p) => p.length > 0);
|
|
206
171
|
if (parts.length === 0) {
|
|
207
172
|
throw new Error(SqlBuilderError.FROM_EMPTY);
|
|
208
173
|
}
|
|
209
|
-
|
|
210
174
|
if (parts.length > 2) {
|
|
211
175
|
throw new Error(SqlBuilderError.TABLE_REF_TOO_MANY_PARTS(table));
|
|
212
176
|
}
|
|
213
|
-
|
|
214
177
|
const namePart = parts[0].trim();
|
|
215
178
|
const aliasPart = parts.length === 2 ? parts[1].trim() : null;
|
|
216
|
-
|
|
217
179
|
const nameSegments = namePart.split(".");
|
|
218
180
|
if (nameSegments.length > 2) {
|
|
219
181
|
throw new Error(SqlBuilderError.TABLE_REF_SCHEMA_TOO_DEEP(table));
|
|
220
182
|
}
|
|
221
|
-
|
|
222
183
|
let escapedName = "";
|
|
223
184
|
if (nameSegments.length === 2) {
|
|
224
185
|
const schema = nameSegments[0].trim();
|
|
225
186
|
const tableName = nameSegments[1].trim();
|
|
226
|
-
|
|
227
187
|
const escapedSchema = this._isQuotedIdent(schema)
|
|
228
188
|
? schema
|
|
229
189
|
: (() => {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
190
|
+
if (this._startsWithQuote(schema) && !this._isQuotedIdent(schema)) {
|
|
191
|
+
throw new Error(SqlBuilderError.SCHEMA_QUOTE_NOT_PAIRED(schema));
|
|
192
|
+
}
|
|
193
|
+
this._validateIdentifierPart(schema, "schema");
|
|
194
|
+
return this._quoteIdent(schema);
|
|
195
|
+
})();
|
|
237
196
|
const escapedTableName = this._isQuotedIdent(tableName)
|
|
238
197
|
? tableName
|
|
239
198
|
: (() => {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
199
|
+
if (this._startsWithQuote(tableName) && !this._isQuotedIdent(tableName)) {
|
|
200
|
+
throw new Error(SqlBuilderError.TABLE_QUOTE_NOT_PAIRED(tableName));
|
|
201
|
+
}
|
|
202
|
+
this._validateIdentifierPart(tableName, "table");
|
|
203
|
+
return this._quoteIdent(tableName);
|
|
204
|
+
})();
|
|
247
205
|
escapedName = `${escapedSchema}.${escapedTableName}`;
|
|
248
|
-
}
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
249
208
|
const tableName = nameSegments[0].trim();
|
|
250
|
-
|
|
251
209
|
if (this._isQuotedIdent(tableName)) {
|
|
252
210
|
escapedName = tableName;
|
|
253
|
-
}
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
254
213
|
if (this._startsWithQuote(tableName) && !this._isQuotedIdent(tableName)) {
|
|
255
214
|
throw new Error(SqlBuilderError.TABLE_QUOTE_NOT_PAIRED(tableName));
|
|
256
215
|
}
|
|
@@ -258,28 +217,23 @@ export class SqlBuilder {
|
|
|
258
217
|
escapedName = this._quoteIdent(tableName);
|
|
259
218
|
}
|
|
260
219
|
}
|
|
261
|
-
|
|
262
220
|
if (aliasPart) {
|
|
263
221
|
this._validateIdentifierPart(aliasPart, "alias");
|
|
264
222
|
return `${escapedName} ${aliasPart}`;
|
|
265
223
|
}
|
|
266
|
-
|
|
267
224
|
return escapedName;
|
|
268
225
|
}
|
|
269
|
-
|
|
270
226
|
/**
|
|
271
227
|
* 验证参数
|
|
272
228
|
*/
|
|
273
|
-
|
|
229
|
+
_validateParam(value) {
|
|
274
230
|
SqlCheck.assertNoUndefinedParam(value, "SQL 参数值");
|
|
275
231
|
}
|
|
276
|
-
|
|
277
232
|
/**
|
|
278
233
|
* 处理单个操作符条件
|
|
279
234
|
*/
|
|
280
|
-
|
|
235
|
+
_applyOperator(fieldName, operator, value) {
|
|
281
236
|
const escapedField = this._escapeField(fieldName);
|
|
282
|
-
|
|
283
237
|
switch (operator) {
|
|
284
238
|
case "$ne":
|
|
285
239
|
case "$not":
|
|
@@ -287,7 +241,6 @@ export class SqlBuilder {
|
|
|
287
241
|
this._where.push(`${escapedField} != ?`);
|
|
288
242
|
this._params.push(value);
|
|
289
243
|
break;
|
|
290
|
-
|
|
291
244
|
case "$in":
|
|
292
245
|
if (!Array.isArray(value)) {
|
|
293
246
|
throw new Error(`$in 操作符的值必须是数组 (operator: ${operator})`);
|
|
@@ -299,7 +252,6 @@ export class SqlBuilder {
|
|
|
299
252
|
this._where.push(`${escapedField} IN (${placeholders})`);
|
|
300
253
|
this._params.push(...value);
|
|
301
254
|
break;
|
|
302
|
-
|
|
303
255
|
case "$nin":
|
|
304
256
|
case "$notIn":
|
|
305
257
|
if (!Array.isArray(value)) {
|
|
@@ -312,43 +264,36 @@ export class SqlBuilder {
|
|
|
312
264
|
this._where.push(`${escapedField} NOT IN (${placeholders2})`);
|
|
313
265
|
this._params.push(...value);
|
|
314
266
|
break;
|
|
315
|
-
|
|
316
267
|
case "$like":
|
|
317
268
|
this._validateParam(value);
|
|
318
269
|
this._where.push(`${escapedField} LIKE ?`);
|
|
319
270
|
this._params.push(value);
|
|
320
271
|
break;
|
|
321
|
-
|
|
322
272
|
case "$notLike":
|
|
323
273
|
this._validateParam(value);
|
|
324
274
|
this._where.push(`${escapedField} NOT LIKE ?`);
|
|
325
275
|
this._params.push(value);
|
|
326
276
|
break;
|
|
327
|
-
|
|
328
277
|
case "$gt":
|
|
329
278
|
this._validateParam(value);
|
|
330
279
|
this._where.push(`${escapedField} > ?`);
|
|
331
280
|
this._params.push(value);
|
|
332
281
|
break;
|
|
333
|
-
|
|
334
282
|
case "$gte":
|
|
335
283
|
this._validateParam(value);
|
|
336
284
|
this._where.push(`${escapedField} >= ?`);
|
|
337
285
|
this._params.push(value);
|
|
338
286
|
break;
|
|
339
|
-
|
|
340
287
|
case "$lt":
|
|
341
288
|
this._validateParam(value);
|
|
342
289
|
this._where.push(`${escapedField} < ?`);
|
|
343
290
|
this._params.push(value);
|
|
344
291
|
break;
|
|
345
|
-
|
|
346
292
|
case "$lte":
|
|
347
293
|
this._validateParam(value);
|
|
348
294
|
this._where.push(`${escapedField} <= ?`);
|
|
349
295
|
this._params.push(value);
|
|
350
296
|
break;
|
|
351
|
-
|
|
352
297
|
case "$between":
|
|
353
298
|
if (Array.isArray(value) && value.length === 2) {
|
|
354
299
|
this._validateParam(value[0]);
|
|
@@ -357,7 +302,6 @@ export class SqlBuilder {
|
|
|
357
302
|
this._params.push(value[0], value[1]);
|
|
358
303
|
}
|
|
359
304
|
break;
|
|
360
|
-
|
|
361
305
|
case "$notBetween":
|
|
362
306
|
if (Array.isArray(value) && value.length === 2) {
|
|
363
307
|
this._validateParam(value[0]);
|
|
@@ -366,19 +310,16 @@ export class SqlBuilder {
|
|
|
366
310
|
this._params.push(value[0], value[1]);
|
|
367
311
|
}
|
|
368
312
|
break;
|
|
369
|
-
|
|
370
313
|
case "$null":
|
|
371
314
|
if (value === true) {
|
|
372
315
|
this._where.push(`${escapedField} IS NULL`);
|
|
373
316
|
}
|
|
374
317
|
break;
|
|
375
|
-
|
|
376
318
|
case "$notNull":
|
|
377
319
|
if (value === true) {
|
|
378
320
|
this._where.push(`${escapedField} IS NOT NULL`);
|
|
379
321
|
}
|
|
380
322
|
break;
|
|
381
|
-
|
|
382
323
|
default:
|
|
383
324
|
// 等于条件
|
|
384
325
|
this._validateParam(value);
|
|
@@ -386,30 +327,27 @@ export class SqlBuilder {
|
|
|
386
327
|
this._params.push(value);
|
|
387
328
|
}
|
|
388
329
|
}
|
|
389
|
-
|
|
390
330
|
/**
|
|
391
331
|
* 处理复杂的 WHERE 条件对象
|
|
392
332
|
*/
|
|
393
|
-
|
|
333
|
+
_processWhereConditions(whereObj) {
|
|
394
334
|
if (!whereObj || typeof whereObj !== "object") {
|
|
395
335
|
return;
|
|
396
336
|
}
|
|
397
|
-
|
|
398
337
|
Object.entries(whereObj).forEach(([key, value]) => {
|
|
399
338
|
// 跳过undefined值
|
|
400
339
|
if (value === undefined) {
|
|
401
340
|
return;
|
|
402
341
|
}
|
|
403
|
-
|
|
404
342
|
if (key === "$and") {
|
|
405
343
|
if (Array.isArray(value)) {
|
|
406
344
|
value.forEach((condition) => this._processWhereConditions(condition));
|
|
407
345
|
}
|
|
408
|
-
}
|
|
346
|
+
}
|
|
347
|
+
else if (key === "$or") {
|
|
409
348
|
if (Array.isArray(value)) {
|
|
410
|
-
const orConditions
|
|
411
|
-
const tempParams
|
|
412
|
-
|
|
349
|
+
const orConditions = [];
|
|
350
|
+
const tempParams = [];
|
|
413
351
|
value.forEach((condition) => {
|
|
414
352
|
const tempBuilder = new SqlBuilder({ quoteIdent: this._quoteIdent });
|
|
415
353
|
tempBuilder._processWhereConditions(condition);
|
|
@@ -418,26 +356,28 @@ export class SqlBuilder {
|
|
|
418
356
|
tempParams.push(...tempBuilder._params);
|
|
419
357
|
}
|
|
420
358
|
});
|
|
421
|
-
|
|
422
359
|
if (orConditions.length > 0) {
|
|
423
360
|
this._where.push(`(${orConditions.join(" OR ")})`);
|
|
424
361
|
this._params.push(...tempParams);
|
|
425
362
|
}
|
|
426
363
|
}
|
|
427
|
-
}
|
|
364
|
+
}
|
|
365
|
+
else if (key.includes("$")) {
|
|
428
366
|
// 一级属性格式:age$gt, role$in 等
|
|
429
367
|
const lastDollarIndex = key.lastIndexOf("$");
|
|
430
368
|
const fieldName = key.substring(0, lastDollarIndex);
|
|
431
|
-
const operator = ("$" + key.substring(lastDollarIndex + 1))
|
|
369
|
+
const operator = ("$" + key.substring(lastDollarIndex + 1));
|
|
432
370
|
this._applyOperator(fieldName, operator, value);
|
|
433
|
-
}
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
434
373
|
// 检查值是否为对象(嵌套条件)
|
|
435
374
|
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
436
375
|
// 嵌套条件:如 { age: { $gt: 18 } }
|
|
437
376
|
for (const [op, val] of Object.entries(value)) {
|
|
438
|
-
this._applyOperator(key, op
|
|
377
|
+
this._applyOperator(key, op, val);
|
|
439
378
|
}
|
|
440
|
-
}
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
441
381
|
// 简单的等于条件
|
|
442
382
|
this._validateParam(value);
|
|
443
383
|
const escapedKey = this._escapeField(key);
|
|
@@ -447,75 +387,65 @@ export class SqlBuilder {
|
|
|
447
387
|
}
|
|
448
388
|
});
|
|
449
389
|
}
|
|
450
|
-
|
|
451
390
|
/**
|
|
452
391
|
* 获取 WHERE 条件(供 DbHelper 使用)
|
|
453
392
|
*/
|
|
454
|
-
getWhereConditions()
|
|
393
|
+
getWhereConditions() {
|
|
455
394
|
return {
|
|
456
395
|
sql: this._where.length > 0 ? this._where.join(" AND ") : "",
|
|
457
396
|
params: [...this._params]
|
|
458
397
|
};
|
|
459
398
|
}
|
|
460
|
-
|
|
461
399
|
/**
|
|
462
400
|
* SELECT 字段
|
|
463
401
|
*/
|
|
464
|
-
select(fields
|
|
402
|
+
select(fields = "*") {
|
|
465
403
|
if (Array.isArray(fields)) {
|
|
466
404
|
this._select = [...this._select, ...fields.map((field) => this._escapeField(field))];
|
|
467
|
-
}
|
|
405
|
+
}
|
|
406
|
+
else if (typeof fields === "string") {
|
|
468
407
|
this._select.push(this._escapeField(fields));
|
|
469
|
-
}
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
470
410
|
throw new Error(SqlBuilderError.SELECT_FIELDS_INVALID);
|
|
471
411
|
}
|
|
472
412
|
return this;
|
|
473
413
|
}
|
|
474
|
-
|
|
475
414
|
/**
|
|
476
415
|
* SELECT 原始表达式(不做转义)
|
|
477
416
|
*/
|
|
478
|
-
selectRaw(expr
|
|
417
|
+
selectRaw(expr) {
|
|
479
418
|
if (typeof expr !== "string" || !expr.trim()) {
|
|
480
419
|
throw new Error(SqlBuilderError.SELECT_RAW_NEED_NON_EMPTY(expr));
|
|
481
420
|
}
|
|
482
421
|
this._select.push(expr);
|
|
483
422
|
return this;
|
|
484
423
|
}
|
|
485
|
-
|
|
486
424
|
/**
|
|
487
425
|
* FROM 表名
|
|
488
426
|
*/
|
|
489
|
-
from(table
|
|
427
|
+
from(table) {
|
|
490
428
|
if (typeof table !== "string" || !table.trim()) {
|
|
491
429
|
throw new Error(SqlBuilderError.FROM_NEED_NON_EMPTY(table));
|
|
492
430
|
}
|
|
493
431
|
this._from = this._escapeTable(table.trim());
|
|
494
432
|
return this;
|
|
495
433
|
}
|
|
496
|
-
|
|
497
434
|
/**
|
|
498
435
|
* FROM 原始表达式(不做转义)
|
|
499
436
|
*/
|
|
500
|
-
fromRaw(tableExpr
|
|
437
|
+
fromRaw(tableExpr) {
|
|
501
438
|
if (typeof tableExpr !== "string" || !tableExpr.trim()) {
|
|
502
439
|
throw new Error(SqlBuilderError.FROM_RAW_NEED_NON_EMPTY(tableExpr));
|
|
503
440
|
}
|
|
504
441
|
this._from = tableExpr;
|
|
505
442
|
return this;
|
|
506
443
|
}
|
|
507
|
-
|
|
508
|
-
/**
|
|
509
|
-
* WHERE 条件
|
|
510
|
-
*/
|
|
511
|
-
where(condition: WhereConditions): this;
|
|
512
|
-
where(field: string, value: SqlValue): this;
|
|
513
|
-
where(conditionOrField: WhereConditions | string, value?: SqlValue): this {
|
|
444
|
+
where(conditionOrField, value) {
|
|
514
445
|
if (typeof conditionOrField === "object" && conditionOrField !== null) {
|
|
515
446
|
this._processWhereConditions(conditionOrField);
|
|
516
447
|
return this;
|
|
517
448
|
}
|
|
518
|
-
|
|
519
449
|
if (typeof conditionOrField === "string") {
|
|
520
450
|
if (value === undefined) {
|
|
521
451
|
throw new Error(SqlBuilderError.WHERE_VALUE_REQUIRED);
|
|
@@ -526,30 +456,25 @@ export class SqlBuilder {
|
|
|
526
456
|
this._params.push(value);
|
|
527
457
|
return this;
|
|
528
458
|
}
|
|
529
|
-
|
|
530
459
|
return this;
|
|
531
460
|
}
|
|
532
|
-
|
|
533
461
|
/**
|
|
534
462
|
* WHERE 原始片段(不做转义),可附带参数。
|
|
535
463
|
*/
|
|
536
|
-
whereRaw(sql
|
|
464
|
+
whereRaw(sql, params) {
|
|
537
465
|
if (typeof sql !== "string" || !sql.trim()) {
|
|
538
466
|
throw new Error(SqlBuilderError.WHERE_RAW_NEED_NON_EMPTY(sql));
|
|
539
467
|
}
|
|
540
|
-
|
|
541
468
|
this._where.push(sql);
|
|
542
469
|
if (params && params.length > 0) {
|
|
543
470
|
this._params.push(...params);
|
|
544
471
|
}
|
|
545
|
-
|
|
546
472
|
return this;
|
|
547
473
|
}
|
|
548
|
-
|
|
549
474
|
/**
|
|
550
475
|
* LEFT JOIN
|
|
551
476
|
*/
|
|
552
|
-
leftJoin(table
|
|
477
|
+
leftJoin(table, on) {
|
|
553
478
|
if (typeof table !== "string" || typeof on !== "string") {
|
|
554
479
|
throw new Error(SqlBuilderError.JOIN_NEED_STRING(table, on));
|
|
555
480
|
}
|
|
@@ -557,11 +482,10 @@ export class SqlBuilder {
|
|
|
557
482
|
this._joins.push(`LEFT JOIN ${escapedTable} ON ${on}`);
|
|
558
483
|
return this;
|
|
559
484
|
}
|
|
560
|
-
|
|
561
485
|
/**
|
|
562
486
|
* RIGHT JOIN
|
|
563
487
|
*/
|
|
564
|
-
rightJoin(table
|
|
488
|
+
rightJoin(table, on) {
|
|
565
489
|
if (typeof table !== "string" || typeof on !== "string") {
|
|
566
490
|
throw new Error(SqlBuilderError.JOIN_NEED_STRING(table, on));
|
|
567
491
|
}
|
|
@@ -569,11 +493,10 @@ export class SqlBuilder {
|
|
|
569
493
|
this._joins.push(`RIGHT JOIN ${escapedTable} ON ${on}`);
|
|
570
494
|
return this;
|
|
571
495
|
}
|
|
572
|
-
|
|
573
496
|
/**
|
|
574
497
|
* INNER JOIN
|
|
575
498
|
*/
|
|
576
|
-
innerJoin(table
|
|
499
|
+
innerJoin(table, on) {
|
|
577
500
|
if (typeof table !== "string" || typeof on !== "string") {
|
|
578
501
|
throw new Error(SqlBuilderError.JOIN_NEED_STRING(table, on));
|
|
579
502
|
}
|
|
@@ -581,67 +504,58 @@ export class SqlBuilder {
|
|
|
581
504
|
this._joins.push(`INNER JOIN ${escapedTable} ON ${on}`);
|
|
582
505
|
return this;
|
|
583
506
|
}
|
|
584
|
-
|
|
585
507
|
/**
|
|
586
508
|
* ORDER BY
|
|
587
509
|
* @param fields - 格式为 ["field#ASC", "field2#DESC"]
|
|
588
510
|
*/
|
|
589
|
-
orderBy(fields
|
|
511
|
+
orderBy(fields) {
|
|
590
512
|
if (!Array.isArray(fields)) {
|
|
591
513
|
throw new Error(SqlBuilderError.ORDER_BY_NEED_ARRAY);
|
|
592
514
|
}
|
|
593
|
-
|
|
594
515
|
fields.forEach((item) => {
|
|
595
516
|
if (typeof item !== "string" || !item.includes("#")) {
|
|
596
517
|
throw new Error(SqlBuilderError.ORDER_BY_ITEM_NEED_HASH(item));
|
|
597
518
|
}
|
|
598
|
-
|
|
599
519
|
const [fieldName, direction] = item.split("#");
|
|
600
520
|
const cleanField = fieldName.trim();
|
|
601
|
-
const cleanDir = direction.trim().toUpperCase()
|
|
602
|
-
|
|
521
|
+
const cleanDir = direction.trim().toUpperCase();
|
|
603
522
|
if (!cleanField) {
|
|
604
523
|
throw new Error(SqlBuilderError.ORDER_BY_FIELD_EMPTY(item));
|
|
605
524
|
}
|
|
606
|
-
|
|
607
525
|
if (!["ASC", "DESC"].includes(cleanDir)) {
|
|
608
526
|
throw new Error(SqlBuilderError.ORDER_BY_DIR_INVALID(cleanDir));
|
|
609
527
|
}
|
|
610
|
-
|
|
611
528
|
const escapedField = this._escapeField(cleanField);
|
|
612
529
|
this._orderBy.push(`${escapedField} ${cleanDir}`);
|
|
613
530
|
});
|
|
614
|
-
|
|
615
531
|
return this;
|
|
616
532
|
}
|
|
617
|
-
|
|
618
533
|
/**
|
|
619
534
|
* GROUP BY
|
|
620
535
|
*/
|
|
621
|
-
groupBy(field
|
|
536
|
+
groupBy(field) {
|
|
622
537
|
if (Array.isArray(field)) {
|
|
623
538
|
const escapedFields = field.filter((f) => typeof f === "string").map((f) => this._escapeField(f));
|
|
624
539
|
this._groupBy = [...this._groupBy, ...escapedFields];
|
|
625
|
-
}
|
|
540
|
+
}
|
|
541
|
+
else if (typeof field === "string") {
|
|
626
542
|
this._groupBy.push(this._escapeField(field));
|
|
627
543
|
}
|
|
628
544
|
return this;
|
|
629
545
|
}
|
|
630
|
-
|
|
631
546
|
/**
|
|
632
547
|
* HAVING
|
|
633
548
|
*/
|
|
634
|
-
having(condition
|
|
549
|
+
having(condition) {
|
|
635
550
|
if (typeof condition === "string") {
|
|
636
551
|
this._having.push(condition);
|
|
637
552
|
}
|
|
638
553
|
return this;
|
|
639
554
|
}
|
|
640
|
-
|
|
641
555
|
/**
|
|
642
556
|
* LIMIT
|
|
643
557
|
*/
|
|
644
|
-
limit(count
|
|
558
|
+
limit(count, offset) {
|
|
645
559
|
if (typeof count !== "number" || count < 0) {
|
|
646
560
|
throw new Error(SqlBuilderError.LIMIT_MUST_NON_NEGATIVE(count));
|
|
647
561
|
}
|
|
@@ -654,186 +568,151 @@ export class SqlBuilder {
|
|
|
654
568
|
}
|
|
655
569
|
return this;
|
|
656
570
|
}
|
|
657
|
-
|
|
658
571
|
/**
|
|
659
572
|
* OFFSET
|
|
660
573
|
*/
|
|
661
|
-
offset(count
|
|
574
|
+
offset(count) {
|
|
662
575
|
if (typeof count !== "number" || count < 0) {
|
|
663
576
|
throw new Error(SqlBuilderError.OFFSET_COUNT_MUST_NON_NEGATIVE(count));
|
|
664
577
|
}
|
|
665
578
|
this._offset = Math.floor(count);
|
|
666
579
|
return this;
|
|
667
580
|
}
|
|
668
|
-
|
|
669
581
|
/**
|
|
670
582
|
* 构建 SELECT 查询
|
|
671
583
|
*/
|
|
672
|
-
toSelectSql()
|
|
584
|
+
toSelectSql() {
|
|
673
585
|
let sql = "SELECT ";
|
|
674
|
-
|
|
675
586
|
sql += this._select.length > 0 ? this._select.join(", ") : "*";
|
|
676
|
-
|
|
677
587
|
if (!this._from) {
|
|
678
588
|
throw new Error(SqlBuilderError.FROM_REQUIRED);
|
|
679
589
|
}
|
|
680
590
|
sql += ` FROM ${this._from}`;
|
|
681
|
-
|
|
682
591
|
if (this._joins.length > 0) {
|
|
683
592
|
sql += " " + this._joins.join(" ");
|
|
684
593
|
}
|
|
685
|
-
|
|
686
594
|
if (this._where.length > 0) {
|
|
687
595
|
sql += " WHERE " + this._where.join(" AND ");
|
|
688
596
|
}
|
|
689
|
-
|
|
690
597
|
if (this._groupBy.length > 0) {
|
|
691
598
|
sql += " GROUP BY " + this._groupBy.join(", ");
|
|
692
599
|
}
|
|
693
|
-
|
|
694
600
|
if (this._having.length > 0) {
|
|
695
601
|
sql += " HAVING " + this._having.join(" AND ");
|
|
696
602
|
}
|
|
697
|
-
|
|
698
603
|
if (this._orderBy.length > 0) {
|
|
699
604
|
sql += " ORDER BY " + this._orderBy.join(", ");
|
|
700
605
|
}
|
|
701
|
-
|
|
702
606
|
if (this._limit !== null) {
|
|
703
607
|
sql += ` LIMIT ${this._limit}`;
|
|
704
608
|
if (this._offset !== null) {
|
|
705
609
|
sql += ` OFFSET ${this._offset}`;
|
|
706
610
|
}
|
|
707
611
|
}
|
|
708
|
-
|
|
709
612
|
return { sql, params: [...this._params] };
|
|
710
613
|
}
|
|
711
|
-
|
|
712
614
|
/**
|
|
713
615
|
* 构建 INSERT 查询
|
|
714
616
|
*/
|
|
715
|
-
toInsertSql(table
|
|
617
|
+
toInsertSql(table, data) {
|
|
716
618
|
if (!table || typeof table !== "string") {
|
|
717
619
|
throw new Error(SqlBuilderError.INSERT_NEED_TABLE(table));
|
|
718
620
|
}
|
|
719
|
-
|
|
720
621
|
if (!data || typeof data !== "object") {
|
|
721
622
|
throw new Error(SqlBuilderError.INSERT_NEED_DATA(table, data));
|
|
722
623
|
}
|
|
723
|
-
|
|
724
624
|
const escapedTable = this._escapeTable(table);
|
|
725
|
-
|
|
726
625
|
if (Array.isArray(data)) {
|
|
727
|
-
const fields = SqlCheck.assertBatchInsertRowsConsistent(data
|
|
728
|
-
|
|
626
|
+
const fields = SqlCheck.assertBatchInsertRowsConsistent(data, { table: table });
|
|
729
627
|
const escapedFields = fields.map((field) => this._escapeField(field));
|
|
730
628
|
const placeholders = fields.map(() => "?").join(", ");
|
|
731
629
|
const values = data.map(() => `(${placeholders})`).join(", ");
|
|
732
|
-
|
|
733
630
|
const sql = `INSERT INTO ${escapedTable} (${escapedFields.join(", ")}) VALUES ${values}`;
|
|
734
|
-
const params
|
|
631
|
+
const params = [];
|
|
735
632
|
for (let i = 0; i < data.length; i++) {
|
|
736
|
-
const row = data[i]
|
|
633
|
+
const row = data[i];
|
|
737
634
|
for (const field of fields) {
|
|
738
635
|
params.push(row[field]);
|
|
739
636
|
}
|
|
740
637
|
}
|
|
741
|
-
|
|
742
638
|
return { sql, params };
|
|
743
|
-
}
|
|
639
|
+
}
|
|
640
|
+
else {
|
|
744
641
|
const fields = Object.keys(data);
|
|
745
642
|
if (fields.length === 0) {
|
|
746
643
|
throw new Error(SqlBuilderError.INSERT_NEED_AT_LEAST_ONE_FIELD(table));
|
|
747
644
|
}
|
|
748
|
-
|
|
749
645
|
for (const field of fields) {
|
|
750
|
-
this._validateParam(
|
|
646
|
+
this._validateParam(data[field]);
|
|
751
647
|
}
|
|
752
|
-
|
|
753
648
|
const escapedFields = fields.map((field) => this._escapeField(field));
|
|
754
649
|
const placeholders = fields.map(() => "?").join(", ");
|
|
755
650
|
const sql = `INSERT INTO ${escapedTable} (${escapedFields.join(", ")}) VALUES (${placeholders})`;
|
|
756
651
|
const params = fields.map((field) => data[field]);
|
|
757
|
-
|
|
758
652
|
return { sql, params };
|
|
759
653
|
}
|
|
760
654
|
}
|
|
761
|
-
|
|
762
655
|
/**
|
|
763
656
|
* 构建 UPDATE 查询
|
|
764
657
|
*/
|
|
765
|
-
toUpdateSql(table
|
|
658
|
+
toUpdateSql(table, data) {
|
|
766
659
|
if (!table || typeof table !== "string") {
|
|
767
660
|
throw new Error(SqlBuilderError.UPDATE_NEED_TABLE);
|
|
768
661
|
}
|
|
769
|
-
|
|
770
662
|
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
771
663
|
throw new Error(SqlBuilderError.UPDATE_NEED_OBJECT);
|
|
772
664
|
}
|
|
773
|
-
|
|
774
665
|
const fields = Object.keys(data);
|
|
775
666
|
if (fields.length === 0) {
|
|
776
667
|
throw new Error(SqlBuilderError.UPDATE_NEED_AT_LEAST_ONE_FIELD);
|
|
777
668
|
}
|
|
778
|
-
|
|
779
669
|
const escapedTable = this._escapeTable(table);
|
|
780
670
|
const setFields = fields.map((field) => `${this._escapeField(field)} = ?`);
|
|
781
|
-
const params
|
|
782
|
-
|
|
671
|
+
const params = [...Object.values(data), ...this._params];
|
|
783
672
|
let sql = `UPDATE ${escapedTable} SET ${setFields.join(", ")}`;
|
|
784
|
-
|
|
785
673
|
if (this._where.length > 0) {
|
|
786
674
|
sql += " WHERE " + this._where.join(" AND ");
|
|
787
|
-
}
|
|
675
|
+
}
|
|
676
|
+
else {
|
|
788
677
|
throw new Error(SqlBuilderError.UPDATE_NEED_WHERE);
|
|
789
678
|
}
|
|
790
|
-
|
|
791
679
|
return { sql, params };
|
|
792
680
|
}
|
|
793
|
-
|
|
794
681
|
/**
|
|
795
682
|
* 构建 DELETE 查询
|
|
796
683
|
*/
|
|
797
|
-
toDeleteSql(table
|
|
684
|
+
toDeleteSql(table) {
|
|
798
685
|
if (!table || typeof table !== "string") {
|
|
799
686
|
throw new Error(SqlBuilderError.DELETE_NEED_TABLE);
|
|
800
687
|
}
|
|
801
|
-
|
|
802
688
|
const escapedTable = this._escapeTable(table);
|
|
803
689
|
let sql = `DELETE FROM ${escapedTable}`;
|
|
804
|
-
|
|
805
690
|
if (this._where.length > 0) {
|
|
806
691
|
sql += " WHERE " + this._where.join(" AND ");
|
|
807
|
-
}
|
|
692
|
+
}
|
|
693
|
+
else {
|
|
808
694
|
throw new Error(SqlBuilderError.DELETE_NEED_WHERE);
|
|
809
695
|
}
|
|
810
|
-
|
|
811
696
|
return { sql, params: [...this._params] };
|
|
812
697
|
}
|
|
813
|
-
|
|
814
698
|
/**
|
|
815
699
|
* 构建 COUNT 查询
|
|
816
700
|
*/
|
|
817
|
-
toCountSql()
|
|
701
|
+
toCountSql() {
|
|
818
702
|
let sql = "SELECT COUNT(*) as total";
|
|
819
|
-
|
|
820
703
|
if (!this._from) {
|
|
821
704
|
throw new Error(SqlBuilderError.COUNT_NEED_FROM);
|
|
822
705
|
}
|
|
823
706
|
sql += ` FROM ${this._from}`;
|
|
824
|
-
|
|
825
707
|
if (this._joins.length > 0) {
|
|
826
708
|
sql += " " + this._joins.join(" ");
|
|
827
709
|
}
|
|
828
|
-
|
|
829
710
|
if (this._where.length > 0) {
|
|
830
711
|
sql += " WHERE " + this._where.join(" AND ");
|
|
831
712
|
}
|
|
832
|
-
|
|
833
713
|
return { sql, params: [...this._params] };
|
|
834
714
|
}
|
|
835
|
-
|
|
836
|
-
static toDeleteInSql(options: { table: string; idField: string; ids: SqlValue[]; quoteIdent: (identifier: string) => string }): SqlQuery {
|
|
715
|
+
static toDeleteInSql(options) {
|
|
837
716
|
if (typeof options.table !== "string" || !options.table.trim()) {
|
|
838
717
|
throw new Error(SqlBuilderError.TO_DELETE_IN_NEED_TABLE(options.table));
|
|
839
718
|
}
|
|
@@ -846,23 +725,11 @@ export class SqlBuilder {
|
|
|
846
725
|
if (options.ids.length === 0) {
|
|
847
726
|
return { sql: "", params: [] };
|
|
848
727
|
}
|
|
849
|
-
|
|
850
728
|
const placeholders = options.ids.map(() => "?").join(",");
|
|
851
729
|
const sql = `DELETE FROM ${options.quoteIdent(options.table)} WHERE ${options.quoteIdent(options.idField)} IN (${placeholders})`;
|
|
852
730
|
return { sql: sql, params: [...options.ids] };
|
|
853
731
|
}
|
|
854
|
-
|
|
855
|
-
static toUpdateCaseByIdSql(options: {
|
|
856
|
-
table: string;
|
|
857
|
-
idField: string;
|
|
858
|
-
rows: Array<{ id: SqlValue; data: Record<string, SqlValue> }>;
|
|
859
|
-
fields: string[];
|
|
860
|
-
quoteIdent: (identifier: string) => string;
|
|
861
|
-
updatedAtField: string;
|
|
862
|
-
updatedAtValue: SqlValue;
|
|
863
|
-
stateField?: string;
|
|
864
|
-
stateGtZero?: boolean;
|
|
865
|
-
}): SqlQuery {
|
|
732
|
+
static toUpdateCaseByIdSql(options) {
|
|
866
733
|
if (typeof options.table !== "string" || !options.table.trim()) {
|
|
867
734
|
throw new Error(SqlBuilderError.TO_UPDATE_CASE_NEED_TABLE(options.table));
|
|
868
735
|
}
|
|
@@ -881,55 +748,42 @@ export class SqlBuilder {
|
|
|
881
748
|
if (options.fields.length === 0) {
|
|
882
749
|
return { sql: "", params: [] };
|
|
883
750
|
}
|
|
884
|
-
|
|
885
|
-
const ids: SqlValue[] = options.rows.map((r) => r.id);
|
|
751
|
+
const ids = options.rows.map((r) => r.id);
|
|
886
752
|
const placeholders = ids.map(() => "?").join(",");
|
|
887
|
-
|
|
888
|
-
const
|
|
889
|
-
const args: SqlValue[] = [];
|
|
890
|
-
|
|
753
|
+
const setSqlList = [];
|
|
754
|
+
const args = [];
|
|
891
755
|
const quotedId = options.quoteIdent(options.idField);
|
|
892
|
-
|
|
893
756
|
for (const field of options.fields) {
|
|
894
|
-
const whenList
|
|
895
|
-
|
|
757
|
+
const whenList = [];
|
|
896
758
|
for (const row of options.rows) {
|
|
897
759
|
if (!(field in row.data)) {
|
|
898
760
|
continue;
|
|
899
761
|
}
|
|
900
|
-
|
|
901
762
|
whenList.push("WHEN ? THEN ?");
|
|
902
763
|
args.push(row.id);
|
|
903
764
|
args.push(row.data[field]);
|
|
904
765
|
}
|
|
905
|
-
|
|
906
766
|
if (whenList.length === 0) {
|
|
907
767
|
continue;
|
|
908
768
|
}
|
|
909
|
-
|
|
910
769
|
const quotedField = options.quoteIdent(field);
|
|
911
770
|
setSqlList.push(`${quotedField} = CASE ${quotedId} ${whenList.join(" ")} ELSE ${quotedField} END`);
|
|
912
771
|
}
|
|
913
|
-
|
|
914
772
|
setSqlList.push(`${options.quoteIdent(options.updatedAtField)} = ?`);
|
|
915
773
|
args.push(options.updatedAtValue);
|
|
916
|
-
|
|
917
774
|
for (const id of ids) {
|
|
918
775
|
args.push(id);
|
|
919
776
|
}
|
|
920
|
-
|
|
921
777
|
let sql = `UPDATE ${options.quoteIdent(options.table)} SET ${setSqlList.join(", ")} WHERE ${quotedId} IN (${placeholders})`;
|
|
922
778
|
if (options.stateGtZero && options.stateField) {
|
|
923
779
|
sql += ` AND ${options.quoteIdent(options.stateField)} > 0`;
|
|
924
780
|
}
|
|
925
|
-
|
|
926
781
|
return { sql: sql, params: args };
|
|
927
782
|
}
|
|
928
783
|
}
|
|
929
|
-
|
|
930
784
|
/**
|
|
931
785
|
* 创建新的 SQL 构建器实例
|
|
932
786
|
*/
|
|
933
|
-
export function createQueryBuilder()
|
|
787
|
+
export function createQueryBuilder() {
|
|
934
788
|
return new SqlBuilder();
|
|
935
789
|
}
|