sql-love 1.0.4 → 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.
package/dist/index.mjs CHANGED
@@ -1,16 +1,1287 @@
1
- var c=class{query;constructor(t){this.query=t}_stripComments(t){return t=t.replace(/\/\*[\s\S]*?\*\//g,""),t=t.replace(/--([\r\n]|\s+[^\r\n]+[\r\n])/g,""),t=t.replace(/#[^\r\n]+[\r\n]/g,""),t}_extractSubQueries(t){let e={},n=0,s=r=>{let o=`~~SUBQUERY_${n++}~~`;return e[o]=r,o};return t=t.replace(/\(\s*SELECT\s+.+\)\s+AS\s+[^\s,]+/,s),t=t.replace(/\bIF\s*\(.+\)\s+AS\s+[^\s,]+/,s),t=t.replace(/\bIN\s*\(\s*SELECT\s.+?\)/,s),{sql:t,subQueries:e}}_injectSubQueries(t){let e=s=>t[s]||s,n=s=>s.replace(/~~SUBQUERY_\d+~~/g,e);this.query._columns=this.query._columns.map(e),this.query._joins=this.query._joins.map(n),this.query._wheres=this.query._wheres.map(n),this.query._havings=this.query._havings.map(n)}_split(t){let e=/(?:^|\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;return t.split(e)}parse(t){let e=this._stripComments(t),{sql:n,subQueries:s}=this._extractSubQueries(e),r=this._split(n),o=1;for(;o<r.length;){let l=r[o++].trim().toUpperCase().replace(/\s+/g,"_"),h=r[o++].trim(),a=`_handle${l}`;this[a](h)}return this._injectSubQueries(s),!0}_handleSELECT(t){let e=t.split(/s*,s*/),n="";e.forEach((s,r)=>{if(r===0){let o=/^(SQL_CALC_FOUND_ROWS)\s+/i,u=s.match(o);u&&(this.query.option(u[1]),s=s.replace(o,""))}n.length?s.indexOf(")")>0?(n+=s,this.query.column(n.trim()),n=""):n+=s+",":s.match(/\([^)]+$/)?n=s+",":this.query.column(s.trim())})}_handleFROM(t){t.split(/\s*,\s*/).forEach(n=>this.query.table(n))}_handleINNER_JOIN(t){this.query.innerJoin(t)}_handleLEFT_JOIN(t){this.query.leftJoin(t)}_handleLEFT_OUTER_JOIN(t){this.query.leftOuterJoin(t)}_handleRIGHT_JOIN(t){this.query.rightJoin(t)}_handleRIGHT_OUTER_JOIN(t){this.query.rightOuterJoin(t)}_handleCROSS_JOIN(t){this.query.crossJoin(t)}_handleFULL_JOIN(t){this.query.fullJoin(t)}_handleFULL_OUTER_JOIN(t){this.query.fullOuterJoin(t)}_handleWHERE(t){/^(1|'1'|true)$/i.test(t)?this.query._wheres.push(t):this._handleConditions("where",t)}_handleHAVING(t){this._handleConditions("having",t)}_handleConditions(t,e){e.split(/\bAND\b/i).forEach(s=>{let r=s.split(/\bOR\b/i).map(o=>o.trim());if(r.length===1){let o=t;this.query[o](r[0])}else{let o=t==="where"?"orWhere":"orHaving";this.query[o](r.map(u=>[u]))}})}_handleGROUP_BY(t){t.split(/\s*,\s*/).forEach(n=>this.query.groupBy(n))}_handleORDER_BY(t){t.split(/\s*,\s*/).forEach(n=>this.query.orderBy(n))}_handleLIMIT(t){let e=t.match(/^(\d+|\?|:\w+)\s*,\s*(\d+|\?|:\w+)$/);e?(this.query.offset(e[1]),this.query.limit(e[2])):(t=t.replace(/ ROWS$/i,""),this.query.limit(t))}_handleOFFSET(t){t=t.replace(/^(\d+) ROWS$/i,"$1"),this.query.offset(t)}_handleFETCH_NEXT(t){t=t.replace(/^(\d+) ROWS ONLY$/i,"$1"),this.query.limit(t)}};function m(i,t){let e=0,n=i.indexOf(t);for(;n!==-1;)e+=1,n=i.indexOf(t,n+1);return e}var _=i=>i==null||i===!1||isNaN(i),f="mysql",y=class i{_bindings=[];_options=[];_tables=[];_columns=[];_joins=[];_wheres=[];_groupBys=[];_havings=[];_orderBys=[];_limit;_offset;_page;constructor(t=null,e=null){if(t){if(e){let n=Object.keys(e),s=new RegExp(`:(${n.join("|")})`,"g");t=t.replace(s,(r,o)=>(this._bindings.push(e[o]),`__BOUND_${this._bindings.length-1}__`))}new c(this).parse(t)}}static setDefaultEngine(t){return f=t,i}compile({engine:t=f,bindingIndex:e=1}={}){let n=["SELECT",this._options.length?` ${this._options.join(`
2
- `)}`:null,this._columns.length?` ${this._columns.join(`,
3
- `)}`:` *
4
- `,`FROM ${this._tables.join(", ")}`,this._joins.length?this._joins.join(`
5
- `):null,this._wheres.length?`WHERE ${this._wheres.join(`
6
- AND `)}`:null,this._groupBys.length?`GROUP BY ${this._groupBys.join(`,
7
- `)}`:null,this._havings.length?`HAVING ${this._havings.join(`
8
- AND `)}`:null,this._orderBys.length?`ORDER BY ${this._orderBys.join(`,
9
- `)}`:null];if(this._page>0){let l=(this._page-1)*this._limit;n.push(this._compileOFFSET(t,l)),n.push(this._compileLIMIT(t,this._limit))}else this._offset&&n.push(this._compileOFFSET(t,this._offset)),this._limit&&n.push(this._compileLIMIT(t,this._limit));let s=n.filter(Boolean).join(`
10
- `).trim(),r,o=[];if(t==="pg"){let l=e;r=(h,a)=>(o.push(this._bindings[a]),"$"+l++)}else r=(l,h)=>(o.push(this._bindings[h]),"?");return{sql:s.replace(/__BOUND_(\d+)__/g,r),bindings:o}}_compileLIMIT(t,e){if(t==="pg"||t==="mysql")return`LIMIT ${e}`;if(t==="mssql")return`FETCH NEXT ${e} ROWS ONLY`}_compileOFFSET(t,e){if(t==="pg"||t==="mysql")return`OFFSET ${e}`;if(t==="mssql"||t==="oracle")return`OFFSET ${e} ROWS`}compileCount({countExpr:t="*",engine:e=f,bindingIndex:n=1}={}){let s=this.getFoundRowsQuery(t),{sql:r,bindings:o}=s.compile({engine:e,bindingIndex:n});return this._havings.length===0?{sql:r,bindings:o}:{sql:`SELECT COUNT(*) AS found_rows
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
+ };
285
+
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
+ }
296
+
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 {
301
+ /**
302
+ * The list of values to bind to the query
303
+ * @private
304
+ */
305
+ _bindings = [];
306
+ /**
307
+ * The list of strings to come immediately after "SELECT"
308
+ * and before column names
309
+ * @private
310
+ */
311
+ _options = [];
312
+ /**
313
+ * The list of column names to select
314
+ * @private
315
+ */
316
+ _tables = [];
317
+ /**
318
+ * The list of tables in the FROM clause
319
+ * @private
320
+ */
321
+ _columns = [];
322
+ /**
323
+ * The list of JOIN strings to add
324
+ * @private
325
+ */
326
+ _joins = [];
327
+ /**
328
+ * The list of WHERE clauses to join with AND
329
+ * @private
330
+ */
331
+ _wheres = [];
332
+ /**
333
+ * The list of GROUP BY clauses to join with commas
334
+ * @private
335
+ */
336
+ _groupBys = [];
337
+ /**
338
+ * The list of HAVING clauses to join with AND
339
+ * @private
340
+ */
341
+ _havings = [];
342
+ /**
343
+ * The list of ORDER BY clauses to join with commas
344
+ * @private
345
+ */
346
+ _orderBys = [];
347
+ /**
348
+ * The LIMIT to use
349
+ * @private
350
+ */
351
+ _limit;
352
+ /**
353
+ * The OFFSET to use
354
+ * @private
355
+ */
356
+ _offset;
357
+ /**
358
+ * The page used to construct an OFFSET based on the LIMIT
359
+ * @private
360
+ */
361
+ _page;
362
+ /**
363
+ * Select constructor
364
+ * @param [sql] A base SQL query to parse
365
+ * @param [bindings] Placeholders to bind to the query
366
+ */
367
+ constructor(sql = null, bindings = null) {
368
+ if (sql) {
369
+ if (bindings) {
370
+ const keys = Object.keys(bindings);
371
+ const regexp = new RegExp(`:(${keys.join("|")})`, "g");
372
+ sql = sql.replace(regexp, ($0, $1) => {
373
+ this._bindings.push(bindings[$1]);
374
+ return `__BOUND_${this._bindings.length - 1}__`;
375
+ });
376
+ }
377
+ new Parser(this).parse(sql);
378
+ }
379
+ }
380
+ /**
381
+ * Set the default database engine style (mssql, mysql, oracle, pg)
382
+ * @param engine
383
+ */
384
+ static setDefaultEngine(engine) {
385
+ defaultEngine = engine;
386
+ return _SelectBuilder;
387
+ }
388
+ /**
389
+ * Get the SQL as a pretty-printed string alongside an array of bindings
390
+ * @param engine The database engine style (mssql, mysql, oracle, pg)
391
+ * @return The SQL query as a string
392
+ */
393
+ compile({
394
+ engine = defaultEngine,
395
+ bindingIndex = 1
396
+ } = {}) {
397
+ const lines = [
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
407
+ ];
408
+ if (this._page > 0) {
409
+ const offset = (this._page - 1) * this._limit;
410
+ lines.push(this._compileOFFSET(engine, offset));
411
+ lines.push(this._compileLIMIT(engine, this._limit));
412
+ } else {
413
+ if (this._offset) {
414
+ lines.push(this._compileOFFSET(engine, this._offset));
415
+ }
416
+ if (this._limit) {
417
+ lines.push(this._compileLIMIT(engine, this._limit));
418
+ }
419
+ }
420
+ const pretty = lines.filter(Boolean).join("\n").trim();
421
+ let replacer;
422
+ const bindings = [];
423
+ if (engine === "pg") {
424
+ let idx = bindingIndex;
425
+ replacer = (_, $1) => {
426
+ bindings.push(this._bindings[$1]);
427
+ return "$" + idx++;
428
+ };
429
+ } else {
430
+ replacer = (_, $1) => {
431
+ bindings.push(this._bindings[$1]);
432
+ return "?";
433
+ };
434
+ }
435
+ const sql = pretty.replace(/__BOUND_(\d+)__/g, replacer);
436
+ return { sql, bindings };
437
+ }
438
+ _compileLIMIT(engineStyle, limit) {
439
+ if (engineStyle === "pg" || engineStyle === "mysql") {
440
+ return `LIMIT ${limit}`;
441
+ } else if (engineStyle === "mssql") {
442
+ return `FETCH NEXT ${limit} ROWS ONLY`;
443
+ }
444
+ }
445
+ _compileOFFSET(engineStyle, offset) {
446
+ if (engineStyle === "pg" || engineStyle === "mysql") {
447
+ return `OFFSET ${offset}`;
448
+ } else if (engineStyle === "mssql" || engineStyle === "oracle") {
449
+ return `OFFSET ${offset} ROWS`;
450
+ }
451
+ }
452
+ /**
453
+ * Get SQL needed to return the found rows of this query
454
+ * @param engineStyle The database engine style (mssql, mysql, oracle, pg)
455
+ * @param countExpr The expression to use inside the COUNT()
456
+ */
457
+ compileCount({
458
+ countExpr = "*",
459
+ engine = defaultEngine,
460
+ bindingIndex = 1
461
+ } = {}) {
462
+ const query = this.getFoundRowsQuery(countExpr);
463
+ const { sql, bindings } = query.compile({ engine, bindingIndex });
464
+ if (this._havings.length === 0) {
465
+ return { sql, bindings };
466
+ } else {
467
+ const subquerySql = sql.replace(/\n/g, "\n ");
468
+ return {
469
+ sql: `SELECT COUNT(*) AS found_rows
11
470
  FROM (
12
- ${r.replace(/\n/g,`
13
- `)}
14
- ) AS subquery_results`,bindings:o}}compileUnion(t,{engine:e=f,bindingIndex:n=1}={}){let s=this.compile({engine:e,bindingIndex:n}),r=t.compile({engine:e,bindingIndex:s.bindings.length+n});return{sql:`(${s.sql})
471
+ ${subquerySql}
472
+ ) AS subquery_results`,
473
+ bindings
474
+ };
475
+ }
476
+ }
477
+ /**
478
+ * Get SQL needed to union this query with another
479
+ * @param query The query to union
480
+ * @param engine The database engine style (mssql, mysql, oracle, pg)
481
+ */
482
+ compileUnion(query, {
483
+ engine = defaultEngine,
484
+ bindingIndex = 1
485
+ } = {}) {
486
+ const first = this.compile({ engine, bindingIndex });
487
+ const second = query.compile({
488
+ engine,
489
+ bindingIndex: first.bindings.length + bindingIndex
490
+ });
491
+ return {
492
+ sql: `(${first.sql})
15
493
  UNION
16
- (${r.sql})`,bindings:[...s.bindings,...r.bindings]}}reset(t=null){if(Array.isArray(t))return t.forEach(e=>this.reset(e)),this;if(t){let e=["option","column","table","where","having","groupBy","orderBy"],n="_"+t.replace(/s$/,"");e.indexOf(t)>-1&&(n+="s");let s=["limit","offset","page"].indexOf(t)>-1;this[n]=s?null:[]}else this._options=[],this._columns=[],this._tables=[],this._joins=[],this._wheres=[],this._groupBys=[],this._havings=[],this._orderBys=[],this._limit=null,this._offset=null,this._page=null,this._bindings=[];return this}getClone(){let t=new i;return t._options=[...this._options],t._columns=[...this._columns],t._tables=[...this._tables],t._joins=[...this._joins],t._wheres=[...this._wheres],t._havings=[...this._havings],t._groupBys=[...this._groupBys],t._orderBys=[...this._orderBys],t._limit=this._limit,t._offset=this._offset,t._page=this._page,t._bindings=[...this._bindings],t}getFoundRowsQuery(t="*"){if(this._havings.length===0){let e=this.getClone();return e._columns=[`COUNT(${t}) AS found_rows`],e._options=[],e._groupBys=[],e._orderBys=[],e._limit=null,e._offset=null,e._page=null,e}else{let e=this.getClone();return e._limit=null,e._offset=null,e._page=null,e}}select(t){return this._columns=this._columns.concat(t),this}columns(t){return this._columns=this._columns.concat(t),this}column(t){return this._columns.push(t),this}option(t){return this._options.push(t),this}table(t){return this._tables.push(t),this}tables(t){return this._tables.push(...t),this}from(t){return this._tables.push(t),this}_addJoin(t,e,n=[]){n.length>0&&(e=e.replace(/\?/g,()=>(this._bindings.push(n.shift()),`__BOUND_${this._bindings.length-1}__`))),this._joins.push(`${t} ${e}`)}join(t,e=[]){return this._addJoin("JOIN",t,e),this}leftJoin(t,e=[]){return this._addJoin("LEFT JOIN",t,e),this}fullJoin(t,e=[]){return this._addJoin("FULL JOIN",t,e),this}rightJoin(t,e=[]){return this._addJoin("RIGHT JOIN",t,e),this}crossJoin(t,e=[]){return this._addJoin("CROSS JOIN",t,e),this}innerJoin(t,e=[]){return this._addJoin("INNER JOIN",t,e),this}leftOuterJoin(t,e=[]){return this._addJoin("LEFT OUTER JOIN",t,e),this}fullOuterJoin(t,e=[]){return this._addJoin("FULL OUTER JOIN",t,e),this}rightOuterJoin(t,e=[]){return this._addJoin("RIGHT OUTER JOIN",t,e),this}unjoin(t){return Array.isArray(t)?(t.forEach(e=>this.unjoin(e)),this):(this._joins=this._joins.filter(e=>!new RegExp(`JOIN ${t}\\b`).test(e)),this)}_conditions(t,e){let n=e.length,[s,r,o]=e;if(typeof s=="object"){for(let l of Object.entries(s))this._conditions(t,l);return this}if(n===1)return t.push(s),this;if(n===2&&Array.isArray(r)&&r.length>0&&m(s,"?")===r.length){let l=r,h=s.replace(/\?/g,()=>(this._bindings.push(l.shift()),`__BOUND_${this._bindings.length-1}__`));return t.push(h),this}else if(n===2){o=r;let l=s.split(" ");s=l.shift(),r=l.join(" ")}(typeof r!="string"||r==="")&&(r="="),r=r.toLocaleUpperCase();let u=r.match(/^(LIKE|NOT LIKE|ILIKE|NOT ILIKE)(?: (\?|\?%|%\?|%\?%))?$/i);if(r==="NOT BETWEEN"||r==="BETWEEN"){let l=this._bindings.length,h=this._bindings.length+1;t.push(`${s} ${r} __BOUND_${l}__ AND __BOUND_${h}__`),this._bindings.push(o[0],o[1])}else if(u){let l=u[1].toUpperCase(),h=u[2]||"?";if(Array.isArray(o)){let a=[];for(let T of o)a.push(`${s} ${l} __BOUND_${this._bindings.length}__`),this._bindings.push(h.replace("?",T));let d=a.join(" OR ");t.push(`(${d})`)}else t.push(`${s} ${l} __BOUND_${this._bindings.length}__`),this._bindings.push(h.replace("?",o||""))}else if(o===null)t.push(r==="="?`${s} IS NULL`:`${s} IS NOT NULL`);else if(Array.isArray(o)){let l=[];for(let a=0;a<o.length;a++)l.push(`__BOUND_${this._bindings.length}__`),this._bindings.push(o[a]);let h=l.join(", ");t.push(r==="="||r==="IN"?`${s} IN(${h})`:`${s} NOT IN(${h})`)}else r==="IN"||r==="NOT IN"?(t.push(`${s} ${r}(__BOUND_${this._bindings.length}__)`),this._bindings.push(o)):(t.push(`${s} ${r} __BOUND_${this._bindings.length}__`),this._bindings.push(o));return this}groupBy(t){return this._groupBys.push(t),this}where(...t){return this._conditions(this._wheres,t),this}whereBetween(t,e){return!_(e[0])&&!_(e[1])?this.where(t,"BETWEEN",e):!_(e[0])&&_(e[1])?this.where(t,">=",e[0]):_(e[0])&&!_(e[1])&&this.where(t,"<=",e[1]),this}_orConditions(t){let e=[];if(Array.isArray(t))for(let s of t)Array.isArray(s)?this._conditions(e,s):this._conditions(e,[s]);else for(let[s,r]of Object.entries(t)){let o=[];this._conditions(o,[s,r]),e.push(o[0])}let n=e.join(" OR ");return n.startsWith("(")&&n.endsWith(")")?n.slice(1,-1):n}orWhere(t){let e=this._orConditions(t);return this.where(`(${e})`),this}orHaving(t){let e=this._orConditions(t);return this.having(`(${e})`),this}having(...t){return this._conditions(this._havings,t),this}orderBy(t,e=null){if(e)this._orderBys.push(`${t} ${e}`);else{let n=t.replace(/^-(.+)/,"$1 DESC");this._orderBys.push(n)}return this}sortField(t,e={}){let n="ASC";t.startsWith("-")&&(n="DESC",t=t.slice(1));let s=e[t]||t;return this.orderBy(s,n),this}_isEntirelyDigits(t){return/^(0|[1-9]\d*)$/.test(t)}_isEntirelyDigitsNoZeros(t){return/^[1-9]\d*$/.test(t)}limit(t){if(typeof t=="string")t=t.trim(),this._isEntirelyDigitsNoZeros(t)&&(this._limit=parseInt(t,10));else if(typeof t=="number"&&Number.isInteger(t)&&t>=1)this._limit=t;else throw new Error(`SelectBuilder: "limit(n)" must receive a positive integer (got "${t}")`);return this}offset(t){if(typeof t=="string"&&this._isEntirelyDigits(t.trim()))this._offset=parseInt(t,10);else if(typeof t=="number"&&Number.isInteger(t)&&t>=0)this._offset=t;else throw new Error(`SelectBuilder: "offset(n)" must receive a positive integer (got "${t}")`);return this}page(t){if(typeof t=="string"&&this._isEntirelyDigitsNoZeros(t.trim()))this._page=parseInt(t,10);else if(typeof t=="number"&&Number.isInteger(t)&&t>=1)this._page=t;else throw new Error('SelectBuilder: "page(n)" must receive a positive integer');return this}};function S(i,t){let e=0,n={};for(let s of t){if(!(i in s))throw new Error(`Column "${String(i)}" is not present in record ${e}`);n[s[i]]||(n[s[i]]=[]),n[s[i]].push(s),e++}return n}function E(i,t){let e=0,n={};for(let s of t){if(!(i in s))throw new Error(`Column "${String(i)}" is not present in record ${e}`);n[s[i]]=s,e++}return n}function R(i,t,e){let n={},s=0;for(let r of e){if(!(i in r))throw new Error(`Column "${String(i)}" is not present in record ${s}`);if(!(t in r))throw new Error(`Column "${String(t)}" is not present in record ${s}`);s++,n[String(r[i])]=r[t]}return n}function g(i,t){t=Number(t)||0;let e=Number(i._limit)||null,n=null;t===0?n=null:i._page?n=Number(i._page):i._limit&&i._offset?n=Math.floor(Number(i._offset)/Number(i._limit))+1:i._limit&&!i._offset?n=1:n=null;let s=t===0||e===null?0:Math.ceil(t/e)||0,r=n===1,o=s>0&&n===s,u=s===0?null:n-1||null,l=s>0&&n+1<=s?n+1:null;return{page:n,prevPage:u,nextPage:l,perPage:e,numPages:s,total:t,isFirst:r,isLast:o}}function b(i,t,e){return new Promise((n,s)=>{i.query(t,e,(r,o,u)=>{if(r){s(r);return}n([o,u])})})}async function N(i,t){let{sql:e,bindings:n}=t.compile({engine:"mysql"});return b(i,e,n)}async function B(i,t,{countExpr:e="*"}={}){let{sql:n,bindings:s}=t.compile({engine:"mysql"}),[r,o]=await b(i,n,s);if(r.length>0){let{sql:u,bindings:l}=t.compileCount({engine:"mysql",countExpr:e}),[h]=await b(i,u,l),a=h[0].found_rows;return{records:r,total:a,pagination:g(t,a),fields:o}}else return{records:r,total:0,pagination:g(t,0),fields:o}}async function $(i,t){let{sql:e,bindings:n}=t.compile({engine:"mysql"});return i.query(e,n)}async function I(i,t,{engine:e="mysql",countExpr:n="*"}={}){let{sql:s,bindings:r}=t.compile({engine:e}),[o,u]=await i.query(s,r);if(o.length>0){let{sql:l,bindings:h}=t.compileCount({engine:e,countExpr:n}),[a]=await i.query(l,h),d=a[0].found_rows;return{records:o,total:d,pagination:g(t,d),fields:u}}else return{records:o,total:0,pagination:g(t,0),fields:u}}function p(i){if(typeof i=="bigint"){let t=Number(i);return i==t?t:i.toString()}return i}function O(i){let t={};for(let e in i)Object.prototype.hasOwnProperty.call(i,e)&&(t[e]=p(i[e]));return t}function C(i){return i.map(O)}function w(i){return JSON.stringify(i,(t,e)=>p(e))}async function x(i,t,{engine:e=void 0}={}){let{sql:n,bindings:s}=t.compile({engine:e});return i.$queryRawUnsafe(n,...s)}async function q(i,t,{engine:e=void 0,countExpr:n="*"}={}){let{sql:s,bindings:r}=t.compile({engine:e}),o=await i.$queryRawUnsafe(s,...r);if(o.length>0){let{sql:u,bindings:l}=t.compileCount({engine:e,countExpr:n}),h=await i.$queryRawUnsafe(u,...l),a=p(h[0].found_rows);return{records:o,total:a,pagination:g(t,a)}}else return{records:o,total:0,pagination:g(t,0)}}export{c as Parser,y as SelectBuilder,S as extractGrouped,E as extractIndexed,R as extractLookup,g as getPagination,N as runMysql,$ as runMysqlAsync,I as runMysqlAsyncWithCount,B as runMysqlWithCount,x as runPrisma,q as runPrismaWithCount,O as toSafeJsonRecord,C as toSafeJsonRecords,w as toSafeJsonString,p as toSafeJsonValue};
494
+ (${second.sql})`,
495
+ bindings: [...first.bindings, ...second.bindings]
496
+ };
497
+ }
498
+ /**
499
+ * @param [fieldOrFields] If given, reset the given component(s), otherwise reset all query components
500
+ * Valid components: option, column, table, where, orWhere, having, groupBy, orderBy, limit, offset, page
501
+ * @return This object
502
+ */
503
+ reset(fieldOrFields = null) {
504
+ if (Array.isArray(fieldOrFields)) {
505
+ fieldOrFields.forEach((name) => this.reset(name));
506
+ return this;
507
+ }
508
+ if (fieldOrFields) {
509
+ const pluralizable = [
510
+ "option",
511
+ "column",
512
+ "table",
513
+ "where",
514
+ "having",
515
+ "groupBy",
516
+ "orderBy"
517
+ ];
518
+ let prop = "_" + fieldOrFields.replace(/s$/, "");
519
+ if (pluralizable.indexOf(fieldOrFields) > -1) {
520
+ prop += "s";
521
+ }
522
+ const isScalar = ["limit", "offset", "page"].indexOf(fieldOrFields) > -1;
523
+ this[prop] = isScalar ? null : [];
524
+ } else {
525
+ this._options = [];
526
+ this._columns = [];
527
+ this._tables = [];
528
+ this._joins = [];
529
+ this._wheres = [];
530
+ this._groupBys = [];
531
+ this._havings = [];
532
+ this._orderBys = [];
533
+ this._limit = null;
534
+ this._offset = null;
535
+ this._page = null;
536
+ this._bindings = [];
537
+ }
538
+ return this;
539
+ }
540
+ /**
541
+ * Clone this builder object
542
+ */
543
+ getClone() {
544
+ const copy = new _SelectBuilder();
545
+ copy._options = [...this._options];
546
+ copy._columns = [...this._columns];
547
+ copy._tables = [...this._tables];
548
+ copy._joins = [...this._joins];
549
+ copy._wheres = [...this._wheres];
550
+ copy._havings = [...this._havings];
551
+ copy._groupBys = [...this._groupBys];
552
+ copy._orderBys = [...this._orderBys];
553
+ copy._limit = this._limit;
554
+ copy._offset = this._offset;
555
+ copy._page = this._page;
556
+ copy._bindings = [...this._bindings];
557
+ return copy;
558
+ }
559
+ /**
560
+ * Build a version of this query that simply returns COUNT(*)
561
+ * @param [countExpr="*"] Use to specify `DISTINCT colname` if needed
562
+ */
563
+ getFoundRowsQuery(countExpr = "*") {
564
+ if (this._havings.length === 0) {
565
+ const clone = this.getClone();
566
+ clone._columns = [`COUNT(${countExpr}) AS found_rows`];
567
+ clone._options = [];
568
+ clone._groupBys = [];
569
+ clone._orderBys = [];
570
+ clone._limit = null;
571
+ clone._offset = null;
572
+ clone._page = null;
573
+ return clone;
574
+ } else {
575
+ const subquery = this.getClone();
576
+ subquery._limit = null;
577
+ subquery._offset = null;
578
+ subquery._page = null;
579
+ return subquery;
580
+ }
581
+ }
582
+ /**
583
+ * Add an array of column names to fetch
584
+ * @param columnNames The names of columns
585
+ */
586
+ select(columnNames) {
587
+ this._columns = this._columns.concat(columnNames);
588
+ return this;
589
+ }
590
+ /**
591
+ * Add an array of column names to fetch
592
+ * @param columnNames The names of columns
593
+ */
594
+ columns(columnNames) {
595
+ this._columns = this._columns.concat(columnNames);
596
+ return this;
597
+ }
598
+ /**
599
+ * Add a column name to fetch
600
+ * @param columnName The name of the column
601
+ */
602
+ column(columnName) {
603
+ this._columns.push(columnName);
604
+ return this;
605
+ }
606
+ /**
607
+ * Add an option expression such as "TOP 10" or "SQL_CALC_FOUND_ROWS"
608
+ * @param optionExpression Expression to go after "SELECT" and before column list
609
+ */
610
+ option(optionExpression) {
611
+ this._options.push(optionExpression);
612
+ return this;
613
+ }
614
+ /**
615
+ * Add a table to the "FROM" clause (same as .from())
616
+ * @param {String} tableName The name of the table to query
617
+ * @return {Select}
618
+ */
619
+ table(tableName) {
620
+ this._tables.push(tableName);
621
+ return this;
622
+ }
623
+ /**
624
+ * Add multiple table to the "FROM" clause
625
+ * @param {Array} tableNames The names of the tables to query
626
+ * @return {Select}
627
+ */
628
+ tables(tableNames) {
629
+ this._tables.push(...tableNames);
630
+ return this;
631
+ }
632
+ /**
633
+ * Add a table to the "FROM" clause (same as .table())
634
+ * @param {String} tableName The name of the table to query
635
+ * @return {Select}
636
+ */
637
+ from(tableName) {
638
+ this._tables.push(tableName);
639
+ return this;
640
+ }
641
+ /**
642
+ * Add the given join type expression
643
+ * @param joinType The type of join (e.g. "INNER JOIN", "LEFT JOIN", etc.)
644
+ * @param expression The expression following the INNER JOIN keyword
645
+ * @param bindings A list of values to bind to ? placeholders in the expression
646
+ * @example
647
+ * query.join('posts p ON p.id = c.post_id');
648
+ * query.join('posts p ON p.id = c.post_id AND p.deleted_at < ?', [myDate]);
649
+ */
650
+ _addJoin(joinType, expression, bindings = []) {
651
+ if (bindings.length > 0) {
652
+ expression = expression.replace(/\?/g, () => {
653
+ this._bindings.push(bindings.shift());
654
+ return `__BOUND_${this._bindings.length - 1}__`;
655
+ });
656
+ }
657
+ this._joins.push(`${joinType} ${expression}`);
658
+ }
659
+ /**
660
+ * Add a JOIN expression
661
+ * @param expression The expression following the INNER JOIN keyword
662
+ * @param bindings A list of values to bind to ? placeholders in the expression
663
+ * @example
664
+ * query.join('posts p ON p.id = c.post_id');
665
+ * query.join('posts p ON p.id = c.post_id AND p.deleted_at < ?', [myDate]);
666
+ */
667
+ join(expression, bindings = []) {
668
+ this._addJoin("JOIN", expression, bindings);
669
+ return this;
670
+ }
671
+ /**
672
+ * Add a LEFT JOIN expression
673
+ * @param expression The expression following the LEFT JOIN keyword
674
+ * @param bindings A list of values to bind to ? placeholders in the expression
675
+ * @example
676
+ * query.leftJoin('posts p ON p.id = c.post_id');
677
+ * query.leftJoin('posts p ON p.id = c.post_id AND p.deleted_at < ?', [myDate]);
678
+ */
679
+ leftJoin(expression, bindings = []) {
680
+ this._addJoin("LEFT JOIN", expression, bindings);
681
+ return this;
682
+ }
683
+ /**
684
+ * Add a FULL JOIN expression
685
+ * @param expression The expression following the FULL JOIN keyword
686
+ * @param bindings A list of values to bind to ? placeholders in the expression
687
+ * @example
688
+ * query.fullJoin('posts p ON p.id = c.post_id');
689
+ * query.fullJoin('posts p ON p.id = c.post_id AND p.deleted_at < ?', [myDate]);
690
+ */
691
+ fullJoin(expression, bindings = []) {
692
+ this._addJoin("FULL JOIN", expression, bindings);
693
+ return this;
694
+ }
695
+ /**
696
+ * Add a RIGHT JOIN expression
697
+ * @param expression The expression following the RIGHT JOIN keyword
698
+ * @param bindings A list of values to bind to ? placeholders in the expression
699
+ * @example
700
+ * query.rightJoin('posts p ON p.id = c.post_id');
701
+ * query.rightJoin('posts p ON p.id = c.post_id AND p.deleted_at < ?', [myDate]);
702
+ */
703
+ rightJoin(expression, bindings = []) {
704
+ this._addJoin("RIGHT JOIN", expression, bindings);
705
+ return this;
706
+ }
707
+ /**
708
+ * Add a CROSS JOIN expression
709
+ * @param expression The expression following the CROSS JOIN keyword
710
+ * @param bindings A list of values to bind to ? placeholders in the expression
711
+ * @example
712
+ * query.crossJoin('posts p ON p.id = c.post_id');
713
+ * query.crossJoin('posts p ON p.id = c.post_id AND p.deleted_at < ?', [myDate]);
714
+ */
715
+ crossJoin(expression, bindings = []) {
716
+ this._addJoin("CROSS JOIN", expression, bindings);
717
+ return this;
718
+ }
719
+ /**
720
+ * Add an INNER JOIN expression (same as ->join())
721
+ * @param expression The expression following the INNER JOIN keyword
722
+ * @param bindings A list of values to bind to ? placeholders in the expression
723
+ * @example
724
+ * query.innerJoin('posts p ON p.id = c.post_id');
725
+ * query.innerJoin('posts p ON p.id = c.post_id AND p.deleted_at < ?', [myDate]);
726
+ */
727
+ innerJoin(expression, bindings = []) {
728
+ this._addJoin("INNER JOIN", expression, bindings);
729
+ return this;
730
+ }
731
+ /**
732
+ * Add a LEFT OUTER JOIN expression
733
+ * @param expression The expression following the LEFT OUTER JOIN keyword
734
+ * @param bindings A list of values to bind to ? placeholders in the expression
735
+ * @example
736
+ * query.leftOuterJoin('posts p ON p.id = c.post_id');
737
+ * query.leftOuterJoin('posts p ON p.id = c.post_id AND p.deleted_at < ?', [myDate]);
738
+ */
739
+ leftOuterJoin(expression, bindings = []) {
740
+ this._addJoin("LEFT OUTER JOIN", expression, bindings);
741
+ return this;
742
+ }
743
+ /**
744
+ * Add a FULL OUTER JOIN expression
745
+ * @param expression The expression following the FULL OUTER JOIN keyword
746
+ * @param bindings A list of values to bind to ? placeholders in the expression
747
+ * @example
748
+ * query.fullOuterJoin('posts p ON p.id = c.post_id');
749
+ * query.fullOuterJoin('posts p ON p.id = c.post_id AND p.deleted_at < ?', [myDate]);
750
+ */
751
+ fullOuterJoin(expression, bindings = []) {
752
+ this._addJoin("FULL OUTER JOIN", expression, bindings);
753
+ return this;
754
+ }
755
+ /**
756
+ * Add a RIGHT OUTER JOIN expression
757
+ * @param expression The expression following the RIGHT OUTER JOIN keyword
758
+ * @param bindings A list of values to bind to ? placeholders in the expression
759
+ * @example
760
+ * query.rightOuterJoin('posts p ON p.id = c.post_id');
761
+ * query.rightOuterJoin('posts p ON p.id = c.post_id AND p.deleted_at < ?', [myDate]);
762
+ */
763
+ rightOuterJoin(expression, bindings = []) {
764
+ this._addJoin("RIGHT OUTER JOIN", expression, bindings);
765
+ return this;
766
+ }
767
+ /**
768
+ * Remove a join condition with the specified table
769
+ * @param table The name of the table or tables in the first part of the join statement
770
+ */
771
+ unjoin(table) {
772
+ if (Array.isArray(table)) {
773
+ table.forEach((t) => this.unjoin(t));
774
+ return this;
775
+ }
776
+ this._joins = this._joins.filter((join) => {
777
+ const regex = new RegExp(`JOIN ${table}\\b`);
778
+ return !regex.test(join);
779
+ });
780
+ return this;
781
+ }
782
+ /**
783
+ * Utility function to add conditions for a clause (WHERE, HAVING)
784
+ * @param collection The collection to add the clauses to (e.g. this._wheres or this._havings)
785
+ * @param criteria Criteria adhering to WhereCriteria type
786
+ * @example The following are equivalent
787
+ * this._conditions(this._wheres, ['deleted_at IS NULL']);
788
+ * this._conditions(this._wheres, ['deleted_at', null]);
789
+ * this._conditions(this._wheres, ['deleted_at', '=', null]);
790
+ * @example More examples
791
+ * this._conditions(this._wheres, ['fname', 'LIKE', 'joe']); // `fname` LIKE 'joe'
792
+ * this._conditions(this._wheres, ['fname', 'LIKE ?', 'joe']); // `fname` LIKE 'joe'
793
+ * this._conditions(this._wheres, ['fname LIKE %?%', 'joe']); // `fname` LIKE '%joe%'
794
+ * this._conditions(this._wheres, ['fname LIKE ?%', 'joe']); // `fname` LIKE 'joe%'
795
+ * this._conditions(this._wheres, ['fname', 'LIKE ?%', 'joe']); // `fname` LIKE 'joe%'
796
+ * this._conditions(this._wheres, ['price >', 10]); // `price` > 10
797
+ * this._conditions(this._wheres, ['price', '>', 10]); // `price` > 10
798
+ * this._conditions(this._wheres, ['price =', 10]); // `price` = 10
799
+ * this._conditions(this._wheres, ['price !=', 10]); // `price` != 10
800
+ * this._conditions(this._wheres, ['price', 10]); // `price` = 10
801
+ * this._conditions(this._wheres, ['price', '=', 10]); // `price` = 10
802
+ * this._conditions(this._wheres, ['price', '!=', 10]); // `price` != 10
803
+ * this._conditions(this._wheres, ['price', 'BETWEEN', [10,20]]); // `price` BETWEEN 10 AND 20
804
+ * this._conditions(this._wheres, ['price', 'NOT BETWEEN', [10,20]]); // `price` NOT BETWEEN 10 AND 20
805
+ * this._conditions(this._wheres, ['price', [10,20]]); // `price` IN(10,20)
806
+ * this._conditions(this._wheres, ['price', '=', [10,20]]); // `price` IN(10,20)
807
+ * this._conditions(this._wheres, ['price', 'IN', [10,20]]); // `price` IN(10,20)
808
+ * this._conditions(this._wheres, ['price', 'NOT IN', [10,20]]); // `price` NOT IN(10,20)
809
+ */
810
+ _conditions(collection, criteria) {
811
+ const numArgs = criteria.length;
812
+ let [column, operator, value] = criteria;
813
+ if (typeof column === "object") {
814
+ for (const keyValue of Object.entries(column)) {
815
+ this._conditions(collection, keyValue);
816
+ }
817
+ return this;
818
+ }
819
+ if (numArgs === 1) {
820
+ collection.push(column);
821
+ return this;
822
+ } else if (numArgs === 2 && Array.isArray(operator) && operator.length > 0 && substrCount(column, "?") === operator.length) {
823
+ const values = operator;
824
+ const sql = column.replace(/\?/g, () => {
825
+ this._bindings.push(values.shift());
826
+ return `__BOUND_${this._bindings.length - 1}__`;
827
+ });
828
+ collection.push(sql);
829
+ return this;
830
+ } else if (numArgs === 2) {
831
+ value = operator;
832
+ const parts = column.split(" ");
833
+ column = parts.shift();
834
+ operator = parts.join(" ");
835
+ }
836
+ if (typeof operator !== "string" || operator === "") {
837
+ operator = "=";
838
+ }
839
+ operator = operator.toLocaleUpperCase();
840
+ const likeMatch = operator.match(
841
+ /^(LIKE|NOT LIKE|ILIKE|NOT ILIKE)(?: (\?|\?%|%\?|%\?%))?$/i
842
+ );
843
+ if (operator === "NOT BETWEEN" || operator === "BETWEEN") {
844
+ const one = this._bindings.length;
845
+ const two = this._bindings.length + 1;
846
+ collection.push(
847
+ `${column} ${operator} __BOUND_${one}__ AND __BOUND_${two}__`
848
+ );
849
+ this._bindings.push(value[0], value[1]);
850
+ } else if (likeMatch) {
851
+ const like = likeMatch[1].toUpperCase();
852
+ const infix = likeMatch[2] || "?";
853
+ if (Array.isArray(value)) {
854
+ const ors = [];
855
+ for (const v of value) {
856
+ ors.push(`${column} ${like} __BOUND_${this._bindings.length}__`);
857
+ this._bindings.push(infix.replace("?", v));
858
+ }
859
+ const joined = ors.join(" OR ");
860
+ collection.push(`(${joined})`);
861
+ } else {
862
+ collection.push(`${column} ${like} __BOUND_${this._bindings.length}__`);
863
+ this._bindings.push(infix.replace("?", value || ""));
864
+ }
865
+ } else if (value === null) {
866
+ collection.push(
867
+ operator === "=" ? `${column} IS NULL` : `${column} IS NOT NULL`
868
+ );
869
+ } else if (Array.isArray(value)) {
870
+ const inVals = [];
871
+ for (let i = 0; i < value.length; i++) {
872
+ inVals.push(`__BOUND_${this._bindings.length}__`);
873
+ this._bindings.push(value[i]);
874
+ }
875
+ const joined = inVals.join(", ");
876
+ collection.push(
877
+ operator === "=" || operator === "IN" ? `${column} IN(${joined})` : `${column} NOT IN(${joined})`
878
+ );
879
+ } else if (operator === "IN" || operator === "NOT IN") {
880
+ collection.push(
881
+ `${column} ${operator}(__BOUND_${this._bindings.length}__)`
882
+ );
883
+ this._bindings.push(value);
884
+ } else {
885
+ collection.push(
886
+ `${column} ${operator} __BOUND_${this._bindings.length}__`
887
+ );
888
+ this._bindings.push(value);
889
+ }
890
+ return this;
891
+ }
892
+ /**
893
+ * Add a group by column or expression
894
+ * @param column The name of a column (or expression) to group by
895
+ */
896
+ groupBy(column) {
897
+ this._groupBys.push(column);
898
+ return this;
899
+ }
900
+ /**
901
+ * Add WHERE clauses to conditions (See _conditions for usage)
902
+ */
903
+ where(...args) {
904
+ this._conditions(this._wheres, args);
905
+ return this;
906
+ }
907
+ /**
908
+ * Add a WHERE clause with a BETWEEN condition
909
+ * @param column The column name
910
+ * @param twoValueArray The two values to be between
911
+ */
912
+ whereBetween(column, twoValueArray) {
913
+ if (!isNullish(twoValueArray[0]) && !isNullish(twoValueArray[1])) {
914
+ this.where(column, "BETWEEN", twoValueArray);
915
+ } else if (!isNullish(twoValueArray[0]) && isNullish(twoValueArray[1])) {
916
+ this.where(column, ">=", twoValueArray[0]);
917
+ } else if (isNullish(twoValueArray[0]) && !isNullish(twoValueArray[1])) {
918
+ this.where(column, "<=", twoValueArray[1]);
919
+ } else {
920
+ }
921
+ return this;
922
+ }
923
+ /**
924
+ * Add a series of WHERE clauses joined by OR (See _conditions for usage)
925
+ * @param conditions
926
+ */
927
+ _orConditions(conditions) {
928
+ const items = [];
929
+ if (Array.isArray(conditions)) {
930
+ for (const condition of conditions) {
931
+ if (Array.isArray(condition)) {
932
+ this._conditions(items, condition);
933
+ } else {
934
+ this._conditions(items, [condition]);
935
+ }
936
+ }
937
+ } else {
938
+ for (const [key, value] of Object.entries(conditions)) {
939
+ const one = [];
940
+ this._conditions(one, [key, value]);
941
+ items.push(one[0]);
942
+ }
943
+ }
944
+ const joined = items.join(" OR ");
945
+ if (joined.startsWith("(") && joined.endsWith(")")) {
946
+ return joined.slice(1, -1);
947
+ } else {
948
+ return joined;
949
+ }
950
+ }
951
+ /**
952
+ * Add WHERE conditions to place inside an OR block (See _conditions for usage)
953
+ * @param conditions A list or object where each item is an array with parameters that would be taken by where()
954
+ */
955
+ orWhere(conditions) {
956
+ const joined = this._orConditions(conditions);
957
+ this.where(`(${joined})`);
958
+ return this;
959
+ }
960
+ /**
961
+ * Add an OR with conditions under the HAVING clause
962
+ * @param conditions A list or object where each item is an array with parameters that would be taken by having()
963
+ */
964
+ orHaving(conditions) {
965
+ const joined = this._orConditions(conditions);
966
+ this.having(`(${joined})`);
967
+ return this;
968
+ }
969
+ /**
970
+ * Add a HAVING condition (See _conditions for usage)
971
+ */
972
+ having(...args) {
973
+ this._conditions(this._havings, args);
974
+ return this;
975
+ }
976
+ /**
977
+ * Add a column or expression to order by
978
+ * @param column The column name or expression to sort by. Include DESC or prefix with - to sort descending
979
+ * @param [direction] The direction to sort by (ASC or DESC)
980
+ * @example
981
+ * query.sortField('modified_at'); // ORDER BY modified_at
982
+ * query.sortField('-modified_at'); // ORDER BY modified_at DESC
983
+ * query.sortField('COUNT(*)'); // ORDER BY COUNT(*)
984
+ * query.sortField('COUNT(*) DESC'); // ORDER BY COUNT(*) DESC
985
+ */
986
+ orderBy(column, direction = null) {
987
+ if (direction) {
988
+ this._orderBys.push(`${column} ${direction}`);
989
+ } else {
990
+ const expression = column.replace(/^-(.+)/, "$1 DESC");
991
+ this._orderBys.push(expression);
992
+ }
993
+ return this;
994
+ }
995
+ /**
996
+ * Sort by the given column, with a map of columns to translate
997
+ * @param fieldName The file name such as "created_at" or "-created_at" for descending
998
+ * @param [mapNames] Column names to translate from field to column expression
999
+ * @example
1000
+ * query.sortField('-modified_at'); // ORDER BY modified_at DESC
1001
+ * query.sortField('-created', {'created': 'created_at'}); // ORDER BY created_at DESC
1002
+ */
1003
+ sortField(fieldName, mapNames = {}) {
1004
+ let direction = "ASC";
1005
+ if (fieldName.startsWith("-")) {
1006
+ direction = "DESC";
1007
+ fieldName = fieldName.slice(1);
1008
+ }
1009
+ const column = mapNames[fieldName] || fieldName;
1010
+ this.orderBy(column, direction);
1011
+ return this;
1012
+ }
1013
+ /**
1014
+ * Check to see if the given string is 0 or all digits not starting with 0
1015
+ * @param string The input string
1016
+ * @private
1017
+ */
1018
+ _isEntirelyDigits(string) {
1019
+ return /^(0|[1-9]\d*)$/.test(string);
1020
+ }
1021
+ /**
1022
+ * Check to see if the given string is all digits
1023
+ * @param string The input string
1024
+ * @private
1025
+ */
1026
+ _isEntirelyDigitsNoZeros(string) {
1027
+ return /^[1-9]\d*$/.test(string);
1028
+ }
1029
+ /**
1030
+ * Limit results to the given number
1031
+ * @param max The number to limit by (placeholder string or integer greater than 0)
1032
+ */
1033
+ limit(max) {
1034
+ if (typeof max === "string") {
1035
+ max = max.trim();
1036
+ if (this._isEntirelyDigitsNoZeros(max)) {
1037
+ this._limit = parseInt(max, 10);
1038
+ }
1039
+ } else if (typeof max === "number" && Number.isInteger(max) && max >= 1) {
1040
+ this._limit = max;
1041
+ } else {
1042
+ throw new Error(
1043
+ `SelectBuilder: "limit(n)" must receive a positive integer (got "${max}")`
1044
+ );
1045
+ }
1046
+ return this;
1047
+ }
1048
+ /**
1049
+ * Fetch results from the given offset
1050
+ * @param number The offset (placeholder string or integer greater than or equal to 0)
1051
+ */
1052
+ offset(number) {
1053
+ if (typeof number === "string" && this._isEntirelyDigits(number.trim())) {
1054
+ this._offset = parseInt(number, 10);
1055
+ } else if (typeof number === "number" && Number.isInteger(number) && number >= 0) {
1056
+ this._offset = number;
1057
+ } else {
1058
+ throw new Error(
1059
+ `SelectBuilder: "offset(n)" must receive a positive integer (got "${number}")`
1060
+ );
1061
+ }
1062
+ return this;
1063
+ }
1064
+ /**
1065
+ * Set the offset based on the limit with the given page number
1066
+ * @param number The page number (integer greater than 0)
1067
+ */
1068
+ page(number) {
1069
+ if (typeof number === "string" && this._isEntirelyDigitsNoZeros(number.trim())) {
1070
+ this._page = parseInt(number, 10);
1071
+ } else if (typeof number === "number" && Number.isInteger(number) && number >= 1) {
1072
+ this._page = number;
1073
+ } else {
1074
+ throw new Error(
1075
+ 'SelectBuilder: "page(n)" must receive a positive integer'
1076
+ );
1077
+ }
1078
+ return this;
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
+ }
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
+ };