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