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