befly 3.20.11 → 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.
@@ -0,0 +1,714 @@
1
+ /**
2
+ * SQL 构造器 - JavaScript 版本
3
+ * 提供链式 API 构建 SQL 查询
4
+ */
5
+
6
+ import { assertBatchInsertRowsConsistent, assertNoUndefinedParam, escapeField, escapeTable, normalizeLimitValue, normalizeOffsetValue, resolveQuoteIdent, validateParam } from "./dbUtil.js";
7
+ import { isNonEmptyString, isString } from "../utils/is.js";
8
+
9
+ function createModel() {
10
+ return {
11
+ select: [],
12
+ from: null,
13
+ where: { type: "group", join: "AND", items: [] },
14
+ joins: [],
15
+ orderBy: [],
16
+ limit: null,
17
+ offset: null
18
+ };
19
+ }
20
+
21
+ /**
22
+ * SQL 构建器类
23
+ */
24
+ export class SqlBuilder {
25
+ _model;
26
+ _quoteIdent;
27
+
28
+ constructor(options = {}) {
29
+ this._quoteIdent = resolveQuoteIdent(options);
30
+ this._model = createModel();
31
+ }
32
+
33
+ /**
34
+ * 重置构建器状态
35
+ */
36
+ reset() {
37
+ this._model = createModel();
38
+ return this;
39
+ }
40
+
41
+ _appendWhereNode(root, node) {
42
+ if (node) {
43
+ root.items.push(node);
44
+ }
45
+ }
46
+
47
+ _buildArrayOperatorNode(fieldName, operator, value, errorFactory, emptyMessage) {
48
+ if (!Array.isArray(value)) {
49
+ throw new Error(errorFactory(operator), {
50
+ cause: null,
51
+ code: "validation"
52
+ });
53
+ }
54
+ if (value.length === 0) {
55
+ throw new Error(emptyMessage, {
56
+ cause: null,
57
+ code: "validation"
58
+ });
59
+ }
60
+
61
+ return { type: "op", field: fieldName, operator: operator, value: value };
62
+ }
63
+
64
+ _buildRangeOrNullOperatorNode(fieldName, operator, value) {
65
+ if (operator === "$between" || operator === "$notBetween") {
66
+ if (Array.isArray(value) && value.length === 2) {
67
+ return { type: "op", field: fieldName, operator: operator, value: value };
68
+ }
69
+ return null;
70
+ }
71
+
72
+ if (value === true) {
73
+ return { type: "op", field: fieldName, operator: operator, value: value };
74
+ }
75
+
76
+ return null;
77
+ }
78
+
79
+ _buildOperatorNode(fieldName, operator, value) {
80
+ switch (operator) {
81
+ case "$in":
82
+ return this._buildArrayOperatorNode(fieldName, operator, value, (currentOperator) => `$in 操作符的值必须是数组 (operator: ${currentOperator})`, "$in 操作符的数组不能为空。提示:空数组会导致查询永远不匹配任何记录,这通常不是预期行为。请检查查询条件或移除该字段。");
83
+ case "$nin":
84
+ case "$notIn":
85
+ return this._buildArrayOperatorNode(fieldName, operator, value, (currentOperator) => `$nin/$notIn 操作符的值必须是数组 (operator: ${currentOperator})`, "$nin/$notIn 操作符的数组不能为空。提示:空数组会导致查询匹配所有记录,这通常不是预期行为。请检查查询条件或移除该字段。");
86
+ case "$between":
87
+ case "$notBetween":
88
+ case "$null":
89
+ case "$notNull":
90
+ return this._buildRangeOrNullOperatorNode(fieldName, operator, value);
91
+ case "$like":
92
+ case "like":
93
+ case "$leftLike":
94
+ case "leftLike":
95
+ case "$rightLike":
96
+ case "rightLike":
97
+ if (isString(value) && value.trim() === "") {
98
+ return null;
99
+ }
100
+ validateParam(value);
101
+ return { type: "op", field: fieldName, operator: operator, value: value };
102
+ default:
103
+ validateParam(value);
104
+ return { type: "op", field: fieldName, operator: operator, value: value };
105
+ }
106
+ }
107
+
108
+ _appendFieldCondition(group, key, value) {
109
+ if (key.includes("$")) {
110
+ const lastDollarIndex = key.lastIndexOf("$");
111
+ this._appendWhereNode(group, this._buildOperatorNode(key.substring(0, lastDollarIndex), `$${key.substring(lastDollarIndex + 1)}`, value));
112
+ return;
113
+ }
114
+
115
+ if (value && typeof value === "object" && !Array.isArray(value)) {
116
+ for (const [operator, operatorValue] of Object.entries(value)) {
117
+ this._appendWhereNode(group, this._buildOperatorNode(key, operator, operatorValue));
118
+ }
119
+ return;
120
+ }
121
+
122
+ this._appendWhereNode(group, this._buildOperatorNode(key, "=", value));
123
+ }
124
+
125
+ _appendAndConditions(group, value) {
126
+ if (!Array.isArray(value)) {
127
+ return;
128
+ }
129
+
130
+ for (const condition of value) {
131
+ if (!condition || typeof condition !== "object" || Array.isArray(condition)) {
132
+ continue;
133
+ }
134
+
135
+ const sub = this._parseWhereObject(condition);
136
+ if (sub.items.length === 0) {
137
+ continue;
138
+ }
139
+
140
+ if (sub.join === "AND") {
141
+ for (const item of sub.items) {
142
+ group.items.push(item);
143
+ }
144
+ continue;
145
+ }
146
+
147
+ group.items.push(sub);
148
+ }
149
+ }
150
+
151
+ _appendOrConditions(group, value) {
152
+ if (!Array.isArray(value)) {
153
+ return;
154
+ }
155
+
156
+ const orGroup = { type: "group", join: "OR", items: [] };
157
+ for (const condition of value) {
158
+ if (!condition || typeof condition !== "object" || Array.isArray(condition)) {
159
+ continue;
160
+ }
161
+
162
+ const sub = this._parseWhereObject(condition);
163
+ if (sub.items.length > 0) {
164
+ orGroup.items.push(sub);
165
+ }
166
+ }
167
+
168
+ if (orGroup.items.length > 0) {
169
+ group.items.push(orGroup);
170
+ }
171
+ }
172
+
173
+ _parseWhereObject(whereObj) {
174
+ if (!whereObj || typeof whereObj !== "object") {
175
+ return { type: "group", join: "AND", items: [] };
176
+ }
177
+
178
+ const group = { type: "group", join: "AND", items: [] };
179
+
180
+ for (const [key, value] of Object.entries(whereObj)) {
181
+ if (value === undefined) {
182
+ continue;
183
+ }
184
+
185
+ if (key === "$and") {
186
+ this._appendAndConditions(group, value);
187
+ continue;
188
+ }
189
+
190
+ if (key === "$or") {
191
+ this._appendOrConditions(group, value);
192
+ continue;
193
+ }
194
+
195
+ this._appendFieldCondition(group, key, value);
196
+ }
197
+
198
+ return group;
199
+ }
200
+
201
+ _pushWhereParams(target, source) {
202
+ for (const param of source) {
203
+ target.push(param);
204
+ }
205
+ }
206
+
207
+ _compileOperatorNode(node) {
208
+ const escapedField = escapeField(node.field, this._quoteIdent);
209
+
210
+ if (node.operator === "$ne" || node.operator === "$not") {
211
+ validateParam(node.value);
212
+ return { sql: `${escapedField} != ?`, params: [node.value] };
213
+ }
214
+
215
+ if (node.operator === "$in") {
216
+ return {
217
+ sql: `${escapedField} IN (${node.value.map(() => "?").join(",")})`,
218
+ params: node.value.slice()
219
+ };
220
+ }
221
+
222
+ if (node.operator === "$nin" || node.operator === "$notIn") {
223
+ return {
224
+ sql: `${escapedField} NOT IN (${node.value.map(() => "?").join(",")})`,
225
+ params: node.value.slice()
226
+ };
227
+ }
228
+
229
+ if (node.operator === "$like" || node.operator === "like") {
230
+ validateParam(node.value);
231
+ return { sql: `${escapedField} LIKE ?`, params: [`%${String(node.value)}%`] };
232
+ }
233
+
234
+ if (node.operator === "$leftLike" || node.operator === "leftLike") {
235
+ validateParam(node.value);
236
+ return { sql: `${escapedField} LIKE ?`, params: [`%${String(node.value)}`] };
237
+ }
238
+
239
+ if (node.operator === "$rightLike" || node.operator === "rightLike") {
240
+ validateParam(node.value);
241
+ return { sql: `${escapedField} LIKE ?`, params: [`${String(node.value)}%`] };
242
+ }
243
+
244
+ if (node.operator === "$notLike") {
245
+ validateParam(node.value);
246
+ return { sql: `${escapedField} NOT LIKE ?`, params: [node.value] };
247
+ }
248
+
249
+ if (node.operator === "$gt") {
250
+ validateParam(node.value);
251
+ return { sql: `${escapedField} > ?`, params: [node.value] };
252
+ }
253
+
254
+ if (node.operator === "$gte") {
255
+ validateParam(node.value);
256
+ return { sql: `${escapedField} >= ?`, params: [node.value] };
257
+ }
258
+
259
+ if (node.operator === "$lt") {
260
+ validateParam(node.value);
261
+ return { sql: `${escapedField} < ?`, params: [node.value] };
262
+ }
263
+
264
+ if (node.operator === "$lte") {
265
+ validateParam(node.value);
266
+ return { sql: `${escapedField} <= ?`, params: [node.value] };
267
+ }
268
+
269
+ if (node.operator === "$between") {
270
+ return { sql: `${escapedField} BETWEEN ? AND ?`, params: [node.value[0], node.value[1]] };
271
+ }
272
+
273
+ if (node.operator === "$notBetween") {
274
+ return { sql: `${escapedField} NOT BETWEEN ? AND ?`, params: [node.value[0], node.value[1]] };
275
+ }
276
+
277
+ if (node.operator === "$null") {
278
+ return { sql: `${escapedField} IS NULL`, params: [] };
279
+ }
280
+
281
+ if (node.operator === "$notNull") {
282
+ return { sql: `${escapedField} IS NOT NULL`, params: [] };
283
+ }
284
+
285
+ validateParam(node.value);
286
+ return { sql: `${escapedField} = ?`, params: [node.value] };
287
+ }
288
+
289
+ _compileWhereNode(node) {
290
+ if (!node) {
291
+ return { sql: "", params: [] };
292
+ }
293
+
294
+ if (node.type === "raw") {
295
+ return { sql: node.sql, params: Array.isArray(node.params) ? node.params.slice() : [] };
296
+ }
297
+
298
+ if (node.type === "op") {
299
+ return this._compileOperatorNode(node);
300
+ }
301
+
302
+ if (node.type !== "group" || !node.items || node.items.length === 0) {
303
+ return { sql: "", params: [] };
304
+ }
305
+
306
+ const parts = [];
307
+ const params = [];
308
+
309
+ for (const item of node.items) {
310
+ const built = this._compileWhereNode(item);
311
+ if (!built.sql) {
312
+ continue;
313
+ }
314
+
315
+ let clause = built.sql;
316
+ if (node.join === "OR") {
317
+ clause = `(${clause})`;
318
+ } else if (item.type === "group" && item.join === "OR") {
319
+ clause = `(${clause})`;
320
+ }
321
+
322
+ parts.push(clause);
323
+ this._pushWhereParams(params, built.params);
324
+ }
325
+
326
+ return { sql: parts.join(` ${node.join} `), params: params };
327
+ }
328
+
329
+ /**
330
+ * 获取 WHERE 条件(供 DbHelper 使用)
331
+ */
332
+ getWhereConditions() {
333
+ const result = this._compileWhereNode(this._model.where);
334
+ return { sql: result.sql, params: result.params };
335
+ }
336
+
337
+ /**
338
+ * SELECT 字段
339
+ */
340
+ select(fields = "*") {
341
+ if (Array.isArray(fields)) {
342
+ for (const field of fields) {
343
+ this._model.select.push({ type: "field", value: field });
344
+ }
345
+ return this;
346
+ }
347
+
348
+ if (isString(fields)) {
349
+ this._model.select.push({ type: "field", value: fields });
350
+ }
351
+ return this;
352
+ }
353
+
354
+ /**
355
+ * SELECT 原始表达式(不做转义)
356
+ */
357
+ selectRaw(expr) {
358
+ this._model.select.push({ type: "raw", value: expr });
359
+ return this;
360
+ }
361
+
362
+ /**
363
+ * FROM 表名
364
+ */
365
+ from(table) {
366
+ this._model.from = { type: "table", value: table.trim() };
367
+ return this;
368
+ }
369
+
370
+ /**
371
+ * FROM 原始表达式(不做转义)
372
+ */
373
+ fromRaw(tableExpr) {
374
+ this._model.from = { type: "raw", value: tableExpr.trim() };
375
+ return this;
376
+ }
377
+
378
+ /**
379
+ * WHERE 条件
380
+ */
381
+ where(conditionOrField, value) {
382
+ if (conditionOrField && typeof conditionOrField === "object" && !Array.isArray(conditionOrField)) {
383
+ const node = this._parseWhereObject(conditionOrField);
384
+ if (node.items.length > 0) {
385
+ this._model.where.items.push(node);
386
+ }
387
+ return this;
388
+ }
389
+
390
+ if (isString(conditionOrField)) {
391
+ this._appendWhereNode(this._model.where, this._buildOperatorNode(conditionOrField, "=", value));
392
+ }
393
+ return this;
394
+ }
395
+
396
+ /**
397
+ * WHERE 原始片段(不做转义),可附带参数。
398
+ */
399
+ whereRaw(sql, params) {
400
+ const paramList = Array.isArray(params) ? params : [];
401
+ for (const param of paramList) {
402
+ validateParam(param);
403
+ }
404
+
405
+ this._model.where.items.push({ type: "raw", sql: sql, params: paramList });
406
+ return this;
407
+ }
408
+
409
+ /**
410
+ * LEFT JOIN
411
+ */
412
+ leftJoin(table, on) {
413
+ this._model.joins.push({ type: "left", table: table, on: on });
414
+ return this;
415
+ }
416
+
417
+ /**
418
+ * ORDER BY
419
+ * @param fields - 格式为 ["field#ASC", "field2#DESC"]
420
+ */
421
+ orderBy(fields) {
422
+ for (const item of fields) {
423
+ if (!isString(item) || !item.includes("#")) {
424
+ throw new Error(`orderBy 字段必须是 "字段#方向" 格式的字符串(例如:"name#ASC", "id#DESC") (item: ${String(item)})`, {
425
+ cause: null,
426
+ code: "validation"
427
+ });
428
+ }
429
+
430
+ const parts = item.split("#");
431
+ if (parts.length !== 2) {
432
+ throw new Error(`orderBy 字段必须是 "字段#方向" 格式的字符串(例如:"name#ASC", "id#DESC") (item: ${String(item)})`, {
433
+ cause: null,
434
+ code: "validation"
435
+ });
436
+ }
437
+
438
+ const cleanField = parts[0].trim();
439
+ const cleanDir = parts[1].trim().toUpperCase();
440
+
441
+ if (!cleanField) {
442
+ throw new Error(`orderBy 中字段名不能为空 (item: ${item})`, {
443
+ cause: null,
444
+ code: "validation"
445
+ });
446
+ }
447
+
448
+ if (!["ASC", "DESC"].includes(cleanDir)) {
449
+ throw new Error(`ORDER BY 方向必须是 ASC 或 DESC (direction: ${cleanDir})`, {
450
+ cause: null,
451
+ code: "validation"
452
+ });
453
+ }
454
+
455
+ this._model.orderBy.push({ field: cleanField, dir: cleanDir });
456
+ }
457
+ return this;
458
+ }
459
+
460
+ /**
461
+ * LIMIT
462
+ */
463
+ limit(count, offset) {
464
+ const result = normalizeLimitValue(count, offset);
465
+ this._model.limit = result.limitValue;
466
+ this._model.offset = result.offsetValue;
467
+ return this;
468
+ }
469
+
470
+ /**
471
+ * OFFSET
472
+ */
473
+ offset(count) {
474
+ const offsetValue = normalizeOffsetValue(count);
475
+ this._model.offset = offsetValue;
476
+ return this;
477
+ }
478
+
479
+ /**
480
+ * 构建 SELECT 查询
481
+ */
482
+ toSelectSql() {
483
+ const selectSql =
484
+ this._model.select.length > 0
485
+ ? this._model.select
486
+ .map((item) => {
487
+ if (item.type === "raw") {
488
+ return item.value;
489
+ }
490
+ return escapeField(item.value, this._quoteIdent);
491
+ })
492
+ .join(", ")
493
+ : "*";
494
+
495
+ const params = [];
496
+ const fromSql = this._model.from.type === "raw" ? this._model.from.value : escapeTable(this._model.from.value, this._quoteIdent);
497
+ let sql = `SELECT ${selectSql} FROM ${fromSql}`;
498
+
499
+ if (this._model.joins.length > 0) {
500
+ sql += ` ${this._model.joins.map((join) => `${join.type.toUpperCase()} JOIN ${escapeTable(join.table, this._quoteIdent)} ON ${join.on}`).join(" ")}`;
501
+ }
502
+
503
+ const whereResult = this._compileWhereNode(this._model.where);
504
+ if (whereResult.sql) {
505
+ sql += ` WHERE ${whereResult.sql}`;
506
+ for (const param of whereResult.params) {
507
+ params.push(param);
508
+ }
509
+ }
510
+
511
+ if (this._model.orderBy.length > 0) {
512
+ sql += ` ORDER BY ${this._model.orderBy.map((item) => `${escapeField(item.field, this._quoteIdent)} ${item.dir}`).join(", ")}`;
513
+ }
514
+
515
+ if (this._model.limit !== null) {
516
+ sql += ` LIMIT ${this._model.limit}`;
517
+ if (this._model.offset !== null) {
518
+ sql += ` OFFSET ${this._model.offset}`;
519
+ }
520
+ }
521
+
522
+ return { sql: sql, params: params };
523
+ }
524
+
525
+ /**
526
+ * 构建 INSERT 查询
527
+ */
528
+ toInsertSql(table, data) {
529
+ const escapedTable = escapeTable(table, this._quoteIdent);
530
+
531
+ if (Array.isArray(data)) {
532
+ const fields = assertBatchInsertRowsConsistent(data, { table: table });
533
+ const escapedFields = fields.map((field) => escapeField(field, this._quoteIdent));
534
+ const placeholders = fields.map(() => "?").join(", ");
535
+ const values = data.map(() => `(${placeholders})`).join(", ");
536
+ const params = [];
537
+
538
+ for (const row of data) {
539
+ for (const field of fields) {
540
+ const value = row[field];
541
+ validateParam(value);
542
+ params.push(value);
543
+ }
544
+ }
545
+
546
+ return {
547
+ sql: `INSERT INTO ${escapedTable} (${escapedFields.join(", ")}) VALUES ${values}`,
548
+ params: params
549
+ };
550
+ }
551
+
552
+ const fields = Object.keys(data);
553
+ if (fields.length === 0) {
554
+ throw new Error(`插入数据必须至少有一个字段 (table: ${table})`, {
555
+ cause: null,
556
+ code: "validation"
557
+ });
558
+ }
559
+
560
+ for (const field of fields) {
561
+ validateParam(data[field]);
562
+ }
563
+
564
+ const escapedFields = fields.map((field) => escapeField(field, this._quoteIdent));
565
+ const placeholders = fields.map(() => "?").join(", ");
566
+ const params = [];
567
+ for (const field of fields) {
568
+ params.push(data[field]);
569
+ }
570
+
571
+ return {
572
+ sql: `INSERT INTO ${escapedTable} (${escapedFields.join(", ")}) VALUES (${placeholders})`,
573
+ params: params
574
+ };
575
+ }
576
+
577
+ /**
578
+ * 构建 UPDATE 查询
579
+ */
580
+ toUpdateSql(table, data) {
581
+ const fields = Object.keys(data);
582
+ if (fields.length === 0) {
583
+ throw new Error("更新数据必须至少有一个字段", {
584
+ cause: null,
585
+ code: "validation"
586
+ });
587
+ }
588
+
589
+ const params = [];
590
+ for (const value of Object.values(data)) {
591
+ params.push(value);
592
+ }
593
+
594
+ const whereResult = this._compileWhereNode(this._model.where);
595
+ for (const param of whereResult.params) {
596
+ params.push(param);
597
+ }
598
+
599
+ return {
600
+ sql: `UPDATE ${escapeTable(table, this._quoteIdent)} SET ${fields.map((field) => `${escapeField(field, this._quoteIdent)} = ?`).join(", ")} WHERE ${whereResult.sql}`,
601
+ params: params
602
+ };
603
+ }
604
+
605
+ /**
606
+ * 构建 DELETE 查询
607
+ */
608
+ toDeleteSql(table) {
609
+ const whereResult = this._compileWhereNode(this._model.where);
610
+ return {
611
+ sql: `DELETE FROM ${escapeTable(table, this._quoteIdent)} WHERE ${whereResult.sql}`,
612
+ params: whereResult.params
613
+ };
614
+ }
615
+
616
+ /**
617
+ * 构建 COUNT 查询
618
+ */
619
+ toCountSql() {
620
+ const params = [];
621
+ const fromSql = this._model.from.type === "raw" ? this._model.from.value : escapeTable(this._model.from.value, this._quoteIdent);
622
+ let sql = `SELECT COUNT(*) as total FROM ${fromSql}`;
623
+
624
+ if (this._model.joins.length > 0) {
625
+ sql += ` ${this._model.joins.map((join) => `${join.type.toUpperCase()} JOIN ${escapeTable(join.table, this._quoteIdent)} ON ${join.on}`).join(" ")}`;
626
+ }
627
+
628
+ const whereResult = this._compileWhereNode(this._model.where);
629
+ if (whereResult.sql) {
630
+ sql += ` WHERE ${whereResult.sql}`;
631
+ for (const param of whereResult.params) {
632
+ params.push(param);
633
+ }
634
+ }
635
+
636
+ return { sql: sql, params: params };
637
+ }
638
+
639
+ static toDeleteInSql(options) {
640
+ if (options.ids.length === 0) {
641
+ return { sql: "", params: [] };
642
+ }
643
+
644
+ const placeholders = options.ids.map(() => "?").join(",");
645
+ const sql = `DELETE FROM ${options.quoteIdent(options.table)} WHERE ${options.quoteIdent(options.idField)} IN (${placeholders})`;
646
+ const params = [];
647
+ for (const id of options.ids) {
648
+ params.push(id);
649
+ }
650
+
651
+ return { sql: sql, params: params };
652
+ }
653
+
654
+ static toUpdateCaseByIdSql(options) {
655
+ if (options.rows.length === 0) {
656
+ return { sql: "", params: [] };
657
+ }
658
+ if (options.fields.length === 0) {
659
+ return { sql: "", params: [] };
660
+ }
661
+
662
+ const ids = options.rows.map((row) => row.id);
663
+ const placeholders = ids.map(() => "?").join(",");
664
+ const setSqlList = [];
665
+ const args = [];
666
+ const quotedId = options.quoteIdent(options.idField);
667
+
668
+ for (const field of options.fields) {
669
+ const whenList = [];
670
+
671
+ for (const row of options.rows) {
672
+ if (!(field in row.data)) {
673
+ continue;
674
+ }
675
+
676
+ whenList.push("WHEN ? THEN ?");
677
+ args.push(row.id);
678
+ const value = row.data[field];
679
+ assertNoUndefinedParam(value, "SQL 参数值");
680
+ args.push(value);
681
+ }
682
+
683
+ if (whenList.length === 0) {
684
+ continue;
685
+ }
686
+
687
+ const quotedField = options.quoteIdent(field);
688
+ setSqlList.push(`${quotedField} = CASE ${quotedId} ${whenList.join(" ")} ELSE ${quotedField} END`);
689
+ }
690
+
691
+ if (isNonEmptyString(options.updatedAtField)) {
692
+ setSqlList.push(`${options.quoteIdent(options.updatedAtField)} = ?`);
693
+ args.push(options.updatedAtValue);
694
+ }
695
+
696
+ for (const id of ids) {
697
+ args.push(id);
698
+ }
699
+
700
+ let sql = `UPDATE ${options.quoteIdent(options.table)} SET ${setSqlList.join(", ")} WHERE ${quotedId} IN (${placeholders})`;
701
+ if (options.stateGtZero && options.stateField) {
702
+ sql += ` AND ${options.quoteIdent(options.stateField)} > 0`;
703
+ }
704
+
705
+ return { sql: sql, params: args };
706
+ }
707
+ }
708
+
709
+ /**
710
+ * 创建新的 SQL 构建器实例
711
+ */
712
+ export function createQueryBuilder() {
713
+ return new SqlBuilder();
714
+ }