sql-love 1.0.3 → 1.0.5

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.
Files changed (56) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +52 -32
  3. package/dist/index.cjs +1328 -0
  4. package/dist/index.d.ts +732 -9
  5. package/{src/SelectBuilder/SelectBuilder.ts → dist/index.mjs} +650 -277
  6. package/package.json +28 -11
  7. package/.github/workflows/node.js.yml +0 -37
  8. package/.prettierrc.json +0 -13
  9. package/CHANGELOG.md +0 -39
  10. package/CONTRIBUTING.md +0 -85
  11. package/assets/sql-love-logo.png +0 -0
  12. package/codecov.yml +0 -7
  13. package/dist/index.js +0 -9
  14. package/dist/src/Parser/Parser.d.ts +0 -157
  15. package/dist/src/Parser/Parser.js +0 -304
  16. package/dist/src/SelectBuilder/SelectBuilder.d.ts +0 -379
  17. package/dist/src/SelectBuilder/SelectBuilder.js +0 -810
  18. package/dist/src/extractGrouped/extractGrouped.d.ts +0 -17
  19. package/dist/src/extractGrouped/extractGrouped.js +0 -31
  20. package/dist/src/extractIndexed/extractIndexed.d.ts +0 -12
  21. package/dist/src/extractIndexed/extractIndexed.js +0 -23
  22. package/dist/src/extractLookup/extractLookup.d.ts +0 -9
  23. package/dist/src/extractLookup/extractLookup.js +0 -21
  24. package/dist/src/getPagination/getPagination.d.ts +0 -16
  25. package/dist/src/getPagination/getPagination.js +0 -40
  26. package/dist/src/runMysql/runMysql.d.ts +0 -58
  27. package/dist/src/runMysql/runMysql.js +0 -61
  28. package/dist/src/runPrisma/runPrisma.d.ts +0 -41
  29. package/dist/src/runPrisma/runPrisma.js +0 -34
  30. package/dist/src/substrCount/substrCount.d.ts +0 -6
  31. package/dist/src/substrCount/substrCount.js +0 -14
  32. package/dist/src/toSafeJson/toSafeJson.d.ts +0 -25
  33. package/dist/src/toSafeJson/toSafeJson.js +0 -45
  34. package/index.ts +0 -19
  35. package/src/Parser/Parser.spec.ts +0 -226
  36. package/src/Parser/Parser.ts +0 -325
  37. package/src/SelectBuilder/SelectBuilder.spec.ts +0 -785
  38. package/src/extractGrouped/extractGrouped.spec.ts +0 -43
  39. package/src/extractGrouped/extractGrouped.ts +0 -36
  40. package/src/extractIndexed/extractIndexed.spec.ts +0 -40
  41. package/src/extractIndexed/extractIndexed.ts +0 -28
  42. package/src/extractLookup/extractLookup.spec.ts +0 -38
  43. package/src/extractLookup/extractLookup.ts +0 -33
  44. package/src/getPagination/getPagination.spec.ts +0 -75
  45. package/src/getPagination/getPagination.ts +0 -42
  46. package/src/runMysql/runMysql.spec.ts +0 -286
  47. package/src/runMysql/runMysql.ts +0 -106
  48. package/src/runPrisma/runPrisma.spec.ts +0 -99
  49. package/src/runPrisma/runPrisma.ts +0 -75
  50. package/src/substrCount/substrCount.spec.ts +0 -29
  51. package/src/substrCount/substrCount.ts +0 -14
  52. package/src/toSafeJson/toSafeJson.spec.ts +0 -28
  53. package/src/toSafeJson/toSafeJson.ts +0 -56
  54. package/tsconfig.build.json +0 -10
  55. package/tsconfig.json +0 -22
  56. package/vitest.config.ts +0 -11
