sql-love 1.1.0 → 1.1.2
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/LICENSE.md +1 -1
- package/README.md +7 -9
- package/dist/index.cjs +574 -110
- package/dist/index.d.ts +9 -9
- package/dist/index.mjs +541 -86
- package/package.json +11 -9
- package/dist/.gitkeep +0 -0
package/dist/index.mjs
CHANGED
|
@@ -1,15 +1,31 @@
|
|
|
1
1
|
// src/Parser/Parser.ts
|
|
2
|
-
|
|
2
|
+
var Parser = class {
|
|
3
3
|
query;
|
|
4
|
+
/**
|
|
5
|
+
* Create a new instance
|
|
6
|
+
* @param query A Select object on which to build parsed conditions
|
|
7
|
+
*/
|
|
4
8
|
constructor(query) {
|
|
5
9
|
this.query = query;
|
|
6
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
|
+
*/
|
|
7
17
|
_stripComments(sql) {
|
|
8
18
|
sql = sql.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
9
19
|
sql = sql.replace(/--([\r\n]|\s+[^\r\n]+[\r\n])/g, "");
|
|
10
20
|
sql = sql.replace(/#[^\r\n]+[\r\n]/g, "");
|
|
11
21
|
return sql;
|
|
12
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
|
+
*/
|
|
13
29
|
_extractSubQueries(sql) {
|
|
14
30
|
const subQueries = {};
|
|
15
31
|
let i = 0;
|
|
@@ -23,6 +39,11 @@ class Parser {
|
|
|
23
39
|
sql = sql.replace(/\bIN\s*\(\s*SELECT\s.+?\)/, extractor);
|
|
24
40
|
return { sql, subQueries };
|
|
25
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Inject column subQueries back into this object
|
|
44
|
+
* @param subQueries The lookup of extracted subQueries
|
|
45
|
+
* @private
|
|
46
|
+
*/
|
|
26
47
|
_injectSubQueries(subQueries) {
|
|
27
48
|
const replacer = ($0) => {
|
|
28
49
|
return subQueries[$0] || $0;
|
|
@@ -35,10 +56,21 @@ class Parser {
|
|
|
35
56
|
this.query._wheres = this.query._wheres.map(mapper);
|
|
36
57
|
this.query._havings = this.query._havings.map(mapper);
|
|
37
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Split SQL into clauses (used by parse())
|
|
61
|
+
* @param sql The SQL to split
|
|
62
|
+
* @return
|
|
63
|
+
* @private
|
|
64
|
+
*/
|
|
38
65
|
_split(sql) {
|
|
39
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;
|
|
40
67
|
return sql.split(splitter);
|
|
41
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
|
+
*/
|
|
42
74
|
parse(rawSql) {
|
|
43
75
|
const stripped = this._stripComments(rawSql);
|
|
44
76
|
const { sql, subQueries } = this._extractSubQueries(stripped);
|
|
@@ -49,11 +81,16 @@ class Parser {
|
|
|
49
81
|
const keyword = rawKeyword.toUpperCase().replace(/\s+/g, "_");
|
|
50
82
|
const clause = expressions[i++].trim();
|
|
51
83
|
const handler = `_handle${keyword}`;
|
|
52
|
-
this[handler](clause);
|
|
84
|
+
this[handler]?.(clause);
|
|
53
85
|
}
|
|
54
86
|
this._injectSubQueries(subQueries);
|
|
55
87
|
return true;
|
|
56
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Handle SQL_CALC_FOUND_ROWS and column names
|
|
91
|
+
* @param clause The clause after the SELECT
|
|
92
|
+
* @private
|
|
93
|
+
*/
|
|
57
94
|
_handleSELECT(clause) {
|
|
58
95
|
let columns = clause.split(/s*,s*/);
|
|
59
96
|
let buffer = "";
|
|
@@ -81,34 +118,84 @@ class Parser {
|
|
|
81
118
|
}
|
|
82
119
|
});
|
|
83
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* Handle table names
|
|
123
|
+
* @param clause The clause after the FROM
|
|
124
|
+
* @private
|
|
125
|
+
*/
|
|
84
126
|
_handleFROM(clause) {
|
|
85
127
|
const tables = clause.split(/\s*,\s*/);
|
|
86
128
|
tables.forEach((table) => this.query.table(table));
|
|
87
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* Handle INNER JOIN statements
|
|
132
|
+
* @param clause The clause after the INNER JOIN
|
|
133
|
+
* @private
|
|
134
|
+
*/
|
|
88
135
|
_handleINNER_JOIN(clause) {
|
|
89
136
|
this.query.innerJoin(clause);
|
|
90
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Handle LEFT JOIN statements
|
|
140
|
+
* @param clause The clause after the LEFT JOIN
|
|
141
|
+
* @private
|
|
142
|
+
*/
|
|
91
143
|
_handleLEFT_JOIN(clause) {
|
|
92
144
|
this.query.leftJoin(clause);
|
|
93
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Handle LEFT OUTER JOIN statements
|
|
148
|
+
* @param clause The clause after the LEFT OUTER JOIN
|
|
149
|
+
* @private
|
|
150
|
+
*/
|
|
94
151
|
_handleLEFT_OUTER_JOIN(clause) {
|
|
95
152
|
this.query.leftOuterJoin(clause);
|
|
96
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* Handle RIGHT JOIN statements
|
|
156
|
+
* @param clause The clause after the RIGHT JOIN
|
|
157
|
+
* @private
|
|
158
|
+
*/
|
|
97
159
|
_handleRIGHT_JOIN(clause) {
|
|
98
160
|
this.query.rightJoin(clause);
|
|
99
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Handle RIGHT OUTER JOIN statements
|
|
164
|
+
* @param clause The clause after the RIGHT OUTER JOIN
|
|
165
|
+
* @private
|
|
166
|
+
*/
|
|
100
167
|
_handleRIGHT_OUTER_JOIN(clause) {
|
|
101
168
|
this.query.rightOuterJoin(clause);
|
|
102
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* Handle CROSS JOIN statements
|
|
172
|
+
* @param clause The clause after the CROSS JOIN
|
|
173
|
+
* @private
|
|
174
|
+
*/
|
|
103
175
|
_handleCROSS_JOIN(clause) {
|
|
104
176
|
this.query.crossJoin(clause);
|
|
105
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Handle FULL JOIN statements
|
|
180
|
+
* @param clause The clause after the FULL JOIN
|
|
181
|
+
* @private
|
|
182
|
+
*/
|
|
106
183
|
_handleFULL_JOIN(clause) {
|
|
107
184
|
this.query.fullJoin(clause);
|
|
108
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Handle FULL OUTER JOIN statements
|
|
188
|
+
* @param clause The clause after the FULL OUTER JOIN
|
|
189
|
+
* @private
|
|
190
|
+
*/
|
|
109
191
|
_handleFULL_OUTER_JOIN(clause) {
|
|
110
192
|
this.query.fullOuterJoin(clause);
|
|
111
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* Handle WHERE conditions
|
|
196
|
+
* @param clause All the conditions after WHERE
|
|
197
|
+
* @private
|
|
198
|
+
*/
|
|
112
199
|
_handleWHERE(clause) {
|
|
113
200
|
if (/^(1|'1'|true)$/i.test(clause)) {
|
|
114
201
|
this.query._wheres.push(clause);
|
|
@@ -116,9 +203,20 @@ class Parser {
|
|
|
116
203
|
this._handleConditions("where", clause);
|
|
117
204
|
}
|
|
118
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* Handle HAVING statements
|
|
208
|
+
* @param clause All the conditions after HAVING
|
|
209
|
+
* @private
|
|
210
|
+
*/
|
|
119
211
|
_handleHAVING(clause) {
|
|
120
212
|
this._handleConditions("having", clause);
|
|
121
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
|
+
*/
|
|
122
220
|
_handleConditions(type, clause) {
|
|
123
221
|
const andGroups = clause.split(/\bAND\b/i);
|
|
124
222
|
andGroups.forEach((andGroup) => {
|
|
@@ -132,14 +230,29 @@ class Parser {
|
|
|
132
230
|
}
|
|
133
231
|
});
|
|
134
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* Handle GROUP BY statements
|
|
235
|
+
* @param clause The clauses after the GROUP BY
|
|
236
|
+
* @private
|
|
237
|
+
*/
|
|
135
238
|
_handleGROUP_BY(clause) {
|
|
136
239
|
const columns = clause.split(/\s*,\s*/);
|
|
137
240
|
columns.forEach((column) => this.query.groupBy(column));
|
|
138
241
|
}
|
|
242
|
+
/**
|
|
243
|
+
* Handle ORDER BY statements
|
|
244
|
+
* @param clause The clause after the ORDER BY
|
|
245
|
+
* @private
|
|
246
|
+
*/
|
|
139
247
|
_handleORDER_BY(clause) {
|
|
140
248
|
const columns = clause.split(/\s*,\s*/);
|
|
141
249
|
columns.forEach((column) => this.query.orderBy(column));
|
|
142
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* Handle LIMIT statements including "LIMIT #" and "LIMIT #, #"
|
|
253
|
+
* @param clause The clause after the LIMIT
|
|
254
|
+
* @private
|
|
255
|
+
*/
|
|
143
256
|
_handleLIMIT(clause) {
|
|
144
257
|
const offsetLimit = clause.match(/^(\d+|\?|:\w+)\s*,\s*(\d+|\?|:\w+)$/);
|
|
145
258
|
if (offsetLimit) {
|
|
@@ -150,15 +263,26 @@ class Parser {
|
|
|
150
263
|
this.query.limit(clause);
|
|
151
264
|
}
|
|
152
265
|
}
|
|
266
|
+
/**
|
|
267
|
+
* Handle OFFSET statements
|
|
268
|
+
* @param clause The number after the OFFSET
|
|
269
|
+
* @private
|
|
270
|
+
*/
|
|
153
271
|
_handleOFFSET(clause) {
|
|
154
272
|
clause = clause.replace(/^(\d+) ROWS$/i, "$1");
|
|
155
273
|
this.query.offset(clause);
|
|
156
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* Handle OFFSET statements
|
|
277
|
+
* @param clause The number after the OFFSET
|
|
278
|
+
* @private
|
|
279
|
+
*/
|
|
157
280
|
_handleFETCH_NEXT(clause) {
|
|
158
281
|
clause = clause.replace(/^(\d+) ROWS ONLY$/i, "$1");
|
|
159
282
|
this.query.limit(clause);
|
|
160
283
|
}
|
|
161
|
-
}
|
|
284
|
+
};
|
|
285
|
+
|
|
162
286
|
// src/substrCount/substrCount.ts
|
|
163
287
|
function substrCount(haystack, needle) {
|
|
164
288
|
let count = 0;
|
|
@@ -171,23 +295,76 @@ function substrCount(haystack, needle) {
|
|
|
171
295
|
}
|
|
172
296
|
|
|
173
297
|
// src/SelectBuilder/SelectBuilder.ts
|
|
174
|
-
var isNullish = (v) => v ===
|
|
298
|
+
var isNullish = (v) => v === void 0 || v === null || v === false || Object.is(NaN, v);
|
|
175
299
|
var defaultEngine = "mysql";
|
|
176
|
-
|
|
177
|
-
|
|
300
|
+
var SelectBuilder = class _SelectBuilder {
|
|
301
|
+
/**
|
|
302
|
+
* The list of values to bind to the query
|
|
303
|
+
* @private
|
|
304
|
+
*/
|
|
178
305
|
_bindings = [];
|
|
306
|
+
/**
|
|
307
|
+
* The list of strings to come immediately after "SELECT"
|
|
308
|
+
* and before column names
|
|
309
|
+
* @private
|
|
310
|
+
*/
|
|
179
311
|
_options = [];
|
|
312
|
+
/**
|
|
313
|
+
* The list of column names to select
|
|
314
|
+
* @private
|
|
315
|
+
*/
|
|
180
316
|
_tables = [];
|
|
317
|
+
/**
|
|
318
|
+
* The list of tables in the FROM clause
|
|
319
|
+
* @private
|
|
320
|
+
*/
|
|
181
321
|
_columns = [];
|
|
322
|
+
/**
|
|
323
|
+
* The list of JOIN strings to add
|
|
324
|
+
* @private
|
|
325
|
+
*/
|
|
182
326
|
_joins = [];
|
|
327
|
+
/**
|
|
328
|
+
* The list of WHERE clauses to join with AND
|
|
329
|
+
* @private
|
|
330
|
+
*/
|
|
183
331
|
_wheres = [];
|
|
332
|
+
/**
|
|
333
|
+
* The list of GROUP BY clauses to join with commas
|
|
334
|
+
* @private
|
|
335
|
+
*/
|
|
184
336
|
_groupBys = [];
|
|
337
|
+
/**
|
|
338
|
+
* The list of HAVING clauses to join with AND
|
|
339
|
+
* @private
|
|
340
|
+
*/
|
|
185
341
|
_havings = [];
|
|
342
|
+
/**
|
|
343
|
+
* The list of ORDER BY clauses to join with commas
|
|
344
|
+
* @private
|
|
345
|
+
*/
|
|
186
346
|
_orderBys = [];
|
|
347
|
+
/**
|
|
348
|
+
* The LIMIT to use
|
|
349
|
+
* @private
|
|
350
|
+
*/
|
|
187
351
|
_limit;
|
|
352
|
+
/**
|
|
353
|
+
* The OFFSET to use
|
|
354
|
+
* @private
|
|
355
|
+
*/
|
|
188
356
|
_offset;
|
|
357
|
+
/**
|
|
358
|
+
* The page used to construct an OFFSET based on the LIMIT
|
|
359
|
+
* @private
|
|
360
|
+
*/
|
|
189
361
|
_page;
|
|
190
|
-
|
|
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, bindings = {}) {
|
|
191
368
|
if (sql) {
|
|
192
369
|
if (bindings) {
|
|
193
370
|
const keys = Object.keys(bindings);
|
|
@@ -200,58 +377,58 @@ class SelectBuilder {
|
|
|
200
377
|
new Parser(this).parse(sql);
|
|
201
378
|
}
|
|
202
379
|
}
|
|
380
|
+
/**
|
|
381
|
+
* Set the default database engine style (mssql, mysql, oracle, pg)
|
|
382
|
+
* @param engine
|
|
383
|
+
*/
|
|
203
384
|
static setDefaultEngine(engine) {
|
|
204
385
|
defaultEngine = engine;
|
|
205
|
-
return
|
|
386
|
+
return _SelectBuilder;
|
|
206
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
|
+
*/
|
|
207
393
|
compile({
|
|
208
394
|
engine = defaultEngine,
|
|
209
395
|
bindingIndex = 1
|
|
210
396
|
} = {}) {
|
|
211
397
|
const lines = [
|
|
212
398
|
"SELECT",
|
|
213
|
-
this._options.length ? ` ${this._options.join(`
|
|
214
|
-
|
|
215
|
-
this._columns.length ? ` ${this._columns.join(`,
|
|
216
|
-
`)}` : ` *
|
|
217
|
-
`,
|
|
399
|
+
this._options.length ? ` ${this._options.join("\n ")}` : null,
|
|
400
|
+
this._columns.length ? ` ${this._columns.join(",\n ")}` : " *\n",
|
|
218
401
|
`FROM ${this._tables.join(", ")}`,
|
|
219
|
-
this._joins.length ? this._joins.join(
|
|
220
|
-
`) : null,
|
|
221
|
-
this.
|
|
222
|
-
AND
|
|
223
|
-
this.
|
|
224
|
-
`)}` : null,
|
|
225
|
-
this._havings.length ? `HAVING ${this._havings.join(`
|
|
226
|
-
AND `)}` : null,
|
|
227
|
-
this._orderBys.length ? `ORDER BY ${this._orderBys.join(`,
|
|
228
|
-
`)}` : null
|
|
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
|
|
229
407
|
];
|
|
230
|
-
if (this._page > 0) {
|
|
408
|
+
if (this._page && this._page > 0 && this._limit) {
|
|
231
409
|
const offset = (this._page - 1) * this._limit;
|
|
232
|
-
lines.push(this._compileOFFSET(engine, offset));
|
|
233
|
-
lines.push(this._compileLIMIT(engine, this._limit));
|
|
410
|
+
lines.push(this._compileOFFSET(engine, offset) ?? "");
|
|
411
|
+
lines.push(this._compileLIMIT(engine, this._limit) ?? "");
|
|
234
412
|
} else {
|
|
235
413
|
if (this._offset) {
|
|
236
|
-
lines.push(this._compileOFFSET(engine, this._offset));
|
|
414
|
+
lines.push(this._compileOFFSET(engine, this._offset) ?? "");
|
|
237
415
|
}
|
|
238
416
|
if (this._limit) {
|
|
239
|
-
lines.push(this._compileLIMIT(engine, this._limit));
|
|
417
|
+
lines.push(this._compileLIMIT(engine, this._limit) ?? "");
|
|
240
418
|
}
|
|
241
419
|
}
|
|
242
|
-
const pretty = lines.filter(Boolean).join(
|
|
243
|
-
`).trim();
|
|
420
|
+
const pretty = lines.filter(Boolean).join("\n").trim();
|
|
244
421
|
let replacer;
|
|
245
422
|
const bindings = [];
|
|
246
423
|
if (engine === "pg") {
|
|
247
424
|
let idx = bindingIndex;
|
|
248
425
|
replacer = (_, $1) => {
|
|
249
|
-
bindings.push(this._bindings[$1]);
|
|
426
|
+
bindings.push(this._bindings[Number($1)]);
|
|
250
427
|
return "$" + idx++;
|
|
251
428
|
};
|
|
252
429
|
} else {
|
|
253
430
|
replacer = (_, $1) => {
|
|
254
|
-
bindings.push(this._bindings[$1]);
|
|
431
|
+
bindings.push(this._bindings[Number($1)]);
|
|
255
432
|
return "?";
|
|
256
433
|
};
|
|
257
434
|
}
|
|
@@ -272,6 +449,11 @@ class SelectBuilder {
|
|
|
272
449
|
return `OFFSET ${offset} ROWS`;
|
|
273
450
|
}
|
|
274
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
|
+
*/
|
|
275
457
|
compileCount({
|
|
276
458
|
countExpr = "*",
|
|
277
459
|
engine = defaultEngine,
|
|
@@ -279,11 +461,10 @@ class SelectBuilder {
|
|
|
279
461
|
} = {}) {
|
|
280
462
|
const query = this.getFoundRowsQuery(countExpr);
|
|
281
463
|
const { sql, bindings } = query.compile({ engine, bindingIndex });
|
|
282
|
-
if (this._havings.length === 0) {
|
|
464
|
+
if (this._havings.length === 0 && this._groupBys.length === 0) {
|
|
283
465
|
return { sql, bindings };
|
|
284
466
|
} else {
|
|
285
|
-
const subquerySql = sql.replace(/\n/g,
|
|
286
|
-
`);
|
|
467
|
+
const subquerySql = sql.replace(/\n/g, "\n ");
|
|
287
468
|
return {
|
|
288
469
|
sql: `SELECT COUNT(*) AS found_rows
|
|
289
470
|
FROM (
|
|
@@ -293,6 +474,11 @@ ${subquerySql}
|
|
|
293
474
|
};
|
|
294
475
|
}
|
|
295
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
|
+
*/
|
|
296
482
|
compileUnion(query, {
|
|
297
483
|
engine = defaultEngine,
|
|
298
484
|
bindingIndex = 1
|
|
@@ -309,7 +495,12 @@ UNION
|
|
|
309
495
|
bindings: [...first.bindings, ...second.bindings]
|
|
310
496
|
};
|
|
311
497
|
}
|
|
312
|
-
|
|
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) {
|
|
313
504
|
if (Array.isArray(fieldOrFields)) {
|
|
314
505
|
fieldOrFields.forEach((name) => this.reset(name));
|
|
315
506
|
return this;
|
|
@@ -339,15 +530,18 @@ UNION
|
|
|
339
530
|
this._groupBys = [];
|
|
340
531
|
this._havings = [];
|
|
341
532
|
this._orderBys = [];
|
|
342
|
-
this._limit =
|
|
343
|
-
this._offset =
|
|
344
|
-
this._page =
|
|
533
|
+
this._limit = void 0;
|
|
534
|
+
this._offset = void 0;
|
|
535
|
+
this._page = void 0;
|
|
345
536
|
this._bindings = [];
|
|
346
537
|
}
|
|
347
538
|
return this;
|
|
348
539
|
}
|
|
540
|
+
/**
|
|
541
|
+
* Clone this builder object
|
|
542
|
+
*/
|
|
349
543
|
getClone() {
|
|
350
|
-
const copy = new
|
|
544
|
+
const copy = new _SelectBuilder();
|
|
351
545
|
copy._options = [...this._options];
|
|
352
546
|
copy._columns = [...this._columns];
|
|
353
547
|
copy._tables = [...this._tables];
|
|
@@ -362,6 +556,10 @@ UNION
|
|
|
362
556
|
copy._bindings = [...this._bindings];
|
|
363
557
|
return copy;
|
|
364
558
|
}
|
|
559
|
+
/**
|
|
560
|
+
* Build a version of this query that simply returns COUNT(*)
|
|
561
|
+
* @param [countExpr="*"] Use to specify `DISTINCT colname` if needed
|
|
562
|
+
*/
|
|
365
563
|
getFoundRowsQuery(countExpr = "*") {
|
|
366
564
|
if (this._havings.length === 0) {
|
|
367
565
|
const clone = this.getClone();
|
|
@@ -369,46 +567,86 @@ UNION
|
|
|
369
567
|
clone._options = [];
|
|
370
568
|
clone._groupBys = [];
|
|
371
569
|
clone._orderBys = [];
|
|
372
|
-
clone._limit =
|
|
373
|
-
clone._offset =
|
|
374
|
-
clone._page =
|
|
570
|
+
clone._limit = void 0;
|
|
571
|
+
clone._offset = void 0;
|
|
572
|
+
clone._page = void 0;
|
|
375
573
|
return clone;
|
|
376
574
|
} else {
|
|
377
575
|
const subquery = this.getClone();
|
|
378
|
-
subquery._limit =
|
|
379
|
-
subquery._offset =
|
|
380
|
-
subquery._page =
|
|
576
|
+
subquery._limit = void 0;
|
|
577
|
+
subquery._offset = void 0;
|
|
578
|
+
subquery._page = void 0;
|
|
381
579
|
return subquery;
|
|
382
580
|
}
|
|
383
581
|
}
|
|
582
|
+
/**
|
|
583
|
+
* Add an array of column names to fetch
|
|
584
|
+
* @param columnNames The names of columns
|
|
585
|
+
*/
|
|
384
586
|
select(columnNames) {
|
|
385
587
|
this._columns = this._columns.concat(columnNames);
|
|
386
588
|
return this;
|
|
387
589
|
}
|
|
590
|
+
/**
|
|
591
|
+
* Add an array of column names to fetch
|
|
592
|
+
* @param columnNames The names of columns
|
|
593
|
+
*/
|
|
388
594
|
columns(columnNames) {
|
|
389
595
|
this._columns = this._columns.concat(columnNames);
|
|
390
596
|
return this;
|
|
391
597
|
}
|
|
598
|
+
/**
|
|
599
|
+
* Add a column name to fetch
|
|
600
|
+
* @param columnName The name of the column
|
|
601
|
+
*/
|
|
392
602
|
column(columnName) {
|
|
393
603
|
this._columns.push(columnName);
|
|
394
604
|
return this;
|
|
395
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
|
+
*/
|
|
396
610
|
option(optionExpression) {
|
|
397
611
|
this._options.push(optionExpression);
|
|
398
612
|
return this;
|
|
399
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
|
+
*/
|
|
400
619
|
table(tableName) {
|
|
401
620
|
this._tables.push(tableName);
|
|
402
621
|
return this;
|
|
403
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
|
+
*/
|
|
404
628
|
tables(tableNames) {
|
|
405
629
|
this._tables.push(...tableNames);
|
|
406
630
|
return this;
|
|
407
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
|
+
*/
|
|
408
637
|
from(tableName) {
|
|
409
638
|
this._tables.push(tableName);
|
|
410
639
|
return this;
|
|
411
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
|
+
*/
|
|
412
650
|
_addJoin(joinType, expression, bindings = []) {
|
|
413
651
|
if (bindings.length > 0) {
|
|
414
652
|
expression = expression.replace(/\?/g, () => {
|
|
@@ -418,42 +656,118 @@ UNION
|
|
|
418
656
|
}
|
|
419
657
|
this._joins.push(`${joinType} ${expression}`);
|
|
420
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
|
+
*/
|
|
421
667
|
join(expression, bindings = []) {
|
|
422
668
|
this._addJoin("JOIN", expression, bindings);
|
|
423
669
|
return this;
|
|
424
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
|
+
*/
|
|
425
679
|
leftJoin(expression, bindings = []) {
|
|
426
680
|
this._addJoin("LEFT JOIN", expression, bindings);
|
|
427
681
|
return this;
|
|
428
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
|
+
*/
|
|
429
691
|
fullJoin(expression, bindings = []) {
|
|
430
692
|
this._addJoin("FULL JOIN", expression, bindings);
|
|
431
693
|
return this;
|
|
432
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
|
+
*/
|
|
433
703
|
rightJoin(expression, bindings = []) {
|
|
434
704
|
this._addJoin("RIGHT JOIN", expression, bindings);
|
|
435
705
|
return this;
|
|
436
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
|
+
*/
|
|
437
715
|
crossJoin(expression, bindings = []) {
|
|
438
716
|
this._addJoin("CROSS JOIN", expression, bindings);
|
|
439
717
|
return this;
|
|
440
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
|
+
*/
|
|
441
727
|
innerJoin(expression, bindings = []) {
|
|
442
728
|
this._addJoin("INNER JOIN", expression, bindings);
|
|
443
729
|
return this;
|
|
444
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
|
+
*/
|
|
445
739
|
leftOuterJoin(expression, bindings = []) {
|
|
446
740
|
this._addJoin("LEFT OUTER JOIN", expression, bindings);
|
|
447
741
|
return this;
|
|
448
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
|
+
*/
|
|
449
751
|
fullOuterJoin(expression, bindings = []) {
|
|
450
752
|
this._addJoin("FULL OUTER JOIN", expression, bindings);
|
|
451
753
|
return this;
|
|
452
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
|
+
*/
|
|
453
763
|
rightOuterJoin(expression, bindings = []) {
|
|
454
764
|
this._addJoin("RIGHT OUTER JOIN", expression, bindings);
|
|
455
765
|
return this;
|
|
456
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
|
+
*/
|
|
457
771
|
unjoin(table) {
|
|
458
772
|
if (Array.isArray(table)) {
|
|
459
773
|
table.forEach((t) => this.unjoin(t));
|
|
@@ -465,6 +779,34 @@ UNION
|
|
|
465
779
|
});
|
|
466
780
|
return this;
|
|
467
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
|
+
*/
|
|
468
810
|
_conditions(collection, criteria) {
|
|
469
811
|
const numArgs = criteria.length;
|
|
470
812
|
let [column, operator, value] = criteria;
|
|
@@ -488,18 +830,22 @@ UNION
|
|
|
488
830
|
} else if (numArgs === 2) {
|
|
489
831
|
value = operator;
|
|
490
832
|
const parts = column.split(" ");
|
|
491
|
-
column = parts.shift();
|
|
833
|
+
column = parts.shift() ?? "";
|
|
492
834
|
operator = parts.join(" ");
|
|
493
835
|
}
|
|
494
836
|
if (typeof operator !== "string" || operator === "") {
|
|
495
837
|
operator = "=";
|
|
496
838
|
}
|
|
497
839
|
operator = operator.toLocaleUpperCase();
|
|
498
|
-
const likeMatch = operator.match(
|
|
840
|
+
const likeMatch = operator.match(
|
|
841
|
+
/^(LIKE|NOT LIKE|ILIKE|NOT ILIKE)(?: (\?|\?%|%\?|%\?%))?$/i
|
|
842
|
+
);
|
|
499
843
|
if (operator === "NOT BETWEEN" || operator === "BETWEEN") {
|
|
500
844
|
const one = this._bindings.length;
|
|
501
845
|
const two = this._bindings.length + 1;
|
|
502
|
-
collection.push(
|
|
846
|
+
collection.push(
|
|
847
|
+
`${column} ${operator} __BOUND_${one}__ AND __BOUND_${two}__`
|
|
848
|
+
);
|
|
503
849
|
this._bindings.push(value[0], value[1]);
|
|
504
850
|
} else if (likeMatch) {
|
|
505
851
|
const like = likeMatch[1].toUpperCase();
|
|
@@ -517,32 +863,52 @@ UNION
|
|
|
517
863
|
this._bindings.push(infix.replace("?", value || ""));
|
|
518
864
|
}
|
|
519
865
|
} else if (value === null) {
|
|
520
|
-
collection.push(
|
|
866
|
+
collection.push(
|
|
867
|
+
operator === "=" ? `${column} IS NULL` : `${column} IS NOT NULL`
|
|
868
|
+
);
|
|
521
869
|
} else if (Array.isArray(value)) {
|
|
522
870
|
const inVals = [];
|
|
523
|
-
for (let i = 0;i < value.length; i++) {
|
|
871
|
+
for (let i = 0; i < value.length; i++) {
|
|
524
872
|
inVals.push(`__BOUND_${this._bindings.length}__`);
|
|
525
873
|
this._bindings.push(value[i]);
|
|
526
874
|
}
|
|
527
875
|
const joined = inVals.join(", ");
|
|
528
|
-
collection.push(
|
|
876
|
+
collection.push(
|
|
877
|
+
operator === "=" || operator === "IN" ? `${column} IN(${joined})` : `${column} NOT IN(${joined})`
|
|
878
|
+
);
|
|
529
879
|
} else if (operator === "IN" || operator === "NOT IN") {
|
|
530
|
-
collection.push(
|
|
880
|
+
collection.push(
|
|
881
|
+
`${column} ${operator}(__BOUND_${this._bindings.length}__)`
|
|
882
|
+
);
|
|
531
883
|
this._bindings.push(value);
|
|
532
884
|
} else {
|
|
533
|
-
collection.push(
|
|
885
|
+
collection.push(
|
|
886
|
+
`${column} ${operator} __BOUND_${this._bindings.length}__`
|
|
887
|
+
);
|
|
534
888
|
this._bindings.push(value);
|
|
535
889
|
}
|
|
536
890
|
return this;
|
|
537
891
|
}
|
|
892
|
+
/**
|
|
893
|
+
* Add a group by column or expression
|
|
894
|
+
* @param column The name of a column (or expression) to group by
|
|
895
|
+
*/
|
|
538
896
|
groupBy(column) {
|
|
539
897
|
this._groupBys.push(column);
|
|
540
898
|
return this;
|
|
541
899
|
}
|
|
900
|
+
/**
|
|
901
|
+
* Add WHERE clauses to conditions (See _conditions for usage)
|
|
902
|
+
*/
|
|
542
903
|
where(...args) {
|
|
543
904
|
this._conditions(this._wheres, args);
|
|
544
905
|
return this;
|
|
545
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. If the first is null, it will use >= instead. If the second is null, it will use <= instead. If both are null, no criteria will be added
|
|
911
|
+
*/
|
|
546
912
|
whereBetween(column, twoValueArray) {
|
|
547
913
|
if (!isNullish(twoValueArray[0]) && !isNullish(twoValueArray[1])) {
|
|
548
914
|
this.where(column, "BETWEEN", twoValueArray);
|
|
@@ -550,9 +916,14 @@ UNION
|
|
|
550
916
|
this.where(column, ">=", twoValueArray[0]);
|
|
551
917
|
} else if (isNullish(twoValueArray[0]) && !isNullish(twoValueArray[1])) {
|
|
552
918
|
this.where(column, "<=", twoValueArray[1]);
|
|
553
|
-
} else {
|
|
919
|
+
} else {
|
|
920
|
+
}
|
|
554
921
|
return this;
|
|
555
922
|
}
|
|
923
|
+
/**
|
|
924
|
+
* Add a series of WHERE clauses joined by OR (See _conditions for usage)
|
|
925
|
+
* @param conditions
|
|
926
|
+
*/
|
|
556
927
|
_orConditions(conditions) {
|
|
557
928
|
const items = [];
|
|
558
929
|
if (Array.isArray(conditions)) {
|
|
@@ -577,21 +948,44 @@ UNION
|
|
|
577
948
|
return joined;
|
|
578
949
|
}
|
|
579
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
|
+
*/
|
|
580
955
|
orWhere(conditions) {
|
|
581
956
|
const joined = this._orConditions(conditions);
|
|
582
957
|
this.where(`(${joined})`);
|
|
583
958
|
return this;
|
|
584
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
|
+
*/
|
|
585
964
|
orHaving(conditions) {
|
|
586
965
|
const joined = this._orConditions(conditions);
|
|
587
966
|
this.having(`(${joined})`);
|
|
588
967
|
return this;
|
|
589
968
|
}
|
|
969
|
+
/**
|
|
970
|
+
* Add a HAVING condition (See _conditions for usage)
|
|
971
|
+
*/
|
|
590
972
|
having(...args) {
|
|
591
973
|
this._conditions(this._havings, args);
|
|
592
974
|
return this;
|
|
593
975
|
}
|
|
594
|
-
|
|
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.orderBy('modified_at'); // ORDER BY modified_at
|
|
982
|
+
* query.orderBy('-modified_at'); // ORDER BY modified_at DESC
|
|
983
|
+
* query.orderBy('COUNT(*)'); // ORDER BY COUNT(*)
|
|
984
|
+
* query.orderBy('COUNT(*) DESC'); // ORDER BY COUNT(*) DESC
|
|
985
|
+
* query.orderBy('modified_at DESC NULLS LAST'); // ORDER BY modified_at DESC NULLS LAST
|
|
986
|
+
* query.orderBy('modified_at USING >'); // ORDER BY modified_at USING >
|
|
987
|
+
*/
|
|
988
|
+
orderBy(column, direction) {
|
|
595
989
|
if (direction) {
|
|
596
990
|
this._orderBys.push(`${column} ${direction}`);
|
|
597
991
|
} else {
|
|
@@ -600,8 +994,24 @@ UNION
|
|
|
600
994
|
}
|
|
601
995
|
return this;
|
|
602
996
|
}
|
|
997
|
+
/**
|
|
998
|
+
* Sort by the given column, with a map of columns to translate
|
|
999
|
+
* @param fieldName The file name such as 'created_at' or '-created_at' for descending
|
|
1000
|
+
* @param [mapNames] Column names to translate from field to column expression
|
|
1001
|
+
* @param [modifier] Additional clause to append such as 'NULLS LAST' or 'COLLATE "en_US"'
|
|
1002
|
+
* @example
|
|
1003
|
+
* query.sortField('-modified_at'); // ORDER BY modified_at DESC
|
|
1004
|
+
* query.sortField('-created', {'created': 'created_at'}); // ORDER BY created_at DESC
|
|
1005
|
+
* query.sortField('-created', {
|
|
1006
|
+
* 'created': 'created_at NULLS LAST',
|
|
1007
|
+
* '-created': 'created_at DESC NULLS LAST'
|
|
1008
|
+
* }); // ORDER BY created_at DESC NULLS LAST
|
|
1009
|
+
* query.sortField('-name', {
|
|
1010
|
+
* 'name': 'name COLLATE "jp_JP"'
|
|
1011
|
+
* }); // ORDER BY name COLLATE "jp_JP" DESC
|
|
1012
|
+
*/
|
|
603
1013
|
sortField(fieldName, mapNames = {}, modifier = "") {
|
|
604
|
-
if (
|
|
1014
|
+
if (typeof fieldName !== "string" || fieldName === "") {
|
|
605
1015
|
return this;
|
|
606
1016
|
}
|
|
607
1017
|
let direction = "ASC";
|
|
@@ -621,12 +1031,26 @@ UNION
|
|
|
621
1031
|
this.orderBy(column, direction);
|
|
622
1032
|
return this;
|
|
623
1033
|
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Check to see if the given string is 0 or all digits not starting with 0
|
|
1036
|
+
* @param string The input string
|
|
1037
|
+
* @private
|
|
1038
|
+
*/
|
|
624
1039
|
_isEntirelyDigits(string) {
|
|
625
1040
|
return /^(0|[1-9]\d*)$/.test(string);
|
|
626
1041
|
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Check to see if the given string is all digits
|
|
1044
|
+
* @param string The input string
|
|
1045
|
+
* @private
|
|
1046
|
+
*/
|
|
627
1047
|
_isEntirelyDigitsNoZeros(string) {
|
|
628
1048
|
return /^[1-9]\d*$/.test(string);
|
|
629
1049
|
}
|
|
1050
|
+
/**
|
|
1051
|
+
* Limit results to the given number
|
|
1052
|
+
* @param max The number to limit by (placeholder string or integer greater than 0)
|
|
1053
|
+
*/
|
|
630
1054
|
limit(max) {
|
|
631
1055
|
if (typeof max === "string") {
|
|
632
1056
|
max = max.trim();
|
|
@@ -636,38 +1060,55 @@ UNION
|
|
|
636
1060
|
} else if (typeof max === "number" && Number.isInteger(max) && max >= 1) {
|
|
637
1061
|
this._limit = max;
|
|
638
1062
|
} else {
|
|
639
|
-
throw new Error(
|
|
1063
|
+
throw new Error(
|
|
1064
|
+
`SelectBuilder: "limit(n)" must receive a positive integer (got "${max}")`
|
|
1065
|
+
);
|
|
640
1066
|
}
|
|
641
1067
|
return this;
|
|
642
1068
|
}
|
|
1069
|
+
/**
|
|
1070
|
+
* Fetch results from the given offset
|
|
1071
|
+
* @param number The offset (placeholder string or integer greater than or equal to 0)
|
|
1072
|
+
*/
|
|
643
1073
|
offset(number) {
|
|
644
1074
|
if (typeof number === "string" && this._isEntirelyDigits(number.trim())) {
|
|
645
1075
|
this._offset = parseInt(number, 10);
|
|
646
1076
|
} else if (typeof number === "number" && Number.isInteger(number) && number >= 0) {
|
|
647
1077
|
this._offset = number;
|
|
648
1078
|
} else {
|
|
649
|
-
throw new Error(
|
|
1079
|
+
throw new Error(
|
|
1080
|
+
`SelectBuilder: "offset(n)" must receive a positive integer (got "${number}")`
|
|
1081
|
+
);
|
|
650
1082
|
}
|
|
651
1083
|
return this;
|
|
652
1084
|
}
|
|
1085
|
+
/**
|
|
1086
|
+
* Set the offset based on the limit with the given page number
|
|
1087
|
+
* @param number The page number (integer greater than 0)
|
|
1088
|
+
*/
|
|
653
1089
|
page(number) {
|
|
654
1090
|
if (typeof number === "string" && this._isEntirelyDigitsNoZeros(number.trim())) {
|
|
655
1091
|
this._page = parseInt(number, 10);
|
|
656
1092
|
} else if (typeof number === "number" && Number.isInteger(number) && number >= 1) {
|
|
657
1093
|
this._page = number;
|
|
658
1094
|
} else {
|
|
659
|
-
throw new Error(
|
|
1095
|
+
throw new Error(
|
|
1096
|
+
'SelectBuilder: "page(n)" must receive a positive integer'
|
|
1097
|
+
);
|
|
660
1098
|
}
|
|
661
1099
|
return this;
|
|
662
1100
|
}
|
|
663
|
-
}
|
|
1101
|
+
};
|
|
1102
|
+
|
|
664
1103
|
// src/extractGrouped/extractGrouped.ts
|
|
665
1104
|
function extractGrouped(field, records) {
|
|
666
1105
|
let idx = 0;
|
|
667
1106
|
const grouped = {};
|
|
668
1107
|
for (const record of records) {
|
|
669
1108
|
if (!(field in record)) {
|
|
670
|
-
throw new Error(
|
|
1109
|
+
throw new Error(
|
|
1110
|
+
`Column "${String(field)}" is not present in record ${idx}`
|
|
1111
|
+
);
|
|
671
1112
|
}
|
|
672
1113
|
if (!grouped[record[field]]) {
|
|
673
1114
|
grouped[record[field]] = [];
|
|
@@ -677,35 +1118,44 @@ function extractGrouped(field, records) {
|
|
|
677
1118
|
}
|
|
678
1119
|
return grouped;
|
|
679
1120
|
}
|
|
1121
|
+
|
|
680
1122
|
// src/extractIndexed/extractIndexed.ts
|
|
681
1123
|
function extractIndexed(field, records) {
|
|
682
1124
|
let idx = 0;
|
|
683
1125
|
const indexed = {};
|
|
684
1126
|
for (const record of records) {
|
|
685
1127
|
if (!(field in record)) {
|
|
686
|
-
throw new Error(
|
|
1128
|
+
throw new Error(
|
|
1129
|
+
`Column "${String(field)}" is not present in record ${idx}`
|
|
1130
|
+
);
|
|
687
1131
|
}
|
|
688
1132
|
indexed[record[field]] = record;
|
|
689
1133
|
idx++;
|
|
690
1134
|
}
|
|
691
1135
|
return indexed;
|
|
692
1136
|
}
|
|
1137
|
+
|
|
693
1138
|
// src/extractLookup/extractLookup.ts
|
|
694
1139
|
function extractLookup(keyField, valueField, records) {
|
|
695
1140
|
const lookup = {};
|
|
696
1141
|
let idx = 0;
|
|
697
1142
|
for (const record of records) {
|
|
698
1143
|
if (!(keyField in record)) {
|
|
699
|
-
throw new Error(
|
|
1144
|
+
throw new Error(
|
|
1145
|
+
`Column "${String(keyField)}" is not present in record ${idx}`
|
|
1146
|
+
);
|
|
700
1147
|
}
|
|
701
1148
|
if (!(valueField in record)) {
|
|
702
|
-
throw new Error(
|
|
1149
|
+
throw new Error(
|
|
1150
|
+
`Column "${String(valueField)}" is not present in record ${idx}`
|
|
1151
|
+
);
|
|
703
1152
|
}
|
|
704
1153
|
idx++;
|
|
705
|
-
lookup[
|
|
1154
|
+
lookup[record[keyField]] = record[valueField];
|
|
706
1155
|
}
|
|
707
1156
|
return lookup;
|
|
708
1157
|
}
|
|
1158
|
+
|
|
709
1159
|
// src/getPagination/getPagination.ts
|
|
710
1160
|
function getPagination(query, total) {
|
|
711
1161
|
total = Number(total) || 0;
|
|
@@ -725,8 +1175,8 @@ function getPagination(query, total) {
|
|
|
725
1175
|
const numPages = total === 0 || perPage === null ? 0 : Math.ceil(total / perPage) || 0;
|
|
726
1176
|
const isFirst = page === 1;
|
|
727
1177
|
const isLast = numPages > 0 && page === numPages;
|
|
728
|
-
const prevPage = numPages === 0 ? null : page - 1 || null;
|
|
729
|
-
const nextPage = numPages > 0 && page + 1 <= numPages ? page + 1 : null;
|
|
1178
|
+
const prevPage = numPages === 0 || page === null ? null : page - 1 || null;
|
|
1179
|
+
const nextPage = numPages > 0 && page !== null && page + 1 <= numPages ? page + 1 : null;
|
|
730
1180
|
return {
|
|
731
1181
|
page,
|
|
732
1182
|
prevPage,
|
|
@@ -738,6 +1188,7 @@ function getPagination(query, total) {
|
|
|
738
1188
|
isLast
|
|
739
1189
|
};
|
|
740
1190
|
}
|
|
1191
|
+
|
|
741
1192
|
// src/runMysql/runMysql.ts
|
|
742
1193
|
function _queryToPromise(mysqlClient, sql, bindings) {
|
|
743
1194
|
return new Promise((resolve, reject) => {
|
|
@@ -788,6 +1239,7 @@ async function runMysqlAsyncWithCount(mysqlClient, query, {
|
|
|
788
1239
|
return { records, total: 0, pagination: getPagination(query, 0), fields };
|
|
789
1240
|
}
|
|
790
1241
|
}
|
|
1242
|
+
|
|
791
1243
|
// src/toSafeJson/toSafeJson.ts
|
|
792
1244
|
function toSafeJsonValue(value) {
|
|
793
1245
|
if (typeof value === "bigint") {
|
|
@@ -814,16 +1266,19 @@ function toSafeJsonString(records) {
|
|
|
814
1266
|
}
|
|
815
1267
|
|
|
816
1268
|
// src/runPrisma/runPrisma.ts
|
|
817
|
-
async function runPrisma(prisma, query, { engine =
|
|
1269
|
+
async function runPrisma(prisma, query, { engine = void 0 } = {}) {
|
|
818
1270
|
const { sql, bindings } = query.compile({ engine });
|
|
819
1271
|
return prisma.$queryRawUnsafe(sql, ...bindings);
|
|
820
1272
|
}
|
|
821
1273
|
async function runPrismaWithCount(prisma, query, {
|
|
822
|
-
engine =
|
|
1274
|
+
engine = void 0,
|
|
823
1275
|
countExpr = "*"
|
|
824
1276
|
} = {}) {
|
|
825
1277
|
const { sql, bindings } = query.compile({ engine });
|
|
826
|
-
const records = await prisma.$queryRawUnsafe(
|
|
1278
|
+
const records = await prisma.$queryRawUnsafe(
|
|
1279
|
+
sql,
|
|
1280
|
+
...bindings
|
|
1281
|
+
);
|
|
827
1282
|
if (records.length > 0) {
|
|
828
1283
|
const { sql: sql2, bindings: bindings2 } = query.compileCount({ engine, countExpr });
|
|
829
1284
|
const result = await prisma.$queryRawUnsafe(sql2, ...bindings2);
|
|
@@ -834,20 +1289,20 @@ async function runPrismaWithCount(prisma, query, {
|
|
|
834
1289
|
}
|
|
835
1290
|
}
|
|
836
1291
|
export {
|
|
837
|
-
|
|
838
|
-
toSafeJsonString,
|
|
839
|
-
toSafeJsonRecords,
|
|
840
|
-
toSafeJsonRecord,
|
|
841
|
-
runPrismaWithCount,
|
|
842
|
-
runPrisma,
|
|
843
|
-
runMysqlWithCount,
|
|
844
|
-
runMysqlAsyncWithCount,
|
|
845
|
-
runMysqlAsync,
|
|
846
|
-
runMysql,
|
|
847
|
-
getPagination,
|
|
848
|
-
extractLookup,
|
|
849
|
-
extractIndexed,
|
|
850
|
-
extractGrouped,
|
|
1292
|
+
Parser,
|
|
851
1293
|
SelectBuilder,
|
|
852
|
-
|
|
1294
|
+
extractGrouped,
|
|
1295
|
+
extractIndexed,
|
|
1296
|
+
extractLookup,
|
|
1297
|
+
getPagination,
|
|
1298
|
+
runMysql,
|
|
1299
|
+
runMysqlAsync,
|
|
1300
|
+
runMysqlAsyncWithCount,
|
|
1301
|
+
runMysqlWithCount,
|
|
1302
|
+
runPrisma,
|
|
1303
|
+
runPrismaWithCount,
|
|
1304
|
+
toSafeJsonRecord,
|
|
1305
|
+
toSafeJsonRecords,
|
|
1306
|
+
toSafeJsonString,
|
|
1307
|
+
toSafeJsonValue
|
|
853
1308
|
};
|