befly 3.20.10 → 3.21.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.
@@ -1,114 +0,0 @@
1
- import { isNonEmptyString } from "../../utils/is.js";
2
- import { SqlCheck } from "./check.js";
3
-
4
- export function toDeleteInSql(options) {
5
- if (!isNonEmptyString(options.table)) {
6
- throw new Error(`toDeleteInSql 需要非空表名 (table: ${String(options.table)})`, {
7
- cause: null,
8
- code: "validation"
9
- });
10
- }
11
- if (!isNonEmptyString(options.idField)) {
12
- throw new Error(`toDeleteInSql 需要非空 idField (idField: ${String(options.idField)})`, {
13
- cause: null,
14
- code: "validation"
15
- });
16
- }
17
- if (!Array.isArray(options.ids)) {
18
- throw new Error("toDeleteInSql 需要 ids 数组", {
19
- cause: null,
20
- code: "validation"
21
- });
22
- }
23
- if (options.ids.length === 0) {
24
- return { sql: "", params: [] };
25
- }
26
-
27
- const placeholders = options.ids.map(() => "?").join(",");
28
- const sql = `DELETE FROM ${options.quoteIdent(options.table)} WHERE ${options.quoteIdent(options.idField)} IN (${placeholders})`;
29
- const params = [];
30
- for (const id of options.ids) {
31
- params.push(id);
32
- }
33
- return { sql: sql, params: params };
34
- }
35
-
36
- export function toUpdateCaseByIdSql(options) {
37
- if (!isNonEmptyString(options.table)) {
38
- throw new Error(`toUpdateCaseByIdSql 需要非空表名 (table: ${String(options.table)})`, {
39
- cause: null,
40
- code: "validation"
41
- });
42
- }
43
- if (!isNonEmptyString(options.idField)) {
44
- throw new Error(`toUpdateCaseByIdSql 需要非空 idField (idField: ${String(options.idField)})`, {
45
- cause: null,
46
- code: "validation"
47
- });
48
- }
49
- if (!Array.isArray(options.rows)) {
50
- throw new Error("toUpdateCaseByIdSql 需要 rows 数组", {
51
- cause: null,
52
- code: "validation"
53
- });
54
- }
55
- if (options.rows.length === 0) {
56
- return { sql: "", params: [] };
57
- }
58
- if (!Array.isArray(options.fields)) {
59
- throw new Error("toUpdateCaseByIdSql 需要 fields 数组", {
60
- cause: null,
61
- code: "validation"
62
- });
63
- }
64
- if (options.fields.length === 0) {
65
- return { sql: "", params: [] };
66
- }
67
-
68
- const ids = options.rows.map((row) => row.id);
69
- const placeholders = ids.map(() => "?").join(",");
70
-
71
- const setSqlList = [];
72
- const args = [];
73
-
74
- const quotedId = options.quoteIdent(options.idField);
75
-
76
- for (const field of options.fields) {
77
- const whenList = [];
78
-
79
- for (const row of options.rows) {
80
- if (!(field in row.data)) {
81
- continue;
82
- }
83
-
84
- whenList.push("WHEN ? THEN ?");
85
- args.push(row.id);
86
- const value = row.data[field];
87
- SqlCheck.assertNoUndefinedParam(value, "SQL 参数值");
88
- args.push(value);
89
- }
90
-
91
- if (whenList.length === 0) {
92
- continue;
93
- }
94
-
95
- const quotedField = options.quoteIdent(field);
96
- setSqlList.push(`${quotedField} = CASE ${quotedId} ${whenList.join(" ")} ELSE ${quotedField} END`);
97
- }
98
-
99
- if (isNonEmptyString(options.updatedAtField)) {
100
- setSqlList.push(`${options.quoteIdent(options.updatedAtField)} = ?`);
101
- args.push(options.updatedAtValue);
102
- }
103
-
104
- for (const id of ids) {
105
- args.push(id);
106
- }
107
-
108
- let sql = `UPDATE ${options.quoteIdent(options.table)} SET ${setSqlList.join(", ")} WHERE ${quotedId} IN (${placeholders})`;
109
- if (options.stateGtZero && options.stateField) {
110
- sql += ` AND ${options.quoteIdent(options.stateField)} > 0`;
111
- }
112
-
113
- return { sql: sql, params: args };
114
- }
@@ -1,82 +0,0 @@
1
- import { isNonEmptyString } from "../../utils/is.js";
2
-
3
- function throwValidation(message) {
4
- throw new Error(message, {
5
- cause: null,
6
- code: "validation"
7
- });
8
- }
9
-
10
- export class SqlCheck {
11
- static assertNoUndefinedParam(value, label) {
12
- if (value === undefined) {
13
- throwValidation(`${label} 不能为 undefined`);
14
- }
15
- }
16
-
17
- static assertSafeIdentifierPart(part, kind) {
18
- if (!SqlCheck.SAFE_IDENTIFIER_RE.test(part)) {
19
- throwValidation(`无效的 ${kind} 标识符: ${part}`);
20
- }
21
- }
22
-
23
- static assertNoExprField(field) {
24
- if (!isNonEmptyString(field)) {
25
- return;
26
- }
27
- const trimmed = field.trim();
28
- if (trimmed.includes("(") || trimmed.includes(")")) {
29
- throwValidation(`字段包含函数/表达式,请使用 selectRaw/whereRaw (field: ${trimmed})`);
30
- }
31
- }
32
-
33
- static assertBatchInsertRowsConsistent(rows, options) {
34
- if (!Array.isArray(rows)) {
35
- throwValidation("批量插入 rows 必须是数组");
36
- }
37
- if (rows.length === 0) {
38
- throwValidation(`插入数据不能为空 (table: ${options.table})`);
39
- }
40
-
41
- const first = rows[0];
42
- if (!first || typeof first !== "object" || Array.isArray(first)) {
43
- throwValidation(`批量插入的每一行必须是对象 (table: ${options.table}, rowIndex: 0)`);
44
- }
45
-
46
- const fields = Object.keys(first);
47
- if (fields.length === 0) {
48
- throwValidation(`插入数据必须至少有一个字段 (table: ${options.table})`);
49
- }
50
-
51
- const fieldSet = new Set(fields);
52
-
53
- for (let i = 0; i < rows.length; i++) {
54
- const row = rows[i];
55
- if (!row || typeof row !== "object" || Array.isArray(row)) {
56
- throwValidation(`批量插入的每一行必须是对象 (table: ${options.table}, rowIndex: ${i})`);
57
- }
58
-
59
- const rowKeys = Object.keys(row);
60
- if (rowKeys.length !== fields.length) {
61
- throwValidation(`批量插入每行字段必须一致 (table: ${options.table}, rowIndex: ${i})`);
62
- }
63
-
64
- for (const key of rowKeys) {
65
- if (!fieldSet.has(key)) {
66
- throwValidation(`批量插入每行字段必须一致 (table: ${options.table}, rowIndex: ${i}, extraField: ${key})`);
67
- }
68
- }
69
-
70
- for (const field of fields) {
71
- if (!(field in row)) {
72
- throwValidation(`批量插入缺少字段 (table: ${options.table}, rowIndex: ${i}, field: ${field})`);
73
- }
74
- SqlCheck.assertNoUndefinedParam(row[field], `批量插入字段值 (table: ${options.table}, rowIndex: ${i}, field: ${field})`);
75
- }
76
- }
77
-
78
- return fields;
79
- }
80
- }
81
-
82
- SqlCheck.SAFE_IDENTIFIER_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
@@ -1,360 +0,0 @@
1
- import { SqlCheck } from "./check.js";
2
- import { escapeField, escapeTable, validateParam } from "./util.js";
3
- import { isString } from "../../utils/is.js";
4
-
5
- function pushParams(target, source) {
6
- for (const param of source) {
7
- target.push(param);
8
- }
9
- }
10
-
11
- function compileFromSql(model, quoteIdent, errorMessage) {
12
- if (!model.from) {
13
- throw new Error(errorMessage, {
14
- cause: null,
15
- code: "validation"
16
- });
17
- }
18
-
19
- return model.from.type === "raw" ? model.from.value : escapeTable(model.from.value, quoteIdent);
20
- }
21
-
22
- function compileJoinSql(model, quoteIdent) {
23
- if (model.joins.length === 0) {
24
- return "";
25
- }
26
-
27
- return model.joins
28
- .map((join) => {
29
- const joinTable = escapeTable(join.table, quoteIdent);
30
- const joinType = join.type.toUpperCase();
31
- return `${joinType} JOIN ${joinTable} ON ${join.on}`;
32
- })
33
- .join(" ");
34
- }
35
-
36
- function appendCompiledWhere(sql, params, whereNode, quoteIdent) {
37
- const whereResult = compileWhere(whereNode, quoteIdent);
38
- if (!whereResult.sql) {
39
- return sql;
40
- }
41
-
42
- pushParams(params, whereResult.params);
43
- return `${sql} WHERE ${whereResult.sql}`;
44
- }
45
-
46
- function compileReadBaseSql(model, quoteIdent, headSql, fromErrorMessage) {
47
- const params = [];
48
- let sql = `${headSql} FROM ${compileFromSql(model, quoteIdent, fromErrorMessage)}`;
49
-
50
- const joinSql = compileJoinSql(model, quoteIdent);
51
- if (joinSql) {
52
- sql += ` ${joinSql}`;
53
- }
54
-
55
- sql = appendCompiledWhere(sql, params, model.where, quoteIdent);
56
- return { sql: sql, params: params };
57
- }
58
-
59
- function compileOrderBySql(orderBy, quoteIdent) {
60
- if (orderBy.length === 0) {
61
- return "";
62
- }
63
-
64
- return orderBy
65
- .map((item) => {
66
- const escapedField = escapeField(item.field, quoteIdent);
67
- return `${escapedField} ${item.dir}`;
68
- })
69
- .join(", ");
70
- }
71
-
72
- function appendLimitSql(sql, model) {
73
- if (model.limit === null) {
74
- return sql;
75
- }
76
-
77
- let result = `${sql} LIMIT ${model.limit}`;
78
- if (model.offset !== null) {
79
- result += ` OFFSET ${model.offset}`;
80
- }
81
-
82
- return result;
83
- }
84
-
85
- export function compileSelect(model, quoteIdent) {
86
- const selectSql =
87
- model.select.length > 0
88
- ? model.select
89
- .map((item) => {
90
- if (item.type === "raw") {
91
- return item.value;
92
- }
93
- return escapeField(item.value, quoteIdent);
94
- })
95
- .join(", ")
96
- : "*";
97
-
98
- const result = compileReadBaseSql(model, quoteIdent, `SELECT ${selectSql}`, "FROM 表名是必需的");
99
- const orderSql = compileOrderBySql(model.orderBy, quoteIdent);
100
- if (orderSql) {
101
- result.sql += ` ORDER BY ${orderSql}`;
102
- }
103
-
104
- result.sql = appendLimitSql(result.sql, model);
105
- return result;
106
- }
107
-
108
- export function compileCount(model, quoteIdent) {
109
- return compileReadBaseSql(model, quoteIdent, "SELECT COUNT(*) as total", "COUNT 需要 FROM 表名");
110
- }
111
-
112
- export function compileInsert(table, data, quoteIdent) {
113
- if (!table || !isString(table)) {
114
- throw new Error(`INSERT 需要表名 (table: ${String(table)})`, {
115
- cause: null,
116
- code: "validation"
117
- });
118
- }
119
-
120
- if (!data || typeof data !== "object") {
121
- throw new Error(`INSERT 需要数据 (table: ${String(table)}, data: ${JSON.stringify(data)})`, {
122
- cause: null,
123
- code: "validation"
124
- });
125
- }
126
-
127
- const escapedTable = escapeTable(table, quoteIdent);
128
-
129
- if (Array.isArray(data)) {
130
- const fields = SqlCheck.assertBatchInsertRowsConsistent(data, { table: table });
131
-
132
- const escapedFields = fields.map((field) => escapeField(field, quoteIdent));
133
- const placeholders = fields.map(() => "?").join(", ");
134
- const values = data.map(() => `(${placeholders})`).join(", ");
135
-
136
- const sql = `INSERT INTO ${escapedTable} (${escapedFields.join(", ")}) VALUES ${values}`;
137
- const params = [];
138
- for (let i = 0; i < data.length; i++) {
139
- const row = data[i];
140
- for (const field of fields) {
141
- const value = row[field];
142
- validateParam(value);
143
- params.push(value);
144
- }
145
- }
146
-
147
- return { sql: sql, params: params };
148
- }
149
-
150
- const fields = Object.keys(data);
151
- if (fields.length === 0) {
152
- throw new Error(`插入数据必须至少有一个字段 (table: ${table})`, {
153
- cause: null,
154
- code: "validation"
155
- });
156
- }
157
-
158
- for (const field of fields) {
159
- validateParam(data[field]);
160
- }
161
-
162
- const escapedFields = fields.map((field) => escapeField(field, quoteIdent));
163
- const placeholders = fields.map(() => "?").join(", ");
164
- const sql = `INSERT INTO ${escapedTable} (${escapedFields.join(", ")}) VALUES (${placeholders})`;
165
- const params = [];
166
- for (const field of fields) {
167
- params.push(data[field]);
168
- }
169
-
170
- return { sql: sql, params: params };
171
- }
172
-
173
- export function compileUpdate(model, table, data, quoteIdent) {
174
- if (!table || !isString(table)) {
175
- throw new Error("UPDATE 需要表名", {
176
- cause: null,
177
- code: "validation"
178
- });
179
- }
180
-
181
- if (!data || typeof data !== "object" || Array.isArray(data)) {
182
- throw new Error("UPDATE 需要数据对象", {
183
- cause: null,
184
- code: "validation"
185
- });
186
- }
187
-
188
- const fields = Object.keys(data);
189
- if (fields.length === 0) {
190
- throw new Error("更新数据必须至少有一个字段", {
191
- cause: null,
192
- code: "validation"
193
- });
194
- }
195
-
196
- const escapedTable = escapeTable(table, quoteIdent);
197
- const setFields = fields.map((field) => `${escapeField(field, quoteIdent)} = ?`);
198
- const params = [];
199
- for (const value of Object.values(data)) {
200
- params.push(value);
201
- }
202
-
203
- let sql = `UPDATE ${escapedTable} SET ${setFields.join(", ")}`;
204
-
205
- const whereResult = compileWhere(model.where, quoteIdent);
206
- if (whereResult.sql) {
207
- sql += ` WHERE ${whereResult.sql}`;
208
- pushParams(params, whereResult.params);
209
- } else {
210
- throw new Error("为安全起见,UPDATE 需要 WHERE 条件", {
211
- cause: null,
212
- code: "validation"
213
- });
214
- }
215
-
216
- return { sql: sql, params: params };
217
- }
218
-
219
- export function compileDelete(model, table, quoteIdent) {
220
- if (!table || !isString(table)) {
221
- throw new Error("DELETE 需要表名", {
222
- cause: null,
223
- code: "validation"
224
- });
225
- }
226
-
227
- const escapedTable = escapeTable(table, quoteIdent);
228
- let sql = `DELETE FROM ${escapedTable}`;
229
-
230
- const whereResult = compileWhere(model.where, quoteIdent);
231
- if (whereResult.sql) {
232
- sql += ` WHERE ${whereResult.sql}`;
233
- } else {
234
- throw new Error("为安全起见,DELETE 需要 WHERE 条件", {
235
- cause: null,
236
- code: "validation"
237
- });
238
- }
239
-
240
- return { sql: sql, params: whereResult.params };
241
- }
242
-
243
- export function compileWhere(node, quoteIdent) {
244
- if (!node) {
245
- return { sql: "", params: [] };
246
- }
247
-
248
- if (node.type === "raw") {
249
- const params = Array.isArray(node.params) ? node.params.slice() : [];
250
- return { sql: node.sql, params: params };
251
- }
252
-
253
- if (node.type === "op") {
254
- return compileOperatorNode(node, quoteIdent);
255
- }
256
-
257
- if (node.type === "group") {
258
- if (!node.items || node.items.length === 0) {
259
- return { sql: "", params: [] };
260
- }
261
- const parts = [];
262
- const params = [];
263
- for (const item of node.items) {
264
- const built = compileWhere(item, quoteIdent);
265
- if (!built.sql) {
266
- continue;
267
- }
268
- let clause = built.sql;
269
- if (node.join === "OR") {
270
- clause = `(${clause})`;
271
- } else if (item.type === "group" && item.join === "OR") {
272
- clause = `(${clause})`;
273
- }
274
- parts.push(clause);
275
- pushParams(params, built.params);
276
- }
277
- return { sql: parts.join(` ${node.join} `), params: params };
278
- }
279
-
280
- return { sql: "", params: [] };
281
- }
282
-
283
- function compileOperatorNode(node, quoteIdent) {
284
- const escapedField = escapeField(node.field, quoteIdent);
285
- const operator = node.operator;
286
-
287
- const buildLikeParam = (value, mode) => {
288
- validateParam(value);
289
- const text = String(value);
290
-
291
- if (mode === "left") {
292
- return `%${text}`;
293
- }
294
-
295
- if (mode === "right") {
296
- return `${text}%`;
297
- }
298
-
299
- return `%${text}%`;
300
- };
301
-
302
- switch (operator) {
303
- case "$ne":
304
- case "$not":
305
- validateParam(node.value);
306
- return { sql: `${escapedField} != ?`, params: [node.value] };
307
- case "$in": {
308
- const placeholders = node.value.map(() => "?").join(",");
309
- const params = [];
310
- for (const item of node.value) {
311
- params.push(item);
312
- }
313
- return { sql: `${escapedField} IN (${placeholders})`, params: params };
314
- }
315
- case "$nin":
316
- case "$notIn": {
317
- const placeholders = node.value.map(() => "?").join(",");
318
- const params = [];
319
- for (const item of node.value) {
320
- params.push(item);
321
- }
322
- return { sql: `${escapedField} NOT IN (${placeholders})`, params: params };
323
- }
324
- case "$like":
325
- case "like":
326
- return { sql: `${escapedField} LIKE ?`, params: [buildLikeParam(node.value, "both")] };
327
- case "$leftLike":
328
- case "leftLike":
329
- return { sql: `${escapedField} LIKE ?`, params: [buildLikeParam(node.value, "left")] };
330
- case "$rightLike":
331
- case "rightLike":
332
- return { sql: `${escapedField} LIKE ?`, params: [buildLikeParam(node.value, "right")] };
333
- case "$notLike":
334
- validateParam(node.value);
335
- return { sql: `${escapedField} NOT LIKE ?`, params: [node.value] };
336
- case "$gt":
337
- validateParam(node.value);
338
- return { sql: `${escapedField} > ?`, params: [node.value] };
339
- case "$gte":
340
- validateParam(node.value);
341
- return { sql: `${escapedField} >= ?`, params: [node.value] };
342
- case "$lt":
343
- validateParam(node.value);
344
- return { sql: `${escapedField} < ?`, params: [node.value] };
345
- case "$lte":
346
- validateParam(node.value);
347
- return { sql: `${escapedField} <= ?`, params: [node.value] };
348
- case "$between":
349
- return { sql: `${escapedField} BETWEEN ? AND ?`, params: [node.value[0], node.value[1]] };
350
- case "$notBetween":
351
- return { sql: `${escapedField} NOT BETWEEN ? AND ?`, params: [node.value[0], node.value[1]] };
352
- case "$null":
353
- return { sql: `${escapedField} IS NULL`, params: [] };
354
- case "$notNull":
355
- return { sql: `${escapedField} IS NOT NULL`, params: [] };
356
- default:
357
- validateParam(node.value);
358
- return { sql: `${escapedField} = ?`, params: [node.value] };
359
- }
360
- }