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