@@ -1,117 +1,374 @@
1
- import Parser from '../Parser/Parser';
2
- import substrCount from '../substrCount/substrCount';
3
-
4
- export type FieldName =
5
- | 'option'
6
- | 'options'
7
- | 'select'
8
- | 'column'
9
- | 'columns'
10
- | 'table'
11
- | 'tables'
12
- | 'join'
13
- | 'joins'
14
- | 'where'
15
- | 'wheres'
16
- | 'having'
17
- | 'havings'
18
- | 'groupBy'
19
- | 'groupBys'
20
- | 'orderBy'
21
- | 'orderBys'
22
- | 'limit'
23
- | 'offset'
24
- | 'page';
25
-
26
- export type EngineStyle = 'mssql' | 'mysql' | 'oracle' | 'pg';
27
-
28
- export type WhereCriteria =
29
- | [string, string, any]
30
- | [string, any]
31
- | [string]
32
- | [Record<string, any>];
33
-
34
- const isNullish = v => v === undefined || v === null || v === false || isNaN(v);
1
+ // src/Parser/Parser.ts
2
+ var Parser = class {
3
+ query;
4
+ /**
5
+ * Create a new instance
6
+ * @param query A Select object on which to build parsed conditions
7
+ */
8
+ constructor(query) {
9
+ this.query = query;
10
+ }
11
+ /**
12
+ * Strip single-line and multi-line comment blocks
13
+ * @param sql The SQL string
14
+ * @return The SQL string without comments
15
+ * @private
16
+ */
17
+ _stripComments(sql) {
18
+ sql = sql.replace(/\/\*[\s\S]*?\*\//g, "");
19
+ sql = sql.replace(/--([\r\n]|\s+[^\r\n]+[\r\n])/g, "");
20
+ sql = sql.replace(/#[^\r\n]+[\r\n]/g, "");
21
+ return sql;
22
+ }
23
+ /**
24
+ * Before splitting into SQL clauses, extract some regex-able subQueries
25
+ * @param sql The unparsed sql string
26
+ * @return An array with new sql and subQueries
27
+ * @private
28
+ */
29
+ _extractSubQueries(sql) {
30
+ const subQueries = {};
31
+ let i = 0;
32
+ const extractor = ($0) => {
33
+ const placeholder = `~~SUBQUERY_${i++}~~`;
34
+ subQueries[placeholder] = $0;
35
+ return placeholder;
36
+ };
37
+ sql = sql.replace(/\(\s*SELECT\s+.+\)\s+AS\s+[^\s,]+/, extractor);
38
+ sql = sql.replace(/\bIF\s*\(.+\)\s+AS\s+[^\s,]+/, extractor);
39
+ sql = sql.replace(/\bIN\s*\(\s*SELECT\s.+?\)/, extractor);
40
+ return { sql, subQueries };
41
+ }
42
+ /**
43
+ * Inject column subQueries back into this object
44
+ * @param subQueries The lookup of extracted subQueries
45
+ * @private
46
+ */
47
+ _injectSubQueries(subQueries) {
48
+ const replacer = ($0) => {
49
+ return subQueries[$0] || $0;
50
+ };
51
+ const mapper = (clause) => {
52
+ return clause.replace(/~~SUBQUERY_\d+~~/g, replacer);
53
+ };
54
+ this.query._columns = this.query._columns.map(replacer);
55
+ this.query._joins = this.query._joins.map(mapper);
56
+ this.query._wheres = this.query._wheres.map(mapper);
57
+ this.query._havings = this.query._havings.map(mapper);
58
+ }
59
+ /**
60
+ * Split SQL into clauses (used by parse())
61
+ * @param sql The SQL to split
62
+ * @return
63
+ * @private
64
+ */
65
+ _split(sql) {
66
+ const splitter = /(?:^|\s)(SELECT|FROM|(?:INNER|LEFT\s+OUTER|RIGHT\s+OUTER|LEFT|RIGHT|CROSS|FULL|FULL\s+OUTER)\s+JOIN|WHERE|GROUP\s+BY|HAVING|ORDER\s+BY|LIMIT|OFFSET|FETCH\s+NEXT)\b/i;
67
+ return sql.split(splitter);
68
+ }
69
+ /**
70
+ * Get a QuickSelect object representing the given SQL SELECT statement
71
+ * @param rawSql The raw SQL for the SELECT statement
72
+ * @return True if the SQL was parsed successfully
73
+ */
74
+ parse(rawSql) {
75
+ const stripped = this._stripComments(rawSql);
76
+ const { sql, subQueries } = this._extractSubQueries(stripped);
77
+ const expressions = this._split(sql);
78
+ let i = 1;
79
+ while (i < expressions.length) {
80
+ const rawKeyword = expressions[i++].trim();
81
+ const keyword = rawKeyword.toUpperCase().replace(/\s+/g, "_");
82
+ const clause = expressions[i++].trim();
83
+ const handler = `_handle${keyword}`;
84
+ this[handler](clause);
85
+ }
86
+ this._injectSubQueries(subQueries);
87
+ return true;
88
+ }
89
+ /**
90
+ * Handle SQL_CALC_FOUND_ROWS and column names
91
+ * @param clause The clause after the SELECT
92
+ * @private
93
+ */
94
+ _handleSELECT(clause) {
95
+ let columns = clause.split(/s*,s*/);
96
+ let buffer = "";
97
+ columns.forEach((column, i) => {
98
+ if (i === 0) {
99
+ const optionRegex = /^(SQL_CALC_FOUND_ROWS)\s+/i;
100
+ const match = column.match(optionRegex);
101
+ if (match) {
102
+ this.query.option(match[1]);
103
+ column = column.replace(optionRegex, "");
104
+ }
105
+ }
106
+ if (buffer.length) {
107
+ if (column.indexOf(")") > 0) {
108
+ buffer += column;
109
+ this.query.column(buffer.trim());
110
+ buffer = "";
111
+ } else {
112
+ buffer += column + ",";
113
+ }
114
+ } else if (column.match(/\([^)]+$/)) {
115
+ buffer = column + ",";
116
+ } else {
117
+ this.query.column(column.trim());
118
+ }
119
+ });
120
+ }
121
+ /**
122
+ * Handle table names
123
+ * @param clause The clause after the FROM
124
+ * @private
125
+ */
126
+ _handleFROM(clause) {
127
+ const tables = clause.split(/\s*,\s*/);
128
+ tables.forEach((table) => this.query.table(table));
129
+ }
130
+ /**
131
+ * Handle INNER JOIN statements
132
+ * @param clause The clause after the INNER JOIN
133
+ * @private
134
+ */
135
+ _handleINNER_JOIN(clause) {
136
+ this.query.innerJoin(clause);
137
+ }
138
+ /**
139
+ * Handle LEFT JOIN statements
140
+ * @param clause The clause after the LEFT JOIN
141
+ * @private
142
+ */
143
+ _handleLEFT_JOIN(clause) {
144
+ this.query.leftJoin(clause);
145
+ }
146
+ /**
147
+ * Handle LEFT OUTER JOIN statements
148
+ * @param clause The clause after the LEFT OUTER JOIN
149
+ * @private
150
+ */
151
+ _handleLEFT_OUTER_JOIN(clause) {
152
+ this.query.leftOuterJoin(clause);
153
+ }
154
+ /**
155
+ * Handle RIGHT JOIN statements
156
+ * @param clause The clause after the RIGHT JOIN
157
+ * @private
158
+ */
159
+ _handleRIGHT_JOIN(clause) {
160
+ this.query.rightJoin(clause);
161
+ }
162
+ /**
163
+ * Handle RIGHT OUTER JOIN statements
164
+ * @param clause The clause after the RIGHT OUTER JOIN
165
+ * @private
166
+ */
167
+ _handleRIGHT_OUTER_JOIN(clause) {
168
+ this.query.rightOuterJoin(clause);
169
+ }
170
+ /**
171
+ * Handle CROSS JOIN statements
172
+ * @param clause The clause after the CROSS JOIN
173
+ * @private
174
+ */
175
+ _handleCROSS_JOIN(clause) {
176
+ this.query.crossJoin(clause);
177
+ }
178
+ /**
179
+ * Handle FULL JOIN statements
180
+ * @param clause The clause after the FULL JOIN
181
+ * @private
182
+ */
183
+ _handleFULL_JOIN(clause) {
184
+ this.query.fullJoin(clause);
185
+ }
186
+ /**
187
+ * Handle FULL OUTER JOIN statements
188
+ * @param clause The clause after the FULL OUTER JOIN
189
+ * @private
190
+ */
191
+ _handleFULL_OUTER_JOIN(clause) {
192
+ this.query.fullOuterJoin(clause);
193
+ }
194
+ /**
195
+ * Handle WHERE conditions
196
+ * @param clause All the conditions after WHERE
197
+ * @private
198
+ */
199
+ _handleWHERE(clause) {
200
+ if (/^(1|'1'|true)$/i.test(clause)) {
201
+ this.query._wheres.push(clause);
202
+ } else {
203
+ this._handleConditions("where", clause);
204
+ }
205
+ }
206
+ /**
207
+ * Handle HAVING statements
208
+ * @param clause All the conditions after HAVING
209
+ * @private
210
+ */
211
+ _handleHAVING(clause) {
212
+ this._handleConditions("having", clause);
213
+ }
214
+ /**
215
+ * Build a conditions list
216
+ * @param type Either where or having
217
+ * @param clause The expressions following the type keyword
218
+ * @private
219
+ */
220
+ _handleConditions(type, clause) {
221
+ const andGroups = clause.split(/\bAND\b/i);
222
+ andGroups.forEach((andGroup) => {
223
+ const orPieces = andGroup.split(/\bOR\b/i).map((str) => str.trim());
224
+ if (orPieces.length === 1) {
225
+ const fn = type;
226
+ this.query[fn](orPieces[0]);
227
+ } else {
228
+ const orFn = type === "where" ? "orWhere" : "orHaving";
229
+ this.query[orFn](orPieces.map((p) => [p]));
230
+ }
231
+ });
232
+ }
233
+ /**
234
+ * Handle GROUP BY statements
235
+ * @param clause The clauses after the GROUP BY
236
+ * @private
237
+ */
238
+ _handleGROUP_BY(clause) {
239
+ const columns = clause.split(/\s*,\s*/);
240
+ columns.forEach((column) => this.query.groupBy(column));
241
+ }
242
+ /**
243
+ * Handle ORDER BY statements
244
+ * @param clause The clause after the ORDER BY
245
+ * @private
246
+ */
247
+ _handleORDER_BY(clause) {
248
+ const columns = clause.split(/\s*,\s*/);
249
+ columns.forEach((column) => this.query.orderBy(column));
250
+ }
251
+ /**
252
+ * Handle LIMIT statements including "LIMIT #" and "LIMIT #, #"
253
+ * @param clause The clause after the LIMIT
254
+ * @private
255
+ */
256
+ _handleLIMIT(clause) {
257
+ const offsetLimit = clause.match(/^(\d+|\?|:\w+)\s*,\s*(\d+|\?|:\w+)$/);
258
+ if (offsetLimit) {
259
+ this.query.offset(offsetLimit[1]);
260
+ this.query.limit(offsetLimit[2]);
261
+ } else {
262
+ clause = clause.replace(/ ROWS$/i, "");
263
+ this.query.limit(clause);
264
+ }
265
+ }
266
+ /**
267
+ * Handle OFFSET statements
268
+ * @param clause The number after the OFFSET
269
+ * @private
270
+ */
271
+ _handleOFFSET(clause) {
272
+ clause = clause.replace(/^(\d+) ROWS$/i, "$1");
273
+ this.query.offset(clause);
274
+ }
275
+ /**
276
+ * Handle OFFSET statements
277
+ * @param clause The number after the OFFSET
278
+ * @private
279
+ */
280
+ _handleFETCH_NEXT(clause) {
281
+ clause = clause.replace(/^(\d+) ROWS ONLY$/i, "$1");
282
+ this.query.limit(clause);
283
+ }
284
+ };
35
285
 
36
- let defaultEngine: EngineStyle = 'mysql';
286
+ // src/substrCount/substrCount.ts
287
+ function substrCount(haystack, needle) {
288
+ let count = 0;
289
+ let pos = haystack.indexOf(needle);
290
+ while (pos !== -1) {
291
+ count += 1;
292
+ pos = haystack.indexOf(needle, pos + 1);
293
+ }
294
+ return count;
295
+ }
37
296
 
38
- /**
39
- * Build a select query
40
- * Class Select
41
- */
42
- export default class SelectBuilder {
297
+ // src/SelectBuilder/SelectBuilder.ts
298
+ var isNullish = (v) => v === void 0 || v === null || v === false || isNaN(v);
299
+ var defaultEngine = "mysql";
300
+ var SelectBuilder = class _SelectBuilder {
43
301
  /**
44
302
  * The list of values to bind to the query
45
303
  * @private
46
304
  */
47
- _bindings: any[] = [];
305
+ _bindings = [];
48
306
  /**
49
307
  * The list of strings to come immediately after "SELECT"
50
308
  * and before column names
51
309
  * @private
52
310
  */
53
- _options: string[] = [];
311
+ _options = [];
54
312
  /**
55
313
  * The list of column names to select
56
314
  * @private
57
315
  */
58
- _tables: string[] = [];
316
+ _tables = [];
59
317
  /**
60
318
  * The list of tables in the FROM clause
61
319
  * @private
62
320
  */
63
- _columns: string[] = [];
321
+ _columns = [];
64
322
  /**
65
323
  * The list of JOIN strings to add
66
324
  * @private
67
325
  */
68
- _joins: string[] = [];
326
+ _joins = [];
69
327
  /**
70
328
  * The list of WHERE clauses to join with AND
71
329
  * @private
72
330
  */
73
- _wheres: string[] = [];
331
+ _wheres = [];
74
332
  /**
75
333
  * The list of GROUP BY clauses to join with commas
76
334
  * @private
77
335
  */
78
- _groupBys: string[] = [];
336
+ _groupBys = [];
79
337
  /**
80
338
  * The list of HAVING clauses to join with AND
81
339
  * @private
82
340
  */
83
- _havings: string[] = [];
341
+ _havings = [];
84
342
  /**
85
343
  * The list of ORDER BY clauses to join with commas
86
344
  * @private
87
345
  */
88
- _orderBys: string[] = [];
346
+ _orderBys = [];
89
347
  /**
90
348
  * The LIMIT to use
91
349
  * @private
92
350
  */
93
- _limit: number;
351
+ _limit;
94
352
  /**
95
353
  * The OFFSET to use
96
354
  * @private
97
355
  */
98
- _offset: number;
356
+ _offset;
99
357
  /**
100
358
  * The page used to construct an OFFSET based on the LIMIT
101
359
  * @private
102
360
  */
103
- _page: number;
104
-
361
+ _page;
105
362
  /**
106
363
  * Select constructor
107
364
  * @param [sql] A base SQL query to parse
108
365
  * @param [bindings] Placeholders to bind to the query
109
366
  */
110
- constructor(sql: string = null, bindings: Record<string, any> = null) {
367
+ constructor(sql = null, bindings = null) {
111
368
  if (sql) {
112
369
  if (bindings) {
113
370
  const keys = Object.keys(bindings);
114
- const regexp = new RegExp(`:(${keys.join('|')})`, 'g');
371
+ const regexp = new RegExp(`:(${keys.join("|")})`, "g");
115
372
  sql = sql.replace(regexp, ($0, $1) => {
116
373
  this._bindings.push(bindings[$1]);
117
374
  return `__BOUND_${this._bindings.length - 1}__`;
@@ -120,16 +377,14 @@ export default class SelectBuilder {
120
377
  new Parser(this).parse(sql);
121
378
  }
122
379
  }
123
-
124
380
  /**
125
381
  * Set the default database engine style (mssql, mysql, oracle, pg)
126
382
  * @param engine
127
383
  */
128
- static setDefaultEngine(engine: EngineStyle) {
384
+ static setDefaultEngine(engine) {
129
385
  defaultEngine = engine;
130
- return SelectBuilder;
386
+ return _SelectBuilder;
131
387
  }
132
-
133
388
  /**
134
389
  * Get the SQL as a pretty-printed string alongside an array of bindings
135
390
  * @param engine The database engine style (mssql, mysql, oracle, pg)
@@ -137,22 +392,18 @@ export default class SelectBuilder {
137
392
  */
138
393
  compile({
139
394
  engine = defaultEngine,
140
- bindingIndex = 1,
141
- }: {
142
- engine?: EngineStyle;
143
- bindingIndex?: number;
395
+ bindingIndex = 1
144
396
  } = {}) {
145
- // build all the lines
146
397
  const lines = [
147
- 'SELECT',
148
- this._options.length ? ` ${this._options.join('\n ')}` : null,
149
- this._columns.length ? ` ${this._columns.join(',\n ')}` : ' *\n',
150
- `FROM ${this._tables.join(', ')}`,
151
- this._joins.length ? this._joins.join('\n') : null,
152
- this._wheres.length ? `WHERE ${this._wheres.join('\n AND ')}` : null,
153
- this._groupBys.length ? `GROUP BY ${this._groupBys.join(',\n ')}` : null,
154
- this._havings.length ? `HAVING ${this._havings.join('\n AND ')}` : null,
155
- this._orderBys.length ? `ORDER BY ${this._orderBys.join(',\n ')}` : null,
398
+ "SELECT",
399
+ this._options.length ? ` ${this._options.join("\n ")}` : null,
400
+ this._columns.length ? ` ${this._columns.join(",\n ")}` : " *\n",
401
+ `FROM ${this._tables.join(", ")}`,
402
+ this._joins.length ? this._joins.join("\n") : null,
403
+ this._wheres.length ? `WHERE ${this._wheres.join("\n AND ")}` : null,
404
+ this._groupBys.length ? `GROUP BY ${this._groupBys.join(",\n ")}` : null,
405
+ this._havings.length ? `HAVING ${this._havings.join("\n AND ")}` : null,
406
+ this._orderBys.length ? `ORDER BY ${this._orderBys.join(",\n ")}` : null
156
407
  ];
157
408
  if (this._page > 0) {
158
409
  const offset = (this._page - 1) * this._limit;
@@ -166,120 +417,109 @@ export default class SelectBuilder {
166
417
  lines.push(this._compileLIMIT(engine, this._limit));
167
418
  }
168
419
  }
169
-
170
- const pretty = lines.filter(Boolean).join('\n').trim();
171
- let replacer: (match0: string, match1: string) => string;
172
- const bindings: any = [];
173
- if (engine === 'pg') {
420
+ const pretty = lines.filter(Boolean).join("\n").trim();
421
+ let replacer;
422
+ const bindings = [];
423
+ if (engine === "pg") {
174
424
  let idx = bindingIndex;
175
425
  replacer = (_, $1) => {
176
426
  bindings.push(this._bindings[$1]);
177
- return '$' + idx++;
427
+ return "$" + idx++;
178
428
  };
179
429
  } else {
180
430
  replacer = (_, $1) => {
181
431
  bindings.push(this._bindings[$1]);
182
- return '?';
432
+ return "?";
183
433
  };
184
434
  }
185
435
  const sql = pretty.replace(/__BOUND_(\d+)__/g, replacer);
186
436
  return { sql, bindings };
187
437
  }
188
-
189
- _compileLIMIT(engineStyle: EngineStyle, limit: number) {
190
- if (engineStyle === 'pg' || engineStyle === 'mysql') {
438
+ _compileLIMIT(engineStyle, limit) {
439
+ if (engineStyle === "pg" || engineStyle === "mysql") {
191
440
  return `LIMIT ${limit}`;
192
- } else if (engineStyle === 'mssql') {
441
+ } else if (engineStyle === "mssql") {
193
442
  return `FETCH NEXT ${limit} ROWS ONLY`;
194
443
  }
195
444
  }
196
-
197
- _compileOFFSET(engineStyle: EngineStyle, offset: number) {
198
- if (engineStyle === 'pg' || engineStyle === 'mysql') {
445
+ _compileOFFSET(engineStyle, offset) {
446
+ if (engineStyle === "pg" || engineStyle === "mysql") {
199
447
  return `OFFSET ${offset}`;
200
- } else if (engineStyle === 'mssql' || engineStyle === 'oracle') {
448
+ } else if (engineStyle === "mssql" || engineStyle === "oracle") {
201
449
  return `OFFSET ${offset} ROWS`;
202
450
  }
203
451
  }
204
-
205
452
  /**
206
453
  * Get SQL needed to return the found rows of this query
207
454
  * @param engineStyle The database engine style (mssql, mysql, oracle, pg)
208
455
  * @param countExpr The expression to use inside the COUNT()
209
456
  */
210
457
  compileCount({
211
- countExpr = '*',
458
+ countExpr = "*",
212
459
  engine = defaultEngine,
213
- bindingIndex = 1,
214
- }: {
215
- countExpr?: string;
216
- engine?: EngineStyle;
217
- bindingIndex?: number;
460
+ bindingIndex = 1
218
461
  } = {}) {
219
462
  const query = this.getFoundRowsQuery(countExpr);
220
463
  const { sql, bindings } = query.compile({ engine, bindingIndex });
221
464
  if (this._havings.length === 0) {
222
465
  return { sql, bindings };
223
466
  } else {
224
- const subquerySql = sql.replace(/\n/g, '\n\t');
467
+ const subquerySql = sql.replace(/\n/g, "\n ");
225
468
  return {
226
- sql: `SELECT COUNT(*) AS found_rows\nFROM (\n${subquerySql}\n) AS subquery_results`,
227
- bindings,
469
+ sql: `SELECT COUNT(*) AS found_rows
470
+ FROM (
471
+ ${subquerySql}
472
+ ) AS subquery_results`,
473
+ bindings
228
474
  };
229
475
  }
230
476
  }
231
-
232
477
  /**
233
478
  * Get SQL needed to union this query with another
234
479
  * @param query The query to union
235
480
  * @param engine The database engine style (mssql, mysql, oracle, pg)
236
481
  */
237
- compileUnion(
238
- query: SelectBuilder,
239
- {
240
- engine = defaultEngine,
241
- bindingIndex = 1,
242
- }: {
243
- engine?: EngineStyle;
244
- bindingIndex?: number;
245
- } = {}
246
- ) {
482
+ compileUnion(query, {
483
+ engine = defaultEngine,
484
+ bindingIndex = 1
485
+ } = {}) {
247
486
  const first = this.compile({ engine, bindingIndex });
248
487
  const second = query.compile({
249
488
  engine,
250
- bindingIndex: first.bindings.length + bindingIndex,
489
+ bindingIndex: first.bindings.length + bindingIndex
251
490
  });
252
491
  return {
253
- sql: `(${first.sql})\nUNION\n(${second.sql})`,
254
- bindings: [...first.bindings, ...second.bindings],
492
+ sql: `(${first.sql})
493
+ UNION
494
+ (${second.sql})`,
495
+ bindings: [...first.bindings, ...second.bindings]
255
496
  };
256
497
  }
257
-
258
498
  /**
259
499
  * @param [fieldOrFields] If given, reset the given component(s), otherwise reset all query components
260
500
  * Valid components: option, column, table, where, orWhere, having, groupBy, orderBy, limit, offset, page
261
501
  * @return This object
262
502
  */
263
- reset(fieldOrFields: FieldName | FieldName[] = null) {
503
+ reset(fieldOrFields = null) {
264
504
  if (Array.isArray(fieldOrFields)) {
265
- fieldOrFields.forEach(name => this.reset(name));
505
+ fieldOrFields.forEach((name) => this.reset(name));
266
506
  return this;
267
507
  }
268
508
  if (fieldOrFields) {
269
509
  const pluralizable = [
270
- 'option',
271
- 'column',
272
- 'table',
273
- 'where',
274
- 'having',
275
- 'groupBy',
276
- 'orderBy',
510
+ "option",
511
+ "column",
512
+ "table",
513
+ "where",
514
+ "having",
515
+ "groupBy",
516
+ "orderBy"
277
517
  ];
278
- let prop = '_' + fieldOrFields.replace(/s$/, '');
518
+ let prop = "_" + fieldOrFields.replace(/s$/, "");
279
519
  if (pluralizable.indexOf(fieldOrFields) > -1) {
280
- prop += 's';
520
+ prop += "s";
281
521
  }
282
- const isScalar = ['limit', 'offset', 'page'].indexOf(fieldOrFields) > -1;
522
+ const isScalar = ["limit", "offset", "page"].indexOf(fieldOrFields) > -1;
283
523
  this[prop] = isScalar ? null : [];
284
524
  } else {
285
525
  this._options = [];
@@ -297,12 +537,11 @@ export default class SelectBuilder {
297
537
  }
298
538
  return this;
299
539
  }
300
-
301
540
  /**
302
541
  * Clone this builder object
303
542
  */
304
543
  getClone() {
305
- const copy = new SelectBuilder();
544
+ const copy = new _SelectBuilder();
306
545
  copy._options = [...this._options];
307
546
  copy._columns = [...this._columns];
308
547
  copy._tables = [...this._tables];
@@ -317,12 +556,11 @@ export default class SelectBuilder {
317
556
  copy._bindings = [...this._bindings];
318
557
  return copy;
319
558
  }
320
-
321
559
  /**
322
560
  * Build a version of this query that simply returns COUNT(*)
323
561
  * @param [countExpr="*"] Use to specify `DISTINCT colname` if needed
324
562
  */
325
- getFoundRowsQuery(countExpr: string = '*') {
563
+ getFoundRowsQuery(countExpr = "*") {
326
564
  if (this._havings.length === 0) {
327
565
  const clone = this.getClone();
328
566
  clone._columns = [`COUNT(${countExpr}) AS found_rows`];
@@ -341,73 +579,65 @@ export default class SelectBuilder {
341
579
  return subquery;
342
580
  }
343
581
  }
344
-
345
582
  /**
346
583
  * Add an array of column names to fetch
347
584
  * @param columnNames The names of columns
348
585
  */
349
- select(columnNames: string[]) {
586
+ select(columnNames) {
350
587
  this._columns = this._columns.concat(columnNames);
351
588
  return this;
352
589
  }
353
-
354
590
  /**
355
591
  * Add an array of column names to fetch
356
592
  * @param columnNames The names of columns
357
593
  */
358
- columns(columnNames: string[]) {
594
+ columns(columnNames) {
359
595
  this._columns = this._columns.concat(columnNames);
360
596
  return this;
361
597
  }
362
-
363
598
  /**
364
599
  * Add a column name to fetch
365
600
  * @param columnName The name of the column
366
601
  */
367
- column(columnName: string) {
602
+ column(columnName) {
368
603
  this._columns.push(columnName);
369
604
  return this;
370
605
  }
371
-
372
606
  /**
373
607
  * Add an option expression such as "TOP 10" or "SQL_CALC_FOUND_ROWS"
374
608
  * @param optionExpression Expression to go after "SELECT" and before column list
375
609
  */
376
- option(optionExpression: string) {
610
+ option(optionExpression) {
377
611
  this._options.push(optionExpression);
378
612
  return this;
379
613
  }
380
-
381
614
  /**
382
615
  * Add a table to the "FROM" clause (same as .from())
383
616
  * @param {String} tableName The name of the table to query
384
617
  * @return {Select}
385
618
  */
386
- table(tableName: string) {
619
+ table(tableName) {
387
620
  this._tables.push(tableName);
388
621
  return this;
389
622
  }
390
-
391
623
  /**
392
624
  * Add multiple table to the "FROM" clause
393
625
  * @param {Array} tableNames The names of the tables to query
394
626
  * @return {Select}
395
627
  */
396
- tables(tableNames: string[]) {
628
+ tables(tableNames) {
397
629
  this._tables.push(...tableNames);
398
630
  return this;
399
631
  }
400
-
401
632
  /**
402
633
  * Add a table to the "FROM" clause (same as .table())
403
634
  * @param {String} tableName The name of the table to query
404
635
  * @return {Select}
405
636
  */
406
- from(tableName: string) {
637
+ from(tableName) {
407
638
  this._tables.push(tableName);
408
639
  return this;
409
640
  }
410
-
411
641
  /**
412
642
  * Add the given join type expression
413
643
  * @param joinType The type of join (e.g. "INNER JOIN", "LEFT JOIN", etc.)
@@ -417,7 +647,7 @@ export default class SelectBuilder {
417
647
  * query.join('posts p ON p.id = c.post_id');
418
648
  * query.join('posts p ON p.id = c.post_id AND p.deleted_at < ?', [myDate]);
419
649
  */
420
- _addJoin(joinType: string, expression: string, bindings: any[] = []) {
650
+ _addJoin(joinType, expression, bindings = []) {
421
651
  if (bindings.length > 0) {
422
652
  expression = expression.replace(/\?/g, () => {
423
653
  this._bindings.push(bindings.shift());
@@ -426,7 +656,6 @@ export default class SelectBuilder {
426
656
  }
427
657
  this._joins.push(`${joinType} ${expression}`);
428
658
  }
429
-
430
659
  /**
431
660
  * Add a JOIN expression
432
661
  * @param expression The expression following the INNER JOIN keyword
@@ -435,11 +664,10 @@ export default class SelectBuilder {
435
664
  * query.join('posts p ON p.id = c.post_id');
436
665
  * query.join('posts p ON p.id = c.post_id AND p.deleted_at < ?', [myDate]);
437
666
  */
438
- join(expression: string, bindings: any[] = []) {
439
- this._addJoin('JOIN', expression, bindings);
667
+ join(expression, bindings = []) {
668
+ this._addJoin("JOIN", expression, bindings);
440
669
  return this;
441
670
  }
442
-
443
671
  /**
444
672
  * Add a LEFT JOIN expression
445
673
  * @param expression The expression following the LEFT JOIN keyword
@@ -448,11 +676,10 @@ export default class SelectBuilder {
448
676
  * query.leftJoin('posts p ON p.id = c.post_id');
449
677
  * query.leftJoin('posts p ON p.id = c.post_id AND p.deleted_at < ?', [myDate]);
450
678
  */
451
- leftJoin(expression: string, bindings: any[] = []) {
452
- this._addJoin('LEFT JOIN', expression, bindings);
679
+ leftJoin(expression, bindings = []) {
680
+ this._addJoin("LEFT JOIN", expression, bindings);
453
681
  return this;
454
682
  }
455
-
456
683
  /**
457
684
  * Add a FULL JOIN expression
458
685
  * @param expression The expression following the FULL JOIN keyword
@@ -461,11 +688,10 @@ export default class SelectBuilder {
461
688
  * query.fullJoin('posts p ON p.id = c.post_id');
462
689
  * query.fullJoin('posts p ON p.id = c.post_id AND p.deleted_at < ?', [myDate]);
463
690
  */
464
- fullJoin(expression: string, bindings: any[] = []) {
465
- this._addJoin('FULL JOIN', expression, bindings);
691
+ fullJoin(expression, bindings = []) {
692
+ this._addJoin("FULL JOIN", expression, bindings);
466
693
  return this;
467
694
  }
468
-
469
695
  /**
470
696
  * Add a RIGHT JOIN expression
471
697
  * @param expression The expression following the RIGHT JOIN keyword
@@ -474,11 +700,10 @@ export default class SelectBuilder {
474
700
  * query.rightJoin('posts p ON p.id = c.post_id');
475
701
  * query.rightJoin('posts p ON p.id = c.post_id AND p.deleted_at < ?', [myDate]);
476
702
  */
477
- rightJoin(expression: string, bindings: any[] = []) {
478
- this._addJoin('RIGHT JOIN', expression, bindings);
703
+ rightJoin(expression, bindings = []) {
704
+ this._addJoin("RIGHT JOIN", expression, bindings);
479
705
  return this;
480
706
  }
481
-
482
707
  /**
483
708
  * Add a CROSS JOIN expression
484
709
  * @param expression The expression following the CROSS JOIN keyword
@@ -487,11 +712,10 @@ export default class SelectBuilder {
487
712
  * query.crossJoin('posts p ON p.id = c.post_id');
488
713
  * query.crossJoin('posts p ON p.id = c.post_id AND p.deleted_at < ?', [myDate]);
489
714
  */
490
- crossJoin(expression: string, bindings: any[] = []) {
491
- this._addJoin('CROSS JOIN', expression, bindings);
715
+ crossJoin(expression, bindings = []) {
716
+ this._addJoin("CROSS JOIN", expression, bindings);
492
717
  return this;
493
718
  }
494
-
495
719
  /**
496
720
  * Add an INNER JOIN expression (same as ->join())
497
721
  * @param expression The expression following the INNER JOIN keyword
@@ -500,11 +724,10 @@ export default class SelectBuilder {
500
724
  * query.innerJoin('posts p ON p.id = c.post_id');
501
725
  * query.innerJoin('posts p ON p.id = c.post_id AND p.deleted_at < ?', [myDate]);
502
726
  */
503
- innerJoin(expression: string, bindings: any[] = []) {
504
- this._addJoin('INNER JOIN', expression, bindings);
727
+ innerJoin(expression, bindings = []) {
728
+ this._addJoin("INNER JOIN", expression, bindings);
505
729
  return this;
506
730
  }
507
-
508
731
  /**
509
732
  * Add a LEFT OUTER JOIN expression
510
733
  * @param expression The expression following the LEFT OUTER JOIN keyword
@@ -513,11 +736,10 @@ export default class SelectBuilder {
513
736
  * query.leftOuterJoin('posts p ON p.id = c.post_id');
514
737
  * query.leftOuterJoin('posts p ON p.id = c.post_id AND p.deleted_at < ?', [myDate]);
515
738
  */
516
- leftOuterJoin(expression: string, bindings: any[] = []) {
517
- this._addJoin('LEFT OUTER JOIN', expression, bindings);
739
+ leftOuterJoin(expression, bindings = []) {
740
+ this._addJoin("LEFT OUTER JOIN", expression, bindings);
518
741
  return this;
519
742
  }
520
-
521
743
  /**
522
744
  * Add a FULL OUTER JOIN expression
523
745
  * @param expression The expression following the FULL OUTER JOIN keyword
@@ -526,11 +748,10 @@ export default class SelectBuilder {
526
748
  * query.fullOuterJoin('posts p ON p.id = c.post_id');
527
749
  * query.fullOuterJoin('posts p ON p.id = c.post_id AND p.deleted_at < ?', [myDate]);
528
750
  */
529
- fullOuterJoin(expression: string, bindings: any[] = []) {
530
- this._addJoin('FULL OUTER JOIN', expression, bindings);
751
+ fullOuterJoin(expression, bindings = []) {
752
+ this._addJoin("FULL OUTER JOIN", expression, bindings);
531
753
  return this;
532
754
  }
533
-
534
755
  /**
535
756
  * Add a RIGHT OUTER JOIN expression
536
757
  * @param expression The expression following the RIGHT OUTER JOIN keyword
@@ -539,27 +760,25 @@ export default class SelectBuilder {
539
760
  * query.rightOuterJoin('posts p ON p.id = c.post_id');
540
761
  * query.rightOuterJoin('posts p ON p.id = c.post_id AND p.deleted_at < ?', [myDate]);
541
762
  */
542
- rightOuterJoin(expression: string, bindings: any[] = []) {
543
- this._addJoin('RIGHT OUTER JOIN', expression, bindings);
763
+ rightOuterJoin(expression, bindings = []) {
764
+ this._addJoin("RIGHT OUTER JOIN", expression, bindings);
544
765
  return this;
545
766
  }
546
-
547
767
  /**
548
768
  * Remove a join condition with the specified table
549
769
  * @param table The name of the table or tables in the first part of the join statement
550
770
  */
551
- unjoin(table: string | string[]) {
771
+ unjoin(table) {
552
772
  if (Array.isArray(table)) {
553
- table.forEach(t => this.unjoin(t));
773
+ table.forEach((t) => this.unjoin(t));
554
774
  return this;
555
775
  }
556
- this._joins = this._joins.filter(join => {
776
+ this._joins = this._joins.filter((join) => {
557
777
  const regex = new RegExp(`JOIN ${table}\\b`);
558
778
  return !regex.test(join);
559
779
  });
560
780
  return this;
561
781
  }
562
-
563
782
  /**
564
783
  * Utility function to add conditions for a clause (WHERE, HAVING)
565
784
  * @param collection The collection to add the clauses to (e.g. this._wheres or this._havings)
@@ -588,28 +807,19 @@ export default class SelectBuilder {
588
807
  * this._conditions(this._wheres, ['price', 'IN', [10,20]]); // `price` IN(10,20)
589
808
  * this._conditions(this._wheres, ['price', 'NOT IN', [10,20]]); // `price` NOT IN(10,20)
590
809
  */
591
- _conditions(collection: string[], criteria: WhereCriteria) {
810
+ _conditions(collection, criteria) {
592
811
  const numArgs = criteria.length;
593
812
  let [column, operator, value] = criteria;
594
- if (typeof column === 'object') {
813
+ if (typeof column === "object") {
595
814
  for (const keyValue of Object.entries(column)) {
596
815
  this._conditions(collection, keyValue);
597
816
  }
598
817
  return this;
599
818
  }
600
819
  if (numArgs === 1) {
601
- // condition is a stand-alone expression
602
- // e.g. "SUM(price) > 10"
603
820
  collection.push(column);
604
821
  return this;
605
- } else if (
606
- numArgs === 2 &&
607
- Array.isArray(operator) &&
608
- operator.length > 0 &&
609
- substrCount(column, '?') === operator.length
610
- ) {
611
- // column is a string with question marks and operator is an array of replacements
612
- // e.g. query.where('SUBSTR(prefs, ?, ?) = role', [1, 4]);
822
+ } else if (numArgs === 2 && Array.isArray(operator) && operator.length > 0 && substrCount(column, "?") === operator.length) {
613
823
  const values = operator;
614
824
  const sql = column.replace(/\?/g, () => {
615
825
  this._bindings.push(values.shift());
@@ -618,23 +828,19 @@ export default class SelectBuilder {
618
828
  collection.push(sql);
619
829
  return this;
620
830
  } else if (numArgs === 2) {
621
- // condition has pairs of "column + operator" => "value"
622
- // e.g. ["price >", 10]
623
- // e.g. ["status LIKE ?%", 10]
624
831
  value = operator;
625
- const parts = column.split(' ');
832
+ const parts = column.split(" ");
626
833
  column = parts.shift();
627
- operator = parts.join(' ');
834
+ operator = parts.join(" ");
628
835
  }
629
- if (typeof operator !== 'string' || operator === '') {
630
- operator = '=';
836
+ if (typeof operator !== "string" || operator === "") {
837
+ operator = "=";
631
838
  }
632
839
  operator = operator.toLocaleUpperCase();
633
840
  const likeMatch = operator.match(
634
841
  /^(LIKE|NOT LIKE|ILIKE|NOT ILIKE)(?: (\?|\?%|%\?|%\?%))?$/i
635
842
  );
636
- if (operator === 'NOT BETWEEN' || operator === 'BETWEEN') {
637
- // expect a two-item array
843
+ if (operator === "NOT BETWEEN" || operator === "BETWEEN") {
638
844
  const one = this._bindings.length;
639
845
  const two = this._bindings.length + 1;
640
846
  collection.push(
@@ -642,39 +848,35 @@ export default class SelectBuilder {
642
848
  );
643
849
  this._bindings.push(value[0], value[1]);
644
850
  } else if (likeMatch) {
645
- const like = likeMatch[1].toUpperCase(); // Either "LIKE", "NOT LIKE" etc.
646
- const infix = likeMatch[2] || '?'; // ONE OF ?% or %?% or %? or ?
851
+ const like = likeMatch[1].toUpperCase();
852
+ const infix = likeMatch[2] || "?";
647
853
  if (Array.isArray(value)) {
648
854
  const ors = [];
649
855
  for (const v of value) {
650
856
  ors.push(`${column} ${like} __BOUND_${this._bindings.length}__`);
651
- this._bindings.push(infix.replace('?', v));
857
+ this._bindings.push(infix.replace("?", v));
652
858
  }
653
- const joined = ors.join(' OR ');
859
+ const joined = ors.join(" OR ");
654
860
  collection.push(`(${joined})`);
655
861
  } else {
656
862
  collection.push(`${column} ${like} __BOUND_${this._bindings.length}__`);
657
- this._bindings.push(infix.replace('?', value || ''));
863
+ this._bindings.push(infix.replace("?", value || ""));
658
864
  }
659
865
  } else if (value === null) {
660
866
  collection.push(
661
- operator === '=' ? `${column} IS NULL` : `${column} IS NOT NULL`
867
+ operator === "=" ? `${column} IS NULL` : `${column} IS NOT NULL`
662
868
  );
663
869
  } else if (Array.isArray(value)) {
664
- // an array of values should be IN or NOT IN
665
870
  const inVals = [];
666
871
  for (let i = 0; i < value.length; i++) {
667
872
  inVals.push(`__BOUND_${this._bindings.length}__`);
668
873
  this._bindings.push(value[i]);
669
874
  }
670
- const joined = inVals.join(', ');
875
+ const joined = inVals.join(", ");
671
876
  collection.push(
672
- operator === '=' || operator === 'IN'
673
- ? `${column} IN(${joined})`
674
- : `${column} NOT IN(${joined})`
877
+ operator === "=" || operator === "IN" ? `${column} IN(${joined})` : `${column} NOT IN(${joined})`
675
878
  );
676
- } else if (operator === 'IN' || operator === 'NOT IN') {
677
- // in clause that is not array
879
+ } else if (operator === "IN" || operator === "NOT IN") {
678
880
  collection.push(
679
881
  `${column} ${operator}(__BOUND_${this._bindings.length}__)`
680
882
  );
@@ -687,54 +889,47 @@ export default class SelectBuilder {
687
889
  }
688
890
  return this;
689
891
  }
690
-
691
892
  /**
692
893
  * Add a group by column or expression
693
894
  * @param column The name of a column (or expression) to group by
694
895
  */
695
- groupBy(column: string) {
896
+ groupBy(column) {
696
897
  this._groupBys.push(column);
697
898
  return this;
698
899
  }
699
-
700
900
  /**
701
901
  * Add WHERE clauses to conditions (See _conditions for usage)
702
902
  */
703
- where(...args: WhereCriteria) {
903
+ where(...args) {
704
904
  this._conditions(this._wheres, args);
705
905
  return this;
706
906
  }
707
-
708
907
  /**
709
908
  * Add a WHERE clause with a BETWEEN condition
710
909
  * @param column The column name
711
910
  * @param twoValueArray The two values to be between
712
911
  */
713
- whereBetween(column: string, twoValueArray: [any, any]) {
912
+ whereBetween(column, twoValueArray) {
714
913
  if (!isNullish(twoValueArray[0]) && !isNullish(twoValueArray[1])) {
715
- this.where(column, 'BETWEEN', twoValueArray);
914
+ this.where(column, "BETWEEN", twoValueArray);
716
915
  } else if (!isNullish(twoValueArray[0]) && isNullish(twoValueArray[1])) {
717
- this.where(column, '>=', twoValueArray[0]);
916
+ this.where(column, ">=", twoValueArray[0]);
718
917
  } else if (isNullish(twoValueArray[0]) && !isNullish(twoValueArray[1])) {
719
- this.where(column, '<=', twoValueArray[1]);
918
+ this.where(column, "<=", twoValueArray[1]);
720
919
  } else {
721
- // both are nullish! omit expression
722
920
  }
723
921
  return this;
724
922
  }
725
-
726
923
  /**
727
924
  * Add a series of WHERE clauses joined by OR (See _conditions for usage)
728
925
  * @param conditions
729
926
  */
730
- _orConditions(
731
- conditions: WhereCriteria[] | Record<string, any>[] | Record<string, any>
732
- ) {
927
+ _orConditions(conditions) {
733
928
  const items = [];
734
929
  if (Array.isArray(conditions)) {
735
930
  for (const condition of conditions) {
736
931
  if (Array.isArray(condition)) {
737
- this._conditions(items, condition as WhereCriteria);
932
+ this._conditions(items, condition);
738
933
  } else {
739
934
  this._conditions(items, [condition]);
740
935
  }
@@ -746,47 +941,38 @@ export default class SelectBuilder {
746
941
  items.push(one[0]);
747
942
  }
748
943
  }
749
- const joined = items.join(' OR ');
750
- if (joined.startsWith('(') && joined.endsWith(')')) {
751
- // start/end paraens will be added by orWhere() or orHaving()
944
+ const joined = items.join(" OR ");
945
+ if (joined.startsWith("(") && joined.endsWith(")")) {
752
946
  return joined.slice(1, -1);
753
947
  } else {
754
948
  return joined;
755
949
  }
756
950
  }
757
-
758
951
  /**
759
952
  * Add WHERE conditions to place inside an OR block (See _conditions for usage)
760
953
  * @param conditions A list or object where each item is an array with parameters that would be taken by where()
761
954
  */
762
- orWhere(
763
- conditions: WhereCriteria[] | Record<string, any>[] | Record<string, any>
764
- ) {
955
+ orWhere(conditions) {
765
956
  const joined = this._orConditions(conditions);
766
957
  this.where(`(${joined})`);
767
958
  return this;
768
959
  }
769
-
770
960
  /**
771
961
  * Add an OR with conditions under the HAVING clause
772
962
  * @param conditions A list or object where each item is an array with parameters that would be taken by having()
773
963
  */
774
- orHaving(
775
- conditions: WhereCriteria[] | Record<string, any>[] | Record<string, any>
776
- ) {
964
+ orHaving(conditions) {
777
965
  const joined = this._orConditions(conditions);
778
966
  this.having(`(${joined})`);
779
967
  return this;
780
968
  }
781
-
782
969
  /**
783
970
  * Add a HAVING condition (See _conditions for usage)
784
971
  */
785
- having(...args: WhereCriteria) {
972
+ having(...args) {
786
973
  this._conditions(this._havings, args);
787
974
  return this;
788
975
  }
789
-
790
976
  /**
791
977
  * Add a column or expression to order by
792
978
  * @param column The column name or expression to sort by. Include DESC or prefix with - to sort descending
@@ -797,16 +983,15 @@ export default class SelectBuilder {
797
983
  * query.sortField('COUNT(*)'); // ORDER BY COUNT(*)
798
984
  * query.sortField('COUNT(*) DESC'); // ORDER BY COUNT(*) DESC
799
985
  */
800
- orderBy(column: string, direction: 'ASC' | 'DESC' = null) {
986
+ orderBy(column, direction = null) {
801
987
  if (direction) {
802
988
  this._orderBys.push(`${column} ${direction}`);
803
989
  } else {
804
- const expression = column.replace(/^-(.+)/, '$1 DESC');
990
+ const expression = column.replace(/^-(.+)/, "$1 DESC");
805
991
  this._orderBys.push(expression);
806
992
  }
807
993
  return this;
808
994
  }
809
-
810
995
  /**
811
996
  * Sort by the given column, with a map of columns to translate
812
997
  * @param fieldName The file name such as "created_at" or "-created_at" for descending
@@ -815,47 +1000,43 @@ export default class SelectBuilder {
815
1000
  * query.sortField('-modified_at'); // ORDER BY modified_at DESC
816
1001
  * query.sortField('-created', {'created': 'created_at'}); // ORDER BY created_at DESC
817
1002
  */
818
- sortField(fieldName: string, mapNames: Record<string, string> = {}) {
819
- let direction = 'ASC' as 'ASC' | 'DESC';
820
- if (fieldName.startsWith('-')) {
821
- direction = 'DESC';
1003
+ sortField(fieldName, mapNames = {}) {
1004
+ let direction = "ASC";
1005
+ if (fieldName.startsWith("-")) {
1006
+ direction = "DESC";
822
1007
  fieldName = fieldName.slice(1);
823
1008
  }
824
1009
  const column = mapNames[fieldName] || fieldName;
825
1010
  this.orderBy(column, direction);
826
1011
  return this;
827
1012
  }
828
-
829
1013
  /**
830
1014
  * Check to see if the given string is 0 or all digits not starting with 0
831
1015
  * @param string The input string
832
1016
  * @private
833
1017
  */
834
- _isEntirelyDigits(string: string) {
1018
+ _isEntirelyDigits(string) {
835
1019
  return /^(0|[1-9]\d*)$/.test(string);
836
1020
  }
837
-
838
1021
  /**
839
1022
  * Check to see if the given string is all digits
840
1023
  * @param string The input string
841
1024
  * @private
842
1025
  */
843
- _isEntirelyDigitsNoZeros(string: string) {
1026
+ _isEntirelyDigitsNoZeros(string) {
844
1027
  return /^[1-9]\d*$/.test(string);
845
1028
  }
846
-
847
1029
  /**
848
1030
  * Limit results to the given number
849
1031
  * @param max The number to limit by (placeholder string or integer greater than 0)
850
1032
  */
851
- limit(max: number | string) {
852
- if (typeof max === 'string') {
1033
+ limit(max) {
1034
+ if (typeof max === "string") {
853
1035
  max = max.trim();
854
1036
  if (this._isEntirelyDigitsNoZeros(max)) {
855
- // is entirely digits (no leading zeros)
856
1037
  this._limit = parseInt(max, 10);
857
1038
  }
858
- } else if (typeof max === 'number' && Number.isInteger(max) && max >= 1) {
1039
+ } else if (typeof max === "number" && Number.isInteger(max) && max >= 1) {
859
1040
  this._limit = max;
860
1041
  } else {
861
1042
  throw new Error(
@@ -864,20 +1045,14 @@ export default class SelectBuilder {
864
1045
  }
865
1046
  return this;
866
1047
  }
867
-
868
1048
  /**
869
1049
  * Fetch results from the given offset
870
1050
  * @param number The offset (placeholder string or integer greater than or equal to 0)
871
1051
  */
872
- offset(number: number | string) {
873
- if (typeof number === 'string' && this._isEntirelyDigits(number.trim())) {
874
- // is entirely digits (or zero)
1052
+ offset(number) {
1053
+ if (typeof number === "string" && this._isEntirelyDigits(number.trim())) {
875
1054
  this._offset = parseInt(number, 10);
876
- } else if (
877
- typeof number === 'number' &&
878
- Number.isInteger(number) &&
879
- number >= 0
880
- ) {
1055
+ } else if (typeof number === "number" && Number.isInteger(number) && number >= 0) {
881
1056
  this._offset = number;
882
1057
  } else {
883
1058
  throw new Error(
@@ -886,23 +1061,14 @@ export default class SelectBuilder {
886
1061
  }
887
1062
  return this;
888
1063
  }
889
-
890
1064
  /**
891
1065
  * Set the offset based on the limit with the given page number
892
1066
  * @param number The page number (integer greater than 0)
893
1067
  */
894
- page(number: number | string) {
895
- if (
896
- typeof number === 'string' &&
897
- this._isEntirelyDigitsNoZeros(number.trim())
898
- ) {
899
- // is entirely digits (or zero)
1068
+ page(number) {
1069
+ if (typeof number === "string" && this._isEntirelyDigitsNoZeros(number.trim())) {
900
1070
  this._page = parseInt(number, 10);
901
- } else if (
902
- typeof number === 'number' &&
903
- Number.isInteger(number) &&
904
- number >= 1
905
- ) {
1071
+ } else if (typeof number === "number" && Number.isInteger(number) && number >= 1) {
906
1072
  this._page = number;
907
1073
  } else {
908
1074
  throw new Error(
@@ -911,4 +1077,211 @@ export default class SelectBuilder {
911
1077
  }
912
1078
  return this;
913
1079
  }
1080
+ };
1081
+
1082
+ // src/extractGrouped/extractGrouped.ts
1083
+ function extractGrouped(field, records) {
1084
+ let idx = 0;
1085
+ const grouped = {};
1086
+ for (const record of records) {
1087
+ if (!(field in record)) {
1088
+ throw new Error(
1089
+ `Column "${String(field)}" is not present in record ${idx}`
1090
+ );
1091
+ }
1092
+ if (!grouped[record[field]]) {
1093
+ grouped[record[field]] = [];
1094
+ }
1095
+ grouped[record[field]].push(record);
1096
+ idx++;
1097
+ }
1098
+ return grouped;
1099
+ }
1100
+
1101
+ // src/extractIndexed/extractIndexed.ts
1102
+ function extractIndexed(field, records) {
1103
+ let idx = 0;
1104
+ const indexed = {};
1105
+ for (const record of records) {
1106
+ if (!(field in record)) {
1107
+ throw new Error(
1108
+ `Column "${String(field)}" is not present in record ${idx}`
1109
+ );
1110
+ }
1111
+ indexed[record[field]] = record;
1112
+ idx++;
1113
+ }
1114
+ return indexed;
1115
+ }
1116
+
1117
+ // src/extractLookup/extractLookup.ts
1118
+ function extractLookup(keyField, valueField, records) {
1119
+ const lookup = {};
1120
+ let idx = 0;
1121
+ for (const record of records) {
1122
+ if (!(keyField in record)) {
1123
+ throw new Error(
1124
+ `Column "${String(keyField)}" is not present in record ${idx}`
1125
+ );
1126
+ }
1127
+ if (!(valueField in record)) {
1128
+ throw new Error(
1129
+ `Column "${String(valueField)}" is not present in record ${idx}`
1130
+ );
1131
+ }
1132
+ idx++;
1133
+ lookup[String(record[keyField])] = record[valueField];
1134
+ }
1135
+ return lookup;
1136
+ }
1137
+
1138
+ // src/getPagination/getPagination.ts
1139
+ function getPagination(query, total) {
1140
+ total = Number(total) || 0;
1141
+ const perPage = Number(query._limit) || null;
1142
+ let page = null;
1143
+ if (total === 0) {
1144
+ page = null;
1145
+ } else if (query._page) {
1146
+ page = Number(query._page);
1147
+ } else if (query._limit && query._offset) {
1148
+ page = Math.floor(Number(query._offset) / Number(query._limit)) + 1;
1149
+ } else if (query._limit && !query._offset) {
1150
+ page = 1;
1151
+ } else {
1152
+ page = null;
1153
+ }
1154
+ const numPages = total === 0 || perPage === null ? 0 : Math.ceil(total / perPage) || 0;
1155
+ const isFirst = page === 1;
1156
+ const isLast = numPages > 0 && page === numPages;
1157
+ const prevPage = numPages === 0 ? null : page - 1 || null;
1158
+ const nextPage = numPages > 0 && page + 1 <= numPages ? page + 1 : null;
1159
+ return {
1160
+ page,
1161
+ prevPage,
1162
+ nextPage,
1163
+ perPage,
1164
+ numPages,
1165
+ total,
1166
+ isFirst,
1167
+ isLast
1168
+ };
1169
+ }
1170
+
1171
+ // src/runMysql/runMysql.ts
1172
+ function _queryToPromise(mysqlClient, sql, bindings) {
1173
+ return new Promise((resolve, reject) => {
1174
+ mysqlClient.query(sql, bindings, (err, rows, fields) => {
1175
+ if (err) {
1176
+ reject(err);
1177
+ return;
1178
+ }
1179
+ resolve([rows, fields]);
1180
+ });
1181
+ });
1182
+ }
1183
+ async function runMysql(mysqlClient, query) {
1184
+ const { sql, bindings } = query.compile({ engine: "mysql" });
1185
+ return _queryToPromise(mysqlClient, sql, bindings);
1186
+ }
1187
+ async function runMysqlWithCount(mysqlClient, query, { countExpr = "*" } = {}) {
1188
+ const { sql, bindings } = query.compile({ engine: "mysql" });
1189
+ const [records, fields] = await _queryToPromise(mysqlClient, sql, bindings);
1190
+ if (records.length > 0) {
1191
+ const { sql: sql2, bindings: bindings2 } = query.compileCount({
1192
+ engine: "mysql",
1193
+ countExpr
1194
+ });
1195
+ const [rows] = await _queryToPromise(mysqlClient, sql2, bindings2);
1196
+ const total = rows[0].found_rows;
1197
+ return { records, total, pagination: getPagination(query, total), fields };
1198
+ } else {
1199
+ return { records, total: 0, pagination: getPagination(query, 0), fields };
1200
+ }
1201
+ }
1202
+ async function runMysqlAsync(mysqlClient, query) {
1203
+ const { sql, bindings } = query.compile({ engine: "mysql" });
1204
+ return mysqlClient.query(sql, bindings);
1205
+ }
1206
+ async function runMysqlAsyncWithCount(mysqlClient, query, {
1207
+ engine = "mysql",
1208
+ countExpr = "*"
1209
+ } = {}) {
1210
+ const { sql, bindings } = query.compile({ engine });
1211
+ const [records, fields] = await mysqlClient.query(sql, bindings);
1212
+ if (records.length > 0) {
1213
+ const { sql: sql2, bindings: bindings2 } = query.compileCount({ engine, countExpr });
1214
+ const [rows] = await mysqlClient.query(sql2, bindings2);
1215
+ const total = rows[0].found_rows;
1216
+ return { records, total, pagination: getPagination(query, total), fields };
1217
+ } else {
1218
+ return { records, total: 0, pagination: getPagination(query, 0), fields };
1219
+ }
1220
+ }
1221
+
1222
+ // src/toSafeJson/toSafeJson.ts
1223
+ function toSafeJsonValue(value) {
1224
+ if (typeof value === "bigint") {
1225
+ const asNumber = Number(value);
1226
+ return value == asNumber ? asNumber : value.toString();
1227
+ }
1228
+ return value;
1229
+ }
1230
+ function toSafeJsonRecord(record) {
1231
+ const newRecord = {};
1232
+ for (const key in record) {
1233
+ if (!Object.prototype.hasOwnProperty.call(record, key)) {
1234
+ continue;
1235
+ }
1236
+ newRecord[key] = toSafeJsonValue(record[key]);
1237
+ }
1238
+ return newRecord;
1239
+ }
1240
+ function toSafeJsonRecords(records) {
1241
+ return records.map(toSafeJsonRecord);
1242
+ }
1243
+ function toSafeJsonString(records) {
1244
+ return JSON.stringify(records, (_, value) => toSafeJsonValue(value));
1245
+ }
1246
+
1247
+ // src/runPrisma/runPrisma.ts
1248
+ async function runPrisma(prisma, query, { engine = void 0 } = {}) {
1249
+ const { sql, bindings } = query.compile({ engine });
1250
+ return prisma.$queryRawUnsafe(sql, ...bindings);
1251
+ }
1252
+ async function runPrismaWithCount(prisma, query, {
1253
+ engine = void 0,
1254
+ countExpr = "*"
1255
+ } = {}) {
1256
+ const { sql, bindings } = query.compile({ engine });
1257
+ const records = await prisma.$queryRawUnsafe(
1258
+ sql,
1259
+ ...bindings
1260
+ );
1261
+ if (records.length > 0) {
1262
+ const { sql: sql2, bindings: bindings2 } = query.compileCount({ engine, countExpr });
1263
+ const result = await prisma.$queryRawUnsafe(sql2, ...bindings2);
1264
+ const total = toSafeJsonValue(result[0].found_rows);
1265
+ return { records, total, pagination: getPagination(query, total) };
1266
+ } else {
1267
+ return { records, total: 0, pagination: getPagination(query, 0) };
1268
+ }
914
1269
  }
1270
+ export {
1271
+ Parser,
1272
+ SelectBuilder,
1273
+ extractGrouped,
1274
+ extractIndexed,
1275
+ extractLookup,
1276
+ getPagination,
1277
+ runMysql,
1278
+ runMysqlAsync,
1279
+ runMysqlAsyncWithCount,
1280
+ runMysqlWithCount,
1281
+ runPrisma,
1282
+ runPrismaWithCount,
1283
+ toSafeJsonRecord,
1284
+ toSafeJsonRecords,
1285
+ toSafeJsonString,
1286
+ toSafeJsonValue
1287
+ };