befly 3.21.1 → 3.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/dbUtil.js CHANGED
@@ -1,72 +1,6 @@
1
1
  import { isFiniteNumber, isNonEmptyString, isNullable, isString } from "../utils/is.js";
2
2
  import { snakeCase } from "../utils/util.js";
3
3
 
4
- export const SAFE_IDENTIFIER_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
5
-
6
- export function assertNoUndefinedParam(value, label) {
7
- if (value === undefined) {
8
- throw new Error(`${label} 不能为 undefined`, {
9
- cause: null,
10
- code: "validation"
11
- });
12
- }
13
- }
14
-
15
- export function assertSafeIdentifierPart(part, kind) {
16
- if (!SAFE_IDENTIFIER_RE.test(part)) {
17
- throw new Error(`无效的 ${kind} 标识符: ${part}`, {
18
- cause: null,
19
- code: "validation"
20
- });
21
- }
22
- }
23
-
24
- export function validateQuoteIdentifierInput(identifier) {
25
- if (!isString(identifier)) {
26
- throw new Error(`quoteIdent 需要字符串类型标识符 (identifier: ${String(identifier)})`, {
27
- cause: null,
28
- code: "validation"
29
- });
30
- }
31
-
32
- const trimmed = identifier.trim();
33
- if (!trimmed) {
34
- throw new Error("SQL 标识符不能为空", {
35
- cause: null,
36
- code: "validation"
37
- });
38
- }
39
-
40
- return trimmed;
41
- }
42
-
43
- export function validateLimitValue(count) {
44
- if (typeof count !== "number" || count < 0) {
45
- throw new Error(`LIMIT 数量必须是非负数 (count: ${String(count)})`, {
46
- cause: null,
47
- code: "validation"
48
- });
49
- }
50
- }
51
-
52
- export function validateOffsetValue(offset, label = "OFFSET") {
53
- if (typeof offset !== "number" || offset < 0) {
54
- throw new Error(`${label} 必须是非负数 (offset: ${String(offset)})`, {
55
- cause: null,
56
- code: "validation"
57
- });
58
- }
59
- }
60
-
61
- export function validateExcludeFieldsResult(resultFields, table, excludeSnakeFields) {
62
- if (resultFields.length === 0) {
63
- throw new Error(`排除字段后没有剩余字段可查询。表: ${table}, 排除: ${excludeSnakeFields.join(", ")}`, {
64
- cause: null,
65
- code: "validation"
66
- });
67
- }
68
- }
69
-
70
4
  export function assertNoExprField(field) {
71
5
  if (!isString(field)) {
72
6
  return;
@@ -84,93 +18,6 @@ export function assertNoExprField(field) {
84
18
  }
85
19
  }
86
20
 
87
- export function assertNoUndefinedInRecord(row, label) {
88
- for (const [key, value] of Object.entries(row)) {
89
- if (value === undefined) {
90
- throw new Error(`${label} 存在 undefined 字段值 (field: ${key})`, {
91
- cause: null,
92
- code: "validation"
93
- });
94
- }
95
- }
96
- }
97
-
98
- export function assertBatchInsertRowsConsistent(rows, options) {
99
- if (!Array.isArray(rows)) {
100
- throw new Error("批量插入 rows 必须是数组", {
101
- cause: null,
102
- code: "validation"
103
- });
104
- }
105
- if (rows.length === 0) {
106
- throw new Error(`插入数据不能为空 (table: ${options.table})`, {
107
- cause: null,
108
- code: "validation"
109
- });
110
- }
111
-
112
- const first = rows[0];
113
- if (!first || typeof first !== "object" || Array.isArray(first)) {
114
- throw new Error(`批量插入的每一行必须是对象 (table: ${options.table}, rowIndex: 0)`, {
115
- cause: null,
116
- code: "validation"
117
- });
118
- }
119
-
120
- const fields = Object.keys(first);
121
- if (fields.length === 0) {
122
- throw new Error(`插入数据必须至少有一个字段 (table: ${options.table})`, {
123
- cause: null,
124
- code: "validation"
125
- });
126
- }
127
-
128
- const fieldSet = new Set(fields);
129
- for (let i = 0; i < rows.length; i++) {
130
- const row = rows[i];
131
- if (!row || typeof row !== "object" || Array.isArray(row)) {
132
- throw new Error(`批量插入的每一行必须是对象 (table: ${options.table}, rowIndex: ${i})`, {
133
- cause: null,
134
- code: "validation"
135
- });
136
- }
137
-
138
- const rowKeys = Object.keys(row);
139
- if (rowKeys.length !== fields.length) {
140
- throw new Error(`批量插入每行字段必须一致 (table: ${options.table}, rowIndex: ${i})`, {
141
- cause: null,
142
- code: "validation"
143
- });
144
- }
145
-
146
- for (const key of rowKeys) {
147
- if (!fieldSet.has(key)) {
148
- throw new Error(`批量插入每行字段必须一致 (table: ${options.table}, rowIndex: ${i}, extraField: ${key})`, {
149
- cause: null,
150
- code: "validation"
151
- });
152
- }
153
- }
154
-
155
- for (const field of fields) {
156
- if (!(field in row)) {
157
- throw new Error(`批量插入缺少字段 (table: ${options.table}, rowIndex: ${i}, field: ${field})`, {
158
- cause: null,
159
- code: "validation"
160
- });
161
- }
162
- if (row[field] === undefined) {
163
- throw new Error(`批量插入字段值不能为 undefined (table: ${options.table}, rowIndex: ${i}, field: ${field})`, {
164
- cause: null,
165
- code: "validation"
166
- });
167
- }
168
- }
169
- }
170
-
171
- return fields;
172
- }
173
-
174
21
  export function parseTableRef(tableRef) {
175
22
  if (!isString(tableRef)) {
176
23
  throw new Error(`tableRef 必须是字符串 (tableRef: ${String(tableRef)})`, {
@@ -180,7 +27,7 @@ export function parseTableRef(tableRef) {
180
27
  }
181
28
 
182
29
  const trimmed = tableRef.trim();
183
- if (!isNonEmptyString(tableRef)) {
30
+ if (!trimmed) {
184
31
  throw new Error("tableRef 不能为空", {
185
32
  cause: null,
186
33
  code: "validation"
@@ -188,12 +35,6 @@ export function parseTableRef(tableRef) {
188
35
  }
189
36
 
190
37
  const parts = trimmed.split(/\s+/).filter((part) => part.length > 0);
191
- if (parts.length === 0) {
192
- throw new Error("tableRef 不能为空", {
193
- cause: null,
194
- code: "validation"
195
- });
196
- }
197
38
  if (parts.length > 2) {
198
39
  throw new Error(`不支持的表引用格式(包含过多片段)。请使用最简形式:table 或 table alias 或 schema.table 或 schema.table alias (tableRef: ${trimmed})`, {
199
40
  cause: null,
@@ -258,76 +99,6 @@ export function parseTableRef(tableRef) {
258
99
  return { schema: null, table: table, alias: aliasPart };
259
100
  }
260
101
 
261
- export function validateAndClassifyFields(fields) {
262
- if (!fields || fields.length === 0) {
263
- return { type: "all", fields: [] };
264
- }
265
-
266
- if (fields.some((field) => field === "*")) {
267
- throw new Error("fields 不支持 * 星号,请使用空数组 [] 或不传参数表示查询所有字段", {
268
- cause: null,
269
- code: "validation"
270
- });
271
- }
272
-
273
- if (fields.some((field) => !isNonEmptyString(field))) {
274
- throw new Error("fields 不能包含空字符串或无效值", {
275
- cause: null,
276
- code: "validation"
277
- });
278
- }
279
-
280
- for (const rawField of fields) {
281
- const checkField = rawField.startsWith("!") ? rawField.substring(1) : rawField;
282
- assertNoExprField(checkField);
283
- }
284
-
285
- const includeFields = fields.filter((field) => !field.startsWith("!"));
286
- const excludeFields = fields.filter((field) => field.startsWith("!"));
287
-
288
- if (includeFields.length > 0 && excludeFields.length === 0) {
289
- return { type: "include", fields: includeFields };
290
- }
291
- if (excludeFields.length > 0 && includeFields.length === 0) {
292
- return { type: "exclude", fields: excludeFields.map((field) => field.substring(1)) };
293
- }
294
-
295
- throw new Error('fields 不能同时包含普通字段和排除字段(! 开头)。只能使用以下3种方式之一:\n1. 空数组 [] 或不传(查询所有)\n2. 全部指定字段 ["id", "name"]\n3. 全部排除字段 ["!password", "!token"]', {
296
- cause: null,
297
- code: "validation"
298
- });
299
- }
300
-
301
- export function validateExplicitReadFields(fields) {
302
- const classified = validateAndClassifyFields(fields);
303
- if (classified.type === "all") {
304
- throw new Error("查询必须显式传 fields,不支持空 fields 或查询全部字段", {
305
- cause: null,
306
- code: "validation"
307
- });
308
- }
309
-
310
- return classified;
311
- }
312
-
313
- export function validateExplicitLeftJoinFields(fields) {
314
- const classified = validateAndClassifyFields(fields);
315
- if (classified.type === "all") {
316
- throw new Error("leftJoin 查询必须显式传 fields,不支持空 fields 或查询全部字段", {
317
- cause: null,
318
- code: "validation"
319
- });
320
- }
321
- if (classified.type === "exclude") {
322
- throw new Error("leftJoin 查询不支持排除字段,必须显式指定主表/从表字段", {
323
- cause: null,
324
- code: "validation"
325
- });
326
- }
327
-
328
- return classified;
329
- }
330
-
331
102
  function isQuotedIdentPaired(value) {
332
103
  const trimmed = value.trim();
333
104
  if (trimmed.length < 2) {
@@ -344,52 +115,53 @@ function startsWithIdentifierQuote(value) {
344
115
  return trimmed.startsWith("`") || trimmed.startsWith('"');
345
116
  }
346
117
 
347
- function assertPairedQuotedIdent(value, label) {
348
- if (startsWithIdentifierQuote(value) && !isQuotedIdentPaired(value)) {
349
- throw new Error(`${label} 引用不完整,请使用成对的 \`...\` 或 "..." (value: ${value})`, {
350
- cause: null,
351
- code: "validation"
352
- });
118
+ export function resolveQuoteIdent(options) {
119
+ if (options && options.quoteIdent) {
120
+ return options.quoteIdent;
353
121
  }
122
+
123
+ return (identifier) => {
124
+ const trimmed = isString(identifier) ? identifier.trim() : String(identifier).trim();
125
+ const escaped = trimmed.replace(/`/g, "``");
126
+ return `\`${escaped}\``;
127
+ };
354
128
  }
355
129
 
356
- function escapeIdentifierPart(part, kind, quoteIdent, unpairedErrorFactory) {
357
- if (isQuotedIdent(part)) {
358
- return part;
130
+ export function parseFieldAliasParts(field) {
131
+ if (!isString(field)) {
132
+ return null;
133
+ }
134
+
135
+ const trimmed = field.trim();
136
+ if (!trimmed) {
137
+ return null;
359
138
  }
360
139
 
361
- if (startsWithQuote(part)) {
362
- throw new Error(unpairedErrorFactory(part), {
140
+ if (/\s+AS\s+/i.test(trimmed)) {
141
+ throw new Error(`字段别名不支持 AS,请使用单个空格分隔字段与别名 (field: ${trimmed})`, {
363
142
  cause: null,
364
143
  code: "validation"
365
144
  });
366
145
  }
367
146
 
368
- assertSafeIdentifierPart(part, kind);
369
- return quoteIdent(part);
370
- }
371
-
372
- export function resolveQuoteIdent(options) {
373
- if (options && options.quoteIdent) {
374
- return options.quoteIdent;
147
+ if (!/\s/.test(trimmed)) {
148
+ return null;
375
149
  }
376
150
 
377
- return (identifier) => {
378
- const trimmed = validateQuoteIdentifierInput(identifier);
151
+ if (!/^[^\s]+ [^\s]+$/.test(trimmed)) {
152
+ throw new Error(`字段别名必须使用单个空格分隔字段与别名 (field: ${trimmed})`, {
153
+ cause: null,
154
+ code: "validation"
155
+ });
156
+ }
379
157
 
380
- const escaped = trimmed.replace(/`/g, "``");
381
- return `\`${escaped}\``;
158
+ const parts = trimmed.split(" ");
159
+ return {
160
+ fieldPart: parts[0],
161
+ aliasPart: parts[1]
382
162
  };
383
163
  }
384
164
 
385
- export function isQuotedIdent(value) {
386
- return isQuotedIdentPaired(value);
387
- }
388
-
389
- export function startsWithQuote(value) {
390
- return startsWithIdentifierQuote(value);
391
- }
392
-
393
165
  export function escapeField(field, quoteIdent) {
394
166
  if (!isString(field)) {
395
167
  return field;
@@ -397,43 +169,25 @@ export function escapeField(field, quoteIdent) {
397
169
 
398
170
  const trimmed = field.trim();
399
171
 
400
- assertPairedQuotedIdent(trimmed, "字段标识符");
172
+ if (!trimmed) {
173
+ return trimmed;
174
+ }
401
175
 
402
- if (trimmed === "*" || isQuotedIdent(trimmed)) {
176
+ if (trimmed === "*" || isQuotedIdentPaired(trimmed)) {
403
177
  return trimmed;
404
178
  }
405
179
 
406
- try {
407
- assertNoExprField(trimmed);
408
- } catch {
409
- throw new Error(`字段包含函数/表达式,请使用 selectRaw/whereRaw (field: ${trimmed})`, {
410
- cause: null,
411
- code: "validation"
412
- });
180
+ if (startsWithIdentifierQuote(trimmed) && !isQuotedIdentPaired(trimmed) && !trimmed.includes(".")) {
181
+ return trimmed;
413
182
  }
414
183
 
415
- if (trimmed.toUpperCase().includes(" AS ")) {
416
- const parts = trimmed.split(/\s+AS\s+/i);
417
- if (parts.length !== 2) {
418
- throw new Error(`字段格式非法,请使用简单字段名或安全引用,复杂表达式请使用 selectRaw/whereRaw (field: ${trimmed})`, {
419
- cause: null,
420
- code: "validation"
421
- });
422
- }
184
+ if (trimmed.includes("(") || trimmed.includes(")") || /\s+AS\s+/i.test(trimmed)) {
185
+ return trimmed;
186
+ }
423
187
 
424
- const fieldPart = parts[0];
425
- const aliasPart = parts[1];
426
- const cleanFieldPart = fieldPart.trim();
427
- const cleanAliasPart = aliasPart.trim();
428
- if (!isQuotedIdent(cleanAliasPart)) {
429
- if (!SAFE_IDENTIFIER_RE.test(cleanAliasPart)) {
430
- throw new Error(`无效的字段别名: ${cleanAliasPart}`, {
431
- cause: null,
432
- code: "validation"
433
- });
434
- }
435
- }
436
- return `${escapeField(cleanFieldPart, quoteIdent)} AS ${cleanAliasPart}`;
188
+ const aliasMatch = trimmed.match(/^([^\s]+)\s+([^\s]+)$/);
189
+ if (aliasMatch) {
190
+ return `${escapeField(aliasMatch[1].trim(), quoteIdent)} ${aliasMatch[2].trim()}`;
437
191
  }
438
192
 
439
193
  if (trimmed.includes(".")) {
@@ -441,10 +195,12 @@ export function escapeField(field, quoteIdent) {
441
195
  return parts
442
196
  .map((part) => {
443
197
  const cleanPart = part.trim();
444
- if (cleanPart === "*" || isQuotedIdent(cleanPart)) {
198
+ if (!cleanPart) {
199
+ return cleanPart;
200
+ }
201
+ if (cleanPart === "*" || isQuotedIdentPaired(cleanPart) || startsWithIdentifierQuote(cleanPart)) {
445
202
  return cleanPart;
446
203
  }
447
- assertPairedQuotedIdent(cleanPart, "字段标识符");
448
204
  return quoteIdent(cleanPart);
449
205
  })
450
206
  .join(".");
@@ -460,129 +216,51 @@ export function escapeTable(table, quoteIdent) {
460
216
 
461
217
  const trimmed = table.trim();
462
218
 
463
- if (isQuotedIdent(trimmed)) {
219
+ if (!trimmed) {
464
220
  return trimmed;
465
221
  }
466
222
 
467
- const parts = trimmed.split(/\s+/).filter((p) => p.length > 0);
468
- if (parts.length === 0) {
469
- throw new Error("FROM 表名不能为空", {
470
- cause: null,
471
- code: "validation"
472
- });
223
+ if (isQuotedIdentPaired(trimmed)) {
224
+ return trimmed;
473
225
  }
474
226
 
227
+ if (startsWithIdentifierQuote(trimmed) && !isQuotedIdentPaired(trimmed) && !trimmed.includes(".")) {
228
+ return trimmed;
229
+ }
230
+
231
+ const parts = trimmed.split(/\s+/).filter((p) => p.length > 0);
475
232
  if (parts.length > 2) {
476
- throw new Error(`不支持的表引用格式(包含过多片段)。请使用 fromRaw 显式传入复杂表达式 (table: ${trimmed})`, {
477
- cause: null,
478
- code: "validation"
479
- });
233
+ return trimmed;
480
234
  }
481
235
 
482
236
  const namePart = parts[0].trim();
483
237
 
484
- const aliasPart = parts.length === 2 ? parts[1].trim() : null;
485
-
486
- const nameSegments = namePart.split(".");
487
- if (nameSegments.length > 2) {
488
- throw new Error(`不支持的表引用格式(schema 层级过深)。请使用 fromRaw (table: ${trimmed})`, {
489
- cause: null,
490
- code: "validation"
491
- });
238
+ if (namePart.split(".").some((part) => startsWithIdentifierQuote(part.trim()) && !isQuotedIdentPaired(part.trim()))) {
239
+ return trimmed;
492
240
  }
493
241
 
494
- let escapedName = "";
495
- if (nameSegments.length === 2) {
496
- const schemaRaw = nameSegments[0];
497
- const tableNameRaw = nameSegments[1];
498
- const schema = schemaRaw.trim();
499
- const tableName = tableNameRaw.trim();
500
-
501
- const escapedSchema = escapeIdentifierPart(schema, "schema", quoteIdent, (part) => `schema 标识符引用不完整,请使用成对的 \`...\` 或 "..." (schema: ${part})`);
502
- const escapedTableName = escapeIdentifierPart(tableName, "table", quoteIdent, (part) => `table 标识符引用不完整,请使用成对的 \`...\` 或 "..." (table: ${part})`);
503
-
504
- escapedName = `${escapedSchema}.${escapedTableName}`;
505
- } else {
506
- const tableNameRaw = nameSegments[0];
507
- const tableName = tableNameRaw.trim();
242
+ const aliasPart = parts.length === 2 ? parts[1].trim() : null;
508
243
 
509
- escapedName = escapeIdentifierPart(tableName, "table", quoteIdent, (part) => `table 标识符引用不完整,请使用成对的 \`...\` 或 "..." (table: ${part})`);
510
- }
244
+ const nameSegments = namePart.split(".");
245
+ const escapedName = nameSegments
246
+ .map((part) => {
247
+ const cleanPart = part.trim();
248
+ if (!cleanPart) {
249
+ return cleanPart;
250
+ }
251
+ if (isQuotedIdentPaired(cleanPart) || startsWithIdentifierQuote(cleanPart)) {
252
+ return cleanPart;
253
+ }
254
+ return quoteIdent(cleanPart);
255
+ })
256
+ .join(".");
511
257
 
512
258
  if (aliasPart) {
513
- assertSafeIdentifierPart(aliasPart, "alias");
514
259
  return `${escapedName} ${aliasPart}`;
515
260
  }
516
261
 
517
262
  return escapedName;
518
263
  }
519
-
520
- export function validateParam(value) {
521
- assertNoUndefinedParam(value, "SQL 参数值");
522
- }
523
-
524
- export function normalizeLimitValue(count, offset) {
525
- validateLimitValue(count);
526
- const limitValue = Math.floor(count);
527
-
528
- let offsetValue = null;
529
- if (!isNullable(offset)) {
530
- validateOffsetValue(offset);
531
- offsetValue = Math.floor(offset);
532
- }
533
-
534
- return { limitValue: limitValue, offsetValue: offsetValue };
535
- }
536
-
537
- export function normalizeOffsetValue(count) {
538
- validateOffsetValue(count, "OFFSET");
539
- return Math.floor(count);
540
- }
541
-
542
- export function toSqlParams(params) {
543
- if (isNullable(params)) {
544
- return [];
545
- }
546
-
547
- if (!Array.isArray(params)) {
548
- throw new Error(`SQL 参数必须是数组,当前类型: ${typeof params}`, {
549
- cause: null,
550
- code: "validation"
551
- });
552
- }
553
-
554
- const out = [];
555
- for (const value of params) {
556
- if (value === undefined) {
557
- throw new Error("SQL 参数不能为 undefined", {
558
- cause: null,
559
- code: "validation"
560
- });
561
- }
562
-
563
- if (typeof value === "bigint") {
564
- out.push(value.toString());
565
- continue;
566
- }
567
-
568
- out.push(value);
569
- }
570
-
571
- return out;
572
- }
573
-
574
- function normalizeTableRef(tableRef) {
575
- const parsed = parseTableRef(tableRef);
576
- const schemaPart = parsed.schema ? snakeCase(parsed.schema) : null;
577
- const tablePart = snakeCase(parsed.table);
578
- const aliasPart = parsed.alias ? snakeCase(parsed.alias) : null;
579
- let result = schemaPart ? `${schemaPart}.${tablePart}` : tablePart;
580
- if (aliasPart) {
581
- result = `${result} ${aliasPart}`;
582
- }
583
- return result;
584
- }
585
-
586
264
  function shouldExcludeFieldValue(field, value, excludeValueMap) {
587
265
  if (!excludeValueMap || excludeValueMap.size === 0) {
588
266
  return false;
@@ -724,13 +402,18 @@ export async function fieldsToSnake(table, classified, getTableColumns) {
724
402
  const allColumns = await getTableColumns(table);
725
403
  const excludeSnakeFields = classified.fields.map((field) => snakeCase(field));
726
404
  const resultFields = allColumns.filter((column) => !excludeSnakeFields.includes(column));
727
- validateExcludeFieldsResult(resultFields, table, excludeSnakeFields);
405
+ if (resultFields.length === 0) {
406
+ throw new Error(`排除字段后没有剩余字段可查询。表: ${table}, 排除: ${excludeSnakeFields.join(", ")}`, {
407
+ cause: null,
408
+ code: "validation"
409
+ });
410
+ }
728
411
  return resultFields;
729
412
  }
730
413
  return ["*"];
731
414
  }
732
415
 
733
- function normalizeQualifierField(field) {
416
+ export function normalizeQualifierField(field) {
734
417
  const parts = field.split(".");
735
418
  if (parts.length === 1) {
736
419
  return snakeCase(field);
@@ -805,14 +488,9 @@ export function processJoinField(field) {
805
488
  return field;
806
489
  }
807
490
 
808
- if (field.toUpperCase().includes(" AS ")) {
809
- const parts = field.split(/\s+AS\s+/i);
810
- const fieldPart = parts[0];
811
- const aliasPart = parts[1];
812
- if (!isString(fieldPart) || !isString(aliasPart)) {
813
- return field;
814
- }
815
- return `${processJoinField(fieldPart.trim())} AS ${aliasPart.trim()}`;
491
+ const aliasParts = parseFieldAliasParts(field);
492
+ if (aliasParts) {
493
+ return `${processJoinField(aliasParts.fieldPart.trim())} ${aliasParts.aliasPart.trim()}`;
816
494
  }
817
495
 
818
496
  return normalizeQualifierField(field);
@@ -896,39 +574,6 @@ export function processJoinOn(on) {
896
574
  return result;
897
575
  }
898
576
 
899
- export function parseLeftJoinItem(joinTable, joinItem) {
900
- const parts = joinItem.split(" ");
901
- return {
902
- type: "left",
903
- table: normalizeTableRef(joinTable),
904
- on: processJoinOn(`${parts[0]} = ${parts[1]}`)
905
- };
906
- }
907
-
908
- export function addDefaultStateFilter(where = {}, table, hasLeftJoin = false, beflyMode = "auto") {
909
- if (beflyMode === "manual") {
910
- return where;
911
- }
912
-
913
- const hasStateCondition = Object.keys(where).some((key) => key.startsWith("state") || key.includes(".state"));
914
- if (hasStateCondition) {
915
- return where;
916
- }
917
-
918
- const result = {};
919
- for (const [key, value] of Object.entries(where)) {
920
- result[key] = value;
921
- }
922
-
923
- if (hasLeftJoin && table) {
924
- result[`${table}.state$gt`] = 0;
925
- return result;
926
- }
927
-
928
- result.state$gt = 0;
929
- return result;
930
- }
931
-
932
577
  export function whereKeysToSnake(where) {
933
578
  return mapWhereKeys(where, (key) => {
934
579
  assertNoExprField(key);