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