@carbonorm/carbonnode 3.0.13 → 3.0.15

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.
@@ -5,7 +5,7 @@ import {iRestMethods} from "../types/ormInterfaces";
5
5
 
6
6
  interface QueryResult {
7
7
  sql: string;
8
- params: any[];
8
+ params: any[] | { [key: string]: any }; // params can be an array or an object for named placeholders
9
9
  }
10
10
 
11
11
  export abstract class SqlBuilder<
@@ -23,201 +23,432 @@ export abstract class SqlBuilder<
23
23
  CustomAndRequiredFields,
24
24
  RequestTableOverrides
25
25
  > {
26
- /** Generate nested WHERE/JOIN conditions with parameter binding */
26
+ /** Flag to determine if named placeholders should be used */
27
+ protected useNamedParams: boolean = false;
28
+
29
+ private convertHexIfBinary(col: string, val: any): any {
30
+ const columnDef = this.config.C6.TABLES[this.config.restModel.TABLE_NAME].TYPE_VALIDATION?.[col];
31
+
32
+ if (
33
+ typeof val === 'string' &&
34
+ /^[0-9a-fA-F]{32}$/.test(val) &&
35
+ typeof columnDef === 'object' &&
36
+ columnDef.MYSQL_TYPE.toUpperCase().includes('BINARY')
37
+ ) {
38
+ return Buffer.from(val, 'hex');
39
+ }
40
+ return val;
41
+ }
42
+
43
+
44
+ /** Generate nested WHERE/ON conditions with parameter binding (supports AND/OR/NOT) */
27
45
  protected buildBooleanJoinedConditions(
28
46
  set: any,
29
- andMode = true,
30
- params: any[] = []
47
+ andMode: boolean = true,
48
+ params: any[] | { [key: string]: any } = []
31
49
  ): string {
32
50
  const booleanOperator = andMode ? 'AND' : 'OR';
33
- const OPERATORS = ['=', '!=', '<', '<=', '>', '>=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN', 'IS', 'IS NOT'];
51
+ const OPERATORS = [
52
+ '=', '!=', '<', '<=', '>', '>=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN', 'IS', 'IS NOT',
53
+ 'BETWEEN', 'NOT BETWEEN', C6Constants.MATCH_AGAINST];
34
54
 
35
- const isAggregateArray = (value: any) =>
36
- Array.isArray(value) && typeof value[0] === 'string' && OPERATORS.includes(value[0]);
37
55
  const isNumericKeyed = (obj: any) =>
38
56
  Array.isArray(obj) && Object.keys(obj).every(k => /^\d+$/.test(k));
39
57
 
58
+ // Helper to add a single condition with proper parameter binding
40
59
  const addCondition = (column: string, op: string, value: any): string => {
41
60
  let clause: string;
42
- /*if (Buffer.isBuffer(value)) { // TODO - I want this as a parameterized option, for now default to faster
43
- params.push(value.toString('hex')); // Or use UNHEX(?) in SQL
44
- clause = `(${column} = UNHEX(?))`;
45
- } else {*/
46
- params.push(value);
47
- clause = `( ${column} ${op} ? )`;
48
- //}
49
- isVerbose() && console.log(`[WHERE] ➕ ${clause} ->`, value);
61
+ // If using named parameters, generate a unique key; otherwise use positional placeholder
62
+ const addParam = (val: any): string => {
63
+ if (Array.isArray(params)) {
64
+ // Positional (unnamed) parameter
65
+ params.push(this.convertHexIfBinary(column, val));
66
+ return '?';
67
+ } else {
68
+ // Named parameter mode
69
+ const key = `param${Object.keys(params).length}`;
70
+ (params as { [key: string]: any })[key] = this.convertHexIfBinary(column, val);
71
+ return `:${key}`;
72
+ }
73
+ };
74
+
75
+ if ((op === 'IN' || op === 'NOT IN') && Array.isArray(value)) {
76
+ if (value.length === 0) {
77
+ // Edge case: empty IN list - use a condition that's always false
78
+ clause = `( ${column} ${op} (NULL) )`;
79
+ isVerbose() && console.warn(`[WHERE] Empty list for ${op} on ${column}`);
80
+ } else {
81
+ const placeholders = value.map(val => addParam(val)).join(', ');
82
+ clause = `( ${column} ${op} (${placeholders}) )`;
83
+ }
84
+ } else if ((op === 'BETWEEN' || op === 'NOT BETWEEN') && Array.isArray(value) && value.length === 2) {
85
+ // BETWEEN expects exactly two values [min, max]
86
+ const [minVal, maxVal] = value;
87
+ const ph1 = addParam(minVal);
88
+ const ph2 = addParam(maxVal);
89
+ clause = `( ${column} ${op} ${ph1} AND ${ph2} )`;
90
+ } else {
91
+ // Default case for single-value operators (including IS, IS NOT, =, etc.)
92
+ // Note: If value is null and op is 'IS' or 'IS NOT', the placeholder will safely bind null.
93
+ const ph = addParam(value);
94
+ clause = `( ${column} ${op} ${ph} )`;
95
+ }
96
+ isVerbose() && console.log(`[WHERE] ➕ ${clause}`);
50
97
  return clause;
51
98
  };
52
99
 
53
100
  let sql: string;
54
101
  if (isNumericKeyed(set)) {
55
- isVerbose() && console.log(`[WHERE] Numeric keyed condition:`, set);
56
- switch (set.length) {
57
- case 2:
58
- sql = addCondition(set[0], '=', set[1]);
59
- break;
60
- case 3:
61
- if (!OPERATORS.includes(set[1])) throw new Error(`Invalid operator: ${set[1]}`);
62
- sql = addCondition(set[0], set[1], set[2]);
63
- break;
64
- default:
65
- throw new Error(`Invalid array condition: ${JSON.stringify(set)}`);
102
+ // Array of conditions, use opposite boolean mode for combining (AND/OR swap)
103
+ isVerbose() && console.log(`[WHERE] Grouping conditions (array) in ${andMode ? 'AND' : 'OR'} mode:`, set);
104
+ if (set.length === 0) {
105
+ sql = ''; // no conditions
106
+ } else if (set.length === 1) {
107
+ // Single element array, just build it normally (no boolean operator needed)
108
+ sql = this.buildBooleanJoinedConditions(set[0], true, params);
109
+ } else {
110
+ // Multiple conditions: join them with the opposite of current mode
111
+ const subClauses = set.map(item => this.buildBooleanJoinedConditions(item, true, params));
112
+ const op = andMode ? 'OR' : 'AND';
113
+ sql = subClauses.map(sc => `(${sc})`).join(` ${op} `);
66
114
  }
67
- } else {
115
+ } else if (typeof set === 'object') {
68
116
  const parts: string[] = [];
69
117
  for (const [key, value] of Object.entries(set)) {
70
118
  if (/^\d+$/.test(key)) {
119
+ // Numeric object keys (should not happen because numeric arrays handled above)
71
120
  parts.push(this.buildBooleanJoinedConditions(value, !andMode, params));
72
- } else if (!Array.isArray(value) || isAggregateArray(value)) {
73
- parts.push(addCondition(key, '=', value));
121
+ } else if (key.toUpperCase() === 'NOT') {
122
+ // NOT operator: negate the inner condition(s)
123
+ const negated = this.buildBooleanJoinedConditions(value, true, params);
124
+ parts.push(`NOT (${negated})`);
125
+ isVerbose() && console.log(`[WHERE] NOT block: (${negated})`);
126
+ } else if (!Array.isArray(value)) {
127
+ if (typeof value === 'object' && value !== null && Object.keys(value).length === 1) {
128
+ const [op, val] = Object.entries(value)[0];
129
+ if (OPERATORS.includes(op.toUpperCase())) {
130
+
131
+ if (
132
+ value !== null &&
133
+ C6Constants.MATCH_AGAINST in value &&
134
+ Array.isArray(value[C6Constants.MATCH_AGAINST])
135
+ ) {
136
+ const [search, mode] = value[C6Constants.MATCH_AGAINST];
137
+ const paramName = `param${Object.keys(params).length}`;
138
+ params[paramName] = search;
139
+
140
+ let againstClause: string;
141
+
142
+ switch (mode?.toUpperCase()) {
143
+ case 'BOOLEAN':
144
+ againstClause = `AGAINST(:${paramName} IN BOOLEAN MODE)`;
145
+ break;
146
+ case 'WITH QUERY EXPANSION':
147
+ againstClause = `AGAINST(:${paramName} WITH QUERY EXPANSION)`;
148
+ break;
149
+ case 'NATURAL':
150
+ case undefined:
151
+ case null:
152
+ againstClause = `AGAINST(:${paramName})`; // default natural language mode
153
+ break;
154
+ default:
155
+ throw new Error(`Unsupported MATCH_AGAINST mode: ${mode}`);
156
+ }
157
+
158
+ parts.push(`(MATCH(${key}) ${againstClause})`);
159
+ continue;
160
+ }
161
+
162
+ parts.push(addCondition(key, op.toUpperCase(), val));
163
+
164
+ } else {
165
+ throw new Error(`Unsupported operator '${op}' for key '${key}'`);
166
+ }
167
+ } else {
168
+ // Fallback: treat as simple equality
169
+ parts.push(addCondition(key, '=', value));
170
+ }
74
171
  } else if (value.length === 2 && OPERATORS.includes(value[0])) {
172
+ // Two-element array, first is an operator (like ['>', 5])
75
173
  parts.push(addCondition(key, value[0], value[1]));
174
+ } else if (value.length === 3 && OPERATORS.includes(value[0])) {
175
+ // Three-element array, e.g. ['BETWEEN', min, max]
176
+ parts.push(addCondition(key, value[0], value.slice(1)));
76
177
  } else {
77
178
  throw new Error(`Invalid condition for ${key}: ${JSON.stringify(value)}`);
78
179
  }
79
180
  }
80
- sql = parts.join(` ${booleanOperator} `);
181
+ sql = parts.length ? parts.join(` ${booleanOperator} `) : '';
182
+ } else {
183
+ // Primitive type (should not happen normally), treat it as a boolean expression string
184
+ sql = String(set);
81
185
  }
82
186
 
83
- isVerbose() && console.log(`[WHERE] Final: (${sql})`);
84
- return `(${sql})`;
187
+ // Wrap the final combined condition in parentheses to maintain correct precedence
188
+ return sql ? `(${sql})` : '';
85
189
  }
86
190
 
87
- /** Translate array or function calls into SQL expressions */
191
+ /** Translate array or function call definitions into SQL expressions (supports nested calls) */
88
192
  protected buildAggregateField(field: string | any[]): string {
89
- if (typeof field === 'string') return field;
90
- if (!Array.isArray(field)) throw new Error('Invalid SELECT entry');
193
+ if (typeof field === 'string') {
194
+ return field;
195
+ }
196
+ if (!Array.isArray(field) || field.length === 0) {
197
+ throw new Error('Invalid SELECT field entry');
198
+ }
91
199
 
200
+ // e.g. field = [ 'ROUND', [ 'SQRT', 'age' ], 2, 'AS', 'roundedRootAge' ]
92
201
  let [fn, ...args] = field;
93
202
  let alias: string | undefined;
94
- if (args.length >= 2 && args[args.length - 2] === 'AS') {
95
- alias = String(args.pop());
96
- args.pop();
203
+ // Handle alias if present in array (e.g. ... 'AS', 'aliasName')
204
+ if (args.length >= 2 && String(args[args.length - 2]).toUpperCase() === 'AS') {
205
+ alias = String(args.pop()); // last element is alias name
206
+ args.pop(); // remove the 'AS'
97
207
  }
208
+
98
209
  const F = String(fn).toUpperCase();
99
- const p = args.join(', ');
210
+ // Map each argument to SQL string, handling nested function arrays recursively
211
+ const argList = args.map(arg => {
212
+ if (Array.isArray(arg)) {
213
+ return this.buildAggregateField(arg);
214
+ } else {
215
+ return arg;
216
+ }
217
+ }).join(', ');
100
218
 
101
- isVerbose() && console.log(`[SELECT] ${F}(${p})${alias ? ` AS ${alias}` : ''}`);
219
+ let expr: string;
102
220
  switch (F) {
103
221
  case 'DATE_ADD':
104
- return `DATE_ADD(${args[0]}, ${args[1]})${alias ? ` AS ${alias}` : ''}`;
105
222
  case 'DATE_SUB':
106
- return `DATE_SUB(${args[0]}, ${args[1]})${alias ? ` AS ${alias}` : ''}`;
223
+ // Functions with comma-separated interval, assume args like [ date, interval_expression ]
224
+ expr = `${F}(${argList})`;
225
+ break;
107
226
  case 'YEAR':
108
- return `YEAR(${args[0]})${alias ? ` AS ${alias}` : ''}`;
109
227
  case 'MONTH':
110
- return `MONTH(${args[0]})${alias ? ` AS ${alias}` : ''}`;
111
228
  case 'DAY':
112
- return `DAY(${args[0]})${alias ? ` AS ${alias}` : ''}`;
113
229
  case 'ROUND':
114
230
  case 'CEIL':
115
231
  case 'FLOOR':
116
232
  case 'ABS':
117
233
  case 'SQRT':
118
- return `${F}(${p})${alias ? ` AS ${alias}` : ''}`;
234
+ case 'UPPER':
235
+ case 'LOWER':
236
+ case 'COALESCE':
237
+ case 'CONCAT':
238
+ case 'IFNULL':
239
+ case 'IF':
240
+ case 'COUNT':
241
+ case 'SUM':
242
+ case 'MIN':
243
+ case 'MAX':
244
+ case 'AVG':
245
+ // Common SQL functions – just format as F(arg1, arg2, ...)
246
+ expr = `${F}(${argList})`;
247
+ break;
119
248
  case 'ST_DISTANCE':
120
- return `ST_Distance(${p})${alias ? ` AS ${alias}` : ''}`;
249
+ expr = `ST_Distance(${argList})`;
250
+ break;
121
251
  default:
122
- if (/^[A-Z_]+$/.test(F)) return `${F}(${p})${alias ? ` AS ${alias}` : ''}`;
123
- throw new Error(`Unsupported function: ${F}`);
252
+ if (/^[A-Z_]+$/.test(F)) {
253
+ // Allow any other SQL function by name (assuming it's a valid function)
254
+ expr = `${F}(${argList})`;
255
+ } else {
256
+ throw new Error(`Unsupported function: ${F}`);
257
+ }
258
+ }
259
+ if (alias) {
260
+ expr += ` AS ${alias}`;
261
+ }
262
+ isVerbose() && console.log(`[SELECT] ${expr}`);
263
+ return expr;
264
+ }
265
+
266
+
267
+ private buildJoinClauses(
268
+ joinArgs: any,
269
+ params: any[] | { [key: string]: any }
270
+ ): string {
271
+ let sql = '';
272
+ for (const joinType in joinArgs) {
273
+ const jk = joinType.replace('_', ' ').toUpperCase();
274
+ for (const joinTable in joinArgs[joinType]) {
275
+ const onConditions = joinArgs[joinType][joinTable];
276
+ const onClause = this.buildBooleanJoinedConditions(onConditions, true, params);
277
+ sql += ` ${jk} JOIN \`${joinTable}\` ON ${onClause}`;
278
+ isVerbose() && console.log(`[JOIN] ${jk} JOIN ${joinTable} ON ${onClause}`);
279
+ }
280
+ }
281
+ return sql;
282
+ }
283
+
284
+ private buildPaginationClause(
285
+ pagination: any,
286
+ _params: any[] | { [key: string]: any }
287
+ ): string {
288
+ let sql = '';
289
+ if (pagination?.[C6Constants.ORDER]) {
290
+ const orderParts = Object.entries(pagination[C6Constants.ORDER])
291
+ .map(([col, dir]) => `${col} ${String(dir).toUpperCase()}`);
292
+ sql += ` ORDER BY ${orderParts.join(', ')}`;
293
+ isVerbose() && console.log(`[ORDER BY] ${orderParts}`);
294
+ }
295
+
296
+ if (pagination?.[C6Constants.LIMIT] != null) {
297
+ const lim = parseInt(pagination[C6Constants.LIMIT], 10);
298
+ const page = parseInt(pagination[C6Constants.PAGE] ?? 1, 10);
299
+ const offset = (page - 1) * lim;
300
+ sql += ` LIMIT ${offset}, ${lim}`;
301
+ isVerbose() && console.log(`[LIMIT] ${offset}, ${lim}`);
302
+ }
303
+
304
+ return sql;
305
+ }
306
+
307
+ private buildWhereClause(
308
+ whereArg: any,
309
+ params: any[] | { [key: string]: any }
310
+ ): string {
311
+ const whereClause = this.buildBooleanJoinedConditions(whereArg, true, params);
312
+ if (whereClause) {
313
+ const trimmed = whereClause.replace(/^\((.*)\)$/, '$1');
314
+ isVerbose() && console.log(`[WHERE] ${trimmed}`);
315
+ return ` WHERE ${trimmed}`;
124
316
  }
317
+ return '';
125
318
  }
126
319
 
127
- /** Compose a parameterized SELECT query with optional JOIN/WHERE/GROUP/HAVING/PAGINATION */
128
320
  protected buildSelectQuery<RestShortTableNames>(
129
321
  table: RestShortTableNames,
130
322
  primary: string | undefined,
131
323
  args: any,
132
- isSubSelect = false
324
+ isSubSelect: boolean = false
133
325
  ): QueryResult {
134
326
  const model = this.config.C6.TABLES[table as string];
135
- const params: any[] = [];
327
+ const params = this.useNamedParams ? {} : [];
136
328
 
137
- // SELECT
138
329
  const selectList = args?.[C6Constants.SELECT] ?? ['*'];
139
330
  const selectFields = Array.isArray(selectList)
140
331
  ? selectList.map(f => this.buildAggregateField(f)).join(', ')
141
- : '*';
332
+ : String(selectList);
142
333
  let sql = `SELECT ${selectFields} FROM ${table}`;
143
- isVerbose() && console.log(`[SELECT]`, selectFields);
144
334
 
145
- // JOIN
146
335
  if (args?.[C6Constants.JOIN]) {
147
- for (const jt in args[C6Constants.JOIN]) {
148
- const jk = jt.replace('_', ' ').toUpperCase();
149
- for (const jn in args[C6Constants.JOIN][jt]) {
150
- const on = this.buildBooleanJoinedConditions(args[C6Constants.JOIN][jt][jn], true, params);
151
- sql += ` ${jk} JOIN \`${jn}\` ON ${on}`;
152
- isVerbose() && console.log(`[JOIN]`, jk, jn, on);
153
- }
154
- }
336
+ sql += this.buildJoinClauses(args[C6Constants.JOIN], params);
155
337
  }
156
338
 
157
- // WHERE
158
339
  if (args?.[C6Constants.WHERE]) {
159
- let wc = this.buildBooleanJoinedConditions(args[C6Constants.WHERE], true, params);
160
- // Trim leading and trailing parentheses if they fully wrap the condition
161
- while (wc.startsWith('(') && wc.endsWith(')')) {
162
- wc = wc.slice(1, -1).trim();
163
- }
164
- sql += ` WHERE ${wc}`;
340
+ sql += this.buildWhereClause(args[C6Constants.WHERE], params);
165
341
  }
166
342
 
167
- // GROUP BY
168
343
  if (args?.[C6Constants.GROUP_BY]) {
169
344
  const gb = Array.isArray(args[C6Constants.GROUP_BY])
170
345
  ? args[C6Constants.GROUP_BY].join(', ')
171
346
  : args[C6Constants.GROUP_BY];
172
347
  sql += ` GROUP BY ${gb}`;
173
- isVerbose() && console.log(`[GROUP BY]`, gb);
348
+ isVerbose() && console.log(`[GROUP BY] ${gb}`);
174
349
  }
175
350
 
176
- // HAVING
177
351
  if (args?.[C6Constants.HAVING]) {
178
- const hc = this.buildBooleanJoinedConditions(args[C6Constants.HAVING], true, params);
179
- sql += ` HAVING ${hc}`;
352
+ const havingClause = this.buildBooleanJoinedConditions(args[C6Constants.HAVING], true, params);
353
+ if (havingClause) {
354
+ sql += ` HAVING ${havingClause}`;
355
+ }
180
356
  }
181
357
 
182
- // PAGINATION
183
358
  if (args?.[C6Constants.PAGINATION]) {
184
- const p = args[C6Constants.PAGINATION];
185
- if (p[C6Constants.ORDER]) {
186
- const ord = Object.entries(p[C6Constants.ORDER]).map(([c, d]) => `${c} ${String(d).toUpperCase()}`);
187
- sql += ` ORDER BY ${ord.join(', ')}`;
188
- isVerbose() && console.log(`[ORDER BY]`, ord);
189
- }
190
- if (p[C6Constants.LIMIT] != null) {
191
- const lim = parseInt(p[C6Constants.LIMIT], 10);
192
- const pg = parseInt(p[C6Constants.PAGE] ?? 1, 10);
193
- const off = (pg - 1) * lim;
194
- sql += ` LIMIT ${off}, ${lim}`;
195
- isVerbose() && console.log(`[LIMIT]`, off, lim);
196
- }
197
- }
198
- // Fallback ORDER/LIMIT
199
- else if (!isSubSelect) {
200
- let ok: string | undefined;
201
- if (primary) ok = primary;
202
- else if (model?.PRIMARY_SHORT?.[0]) ok = model.PRIMARY_SHORT[0];
203
- else for (const ts of ['created_at', 'updated_at']) {
359
+ sql += this.buildPaginationClause(args[C6Constants.PAGINATION], params);
360
+ } else if (!isSubSelect) {
361
+ let orderKey = primary || model?.PRIMARY_SHORT?.[0];
362
+ if (!orderKey) {
363
+ for (const ts of ['created_at', 'updated_at']) {
204
364
  if (model?.COLUMNS?.[`${table}.${ts}`]) {
205
- ok = ts;
365
+ orderKey = ts;
206
366
  break;
207
367
  }
208
368
  }
209
- if (ok) {
369
+ }
370
+ if (orderKey) {
210
371
  const dir = primary ? 'ASC' : 'DESC';
211
- const lim = primary ? 1 : 100;
212
- sql += ` ORDER BY ${ok} ${dir} LIMIT ${lim}`;
213
- isVerbose() && console.log(`[ORDER]`, ok, dir, lim);
372
+ sql += ` ORDER BY ${orderKey} ${dir} LIMIT ${primary ? 1 : 100}`;
373
+ isVerbose() && console.log(`[DEFAULT ORDER] ${orderKey} ${dir}`);
214
374
  } else {
215
375
  sql += ` LIMIT 100`;
216
- isVerbose() && console.warn(`[ORDER] fallback LIMIT 100`);
376
+ isVerbose() && console.warn(`[DEFAULT LIMIT] 100 (no order key found)`);
377
+ }
378
+ }
379
+
380
+ isVerbose() && console.log(`[SQL SELECT]`, sql, params);
381
+
382
+ return { sql, params };
383
+ }
384
+
385
+ protected buildUpdateQuery(
386
+ table: RestShortTableName,
387
+ data: Partial<RestTableInterface>,
388
+ args: any = {}
389
+ ): QueryResult {
390
+ const params = this.useNamedParams ? {} : [];
391
+ let sql = `UPDATE ${table}`;
392
+
393
+ if (args[C6Constants.JOIN]) {
394
+ sql += this.buildJoinClauses(args[C6Constants.JOIN], params);
395
+ }
396
+
397
+ const setClauses: string[] = [];
398
+ for (const [col, val] of Object.entries(data)) {
399
+ if (Array.isArray(val)) {
400
+ const expr = this.buildAggregateField(val);
401
+ setClauses.push(`\`${col}\` = ${expr}`);
402
+ } else {
403
+ const key = `param${Array.isArray(params) ? params.length : Object.keys(params).length}`;
404
+ if (Array.isArray(params)) {
405
+ params.push(val);
406
+ setClauses.push(`\`${col}\` = ?`);
407
+ } else {
408
+ params[key] = val;
409
+ setClauses.push(`\`${col}\` = :${key}`);
410
+ }
217
411
  }
218
412
  }
413
+ sql += ` SET ${setClauses.join(', ')}`;
414
+ isVerbose() && console.log(`[SET] ${setClauses}`);
219
415
 
220
- isVerbose() && console.log(`[SQL]`, sql, params);
221
- return {sql, params};
416
+ if (args[C6Constants.WHERE]) {
417
+ sql += this.buildWhereClause(args[C6Constants.WHERE], params);
418
+ }
419
+
420
+ if (args[C6Constants.PAGINATION]) {
421
+ sql += this.buildPaginationClause(args[C6Constants.PAGINATION], params);
422
+ }
423
+
424
+ isVerbose() && console.log(`[SQL UPDATE]`, sql, params);
425
+ return { sql, params };
222
426
  }
427
+
428
+ /** Compose a DELETE query with optional JOIN and WHERE clauses */
429
+ protected buildDeleteQuery(
430
+ table: RestShortTableName,
431
+ args: any = {}
432
+ ): QueryResult {
433
+ const params = this.useNamedParams ? {} : [];
434
+ let sql = args[C6Constants.JOIN]
435
+ ? `DELETE ${table} FROM ${table}`
436
+ : `DELETE FROM ${table}`;
437
+
438
+ if (args[C6Constants.JOIN]) {
439
+ sql += this.buildJoinClauses(args[C6Constants.JOIN], params);
440
+ }
441
+
442
+ if (args[C6Constants.WHERE]) {
443
+ sql += this.buildWhereClause(args[C6Constants.WHERE], params);
444
+ }
445
+
446
+ if (args[C6Constants.PAGINATION]) {
447
+ sql += this.buildPaginationClause(args[C6Constants.PAGINATION], params);
448
+ }
449
+
450
+ isVerbose() && console.log(`[SQL DELETE]`, sql, params);
451
+ return { sql, params };
452
+ }
453
+
223
454
  }