1 | "use strict";
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 | Object.defineProperty(exports, "__esModule", { value: true });
|
11 | exports.ModelQueryBuilder = void 0;
|
12 | const utils_1 = require("@poppinss/utils");
|
13 | const utils_2 = require("../../utils");
|
14 | const Preloader_1 = require("../Preloader");
|
15 | const Paginator_1 = require("../Paginator");
|
16 | const QueryRunner_1 = require("../../QueryRunner");
|
17 | const Chainable_1 = require("../../Database/QueryBuilder/Chainable");
|
18 | const SimplePaginator_1 = require("../../Database/Paginator/SimplePaginator");
|
19 |
|
20 |
|
21 |
|
22 |
|
23 | class ModelScopes {
|
24 | constructor(builder) {
|
25 | Object.defineProperty(this, "builder", {
|
26 | enumerable: true,
|
27 | configurable: true,
|
28 | writable: true,
|
29 | value: builder
|
30 | });
|
31 | return new Proxy(this, {
|
32 | get(target, key) {
|
33 | if (typeof target.builder.model[key] === 'function') {
|
34 | return (...args) => {
|
35 | return target.builder.model[key](target.builder, ...args);
|
36 | };
|
37 | }
|
38 | |
39 |
|
40 |
|
41 | throw new Error(`"${String(key)}" is not defined as a query scope on "${target.builder.model.name}" model`);
|
42 | },
|
43 | });
|
44 | }
|
45 | }
|
46 |
|
47 |
|
48 |
|
49 |
|
50 | class ModelQueryBuilder extends Chainable_1.Chainable {
|
51 | constructor(builder, model, client, customFn = (userFn) => {
|
52 | return ($builder) => {
|
53 | const subQuery = new ModelQueryBuilder($builder, this.model, this.client);
|
54 | subQuery.isChildQuery = true;
|
55 | userFn(subQuery);
|
56 | subQuery.applyWhere();
|
57 | };
|
58 | }) {
|
59 | super(builder, customFn, model.$keys.attributesToColumns.resolve.bind(model.$keys.attributesToColumns));
|
60 | Object.defineProperty(this, "model", {
|
61 | enumerable: true,
|
62 | configurable: true,
|
63 | writable: true,
|
64 | value: model
|
65 | });
|
66 | Object.defineProperty(this, "client", {
|
67 | enumerable: true,
|
68 | configurable: true,
|
69 | writable: true,
|
70 | value: client
|
71 | });
|
72 | |
73 |
|
74 |
|
75 | Object.defineProperty(this, "sideloaded", {
|
76 | enumerable: true,
|
77 | configurable: true,
|
78 | writable: true,
|
79 | value: {}
|
80 | });
|
81 | |
82 |
|
83 |
|
84 | Object.defineProperty(this, "preloader", {
|
85 | enumerable: true,
|
86 | configurable: true,
|
87 | writable: true,
|
88 | value: new Preloader_1.Preloader(this.model)
|
89 | });
|
90 | |
91 |
|
92 |
|
93 | Object.defineProperty(this, "rowTransformerCallback", {
|
94 | enumerable: true,
|
95 | configurable: true,
|
96 | writable: true,
|
97 | value: void 0
|
98 | });
|
99 | |
100 |
|
101 |
|
102 |
|
103 | Object.defineProperty(this, "scopesWrapper", {
|
104 | enumerable: true,
|
105 | configurable: true,
|
106 | writable: true,
|
107 | value: undefined
|
108 | });
|
109 | |
110 |
|
111 |
|
112 |
|
113 | Object.defineProperty(this, "wrapResultsToModelInstances", {
|
114 | enumerable: true,
|
115 | configurable: true,
|
116 | writable: true,
|
117 | value: true
|
118 | });
|
119 | |
120 |
|
121 |
|
122 |
|
123 | Object.defineProperty(this, "customReporterData", {
|
124 | enumerable: true,
|
125 | configurable: true,
|
126 | writable: true,
|
127 | value: void 0
|
128 | });
|
129 | |
130 |
|
131 |
|
132 |
|
133 | Object.defineProperty(this, "debugQueries", {
|
134 | enumerable: true,
|
135 | configurable: true,
|
136 | writable: true,
|
137 | value: this.client.debug
|
138 | });
|
139 | |
140 |
|
141 |
|
142 |
|
143 | Object.defineProperty(this, "joinCounter", {
|
144 | enumerable: true,
|
145 | configurable: true,
|
146 | writable: true,
|
147 | value: 0
|
148 | });
|
149 | |
150 |
|
151 |
|
152 | Object.defineProperty(this, "clientOptions", {
|
153 | enumerable: true,
|
154 | configurable: true,
|
155 | writable: true,
|
156 | value: {
|
157 | client: this.client,
|
158 | connection: this.client.connectionName,
|
159 | profiler: this.client.profiler,
|
160 | }
|
161 | });
|
162 | |
163 |
|
164 |
|
165 | Object.defineProperty(this, "isChildQuery", {
|
166 | enumerable: true,
|
167 | configurable: true,
|
168 | writable: true,
|
169 | value: false
|
170 | });
|
171 | |
172 |
|
173 |
|
174 | if (!builder['_single'] || !builder['_single'].table) {
|
175 | builder.table(model.table);
|
176 | }
|
177 | }
|
178 | |
179 |
|
180 |
|
181 | async execQuery() {
|
182 | this.applyWhere();
|
183 | const isWriteQuery = ['update', 'del', 'insert'].includes(this.knexQuery['_method']);
|
184 | const queryData = Object.assign(this.getQueryData(), this.customReporterData);
|
185 | const rows = await new QueryRunner_1.QueryRunner(this.client, this.debugQueries, queryData).run(this.knexQuery);
|
186 | |
187 |
|
188 |
|
189 | if (isWriteQuery || !this.wrapResultsToModelInstances) {
|
190 | return Array.isArray(rows) ? rows : [rows];
|
191 | }
|
192 | |
193 |
|
194 |
|
195 | const modelInstances = rows.reduce((models, row) => {
|
196 | if ((0, utils_2.isObject)(row)) {
|
197 | const modelInstance = this.model.$createFromAdapterResult(row, this.sideloaded, this.clientOptions);
|
198 | |
199 |
|
200 |
|
201 | if (this.rowTransformerCallback) {
|
202 | this.rowTransformerCallback(modelInstance);
|
203 | }
|
204 | models.push(modelInstance);
|
205 | }
|
206 | return models;
|
207 | }, []);
|
208 | |
209 |
|
210 |
|
211 | await this.preloader
|
212 | .sideload(this.sideloaded)
|
213 | .debug(this.debugQueries)
|
214 | .processAllForMany(modelInstances, this.client);
|
215 | return modelInstances;
|
216 | }
|
217 | |
218 |
|
219 |
|
220 |
|
221 | ensureCanPerformWrites() {
|
222 | if (this.client && this.client.mode === 'read') {
|
223 | throw new utils_1.Exception('Updates and deletes cannot be performed in read mode');
|
224 | }
|
225 | }
|
226 | |
227 |
|
228 |
|
229 | addWhereHas(relationName, boolean, operator, value, callback) {
|
230 | let rawMethod = 'whereRaw';
|
231 | let existsMethod = 'whereExists';
|
232 | switch (boolean) {
|
233 | case 'or':
|
234 | rawMethod = 'orWhereRaw';
|
235 | existsMethod = 'orWhereExists';
|
236 | break;
|
237 | case 'not':
|
238 | existsMethod = 'whereNotExists';
|
239 | break;
|
240 | case 'orNot':
|
241 | rawMethod = 'orWhereRaw';
|
242 | existsMethod = 'orWhereNotExists';
|
243 | break;
|
244 | }
|
245 | const subQuery = this.getRelationship(relationName).subQuery(this.client);
|
246 | subQuery.selfJoinCounter = this.joinCounter;
|
247 | |
248 |
|
249 |
|
250 | if (typeof callback === 'function') {
|
251 | callback(subQuery);
|
252 | }
|
253 | |
254 |
|
255 |
|
256 | if (value !== undefined && operator !== undefined) {
|
257 | |
258 |
|
259 |
|
260 |
|
261 | if (!subQuery.hasAggregates) {
|
262 | subQuery.count('*');
|
263 | }
|
264 | |
265 |
|
266 |
|
267 | const { sql, bindings } = subQuery.prepare().toSQL();
|
268 | |
269 |
|
270 |
|
271 |
|
272 | boolean === 'orNot' || boolean === 'not'
|
273 | ? this[rawMethod](`not (${sql}) ${operator} (?)`, bindings.concat([value]))
|
274 | : this[rawMethod](`(${sql}) ${operator} (?)`, bindings.concat([value]));
|
275 | return this;
|
276 | }
|
277 | |
278 |
|
279 |
|
280 | this[existsMethod](subQuery.prepare());
|
281 | return this;
|
282 | }
|
283 | |
284 |
|
285 |
|
286 |
|
287 | getQueryData() {
|
288 | return {
|
289 | connection: this.client.connectionName,
|
290 | inTransaction: this.client.isTransaction,
|
291 | model: this.model.name,
|
292 | };
|
293 | }
|
294 | |
295 |
|
296 |
|
297 |
|
298 | getRelationship(name) {
|
299 | const relation = this.model.$getRelation(name);
|
300 | |
301 |
|
302 |
|
303 | if (!relation) {
|
304 | throw new utils_1.Exception(`"${name}" is not defined as a relationship on "${this.model.name}" model`, 500, 'E_UNDEFINED_RELATIONSHIP');
|
305 | }
|
306 | relation.boot();
|
307 | return relation;
|
308 | }
|
309 | |
310 |
|
311 |
|
312 |
|
313 | reporterData(data) {
|
314 | this.customReporterData = data;
|
315 | return this;
|
316 | }
|
317 | |
318 |
|
319 |
|
320 | rowTransformer(callback) {
|
321 | this.rowTransformerCallback = callback;
|
322 | return this;
|
323 | }
|
324 | |
325 |
|
326 |
|
327 | clone() {
|
328 | const clonedQuery = new ModelQueryBuilder(this.knexQuery.clone(), this.model, this.client);
|
329 | this.applyQueryFlags(clonedQuery);
|
330 | clonedQuery.sideloaded = Object.assign({}, this.sideloaded);
|
331 | clonedQuery.debug(this.debugQueries);
|
332 | clonedQuery.reporterData(this.customReporterData);
|
333 | this.rowTransformerCallback && this.rowTransformer(this.rowTransformerCallback);
|
334 | return clonedQuery;
|
335 | }
|
336 | |
337 |
|
338 |
|
339 | returning(columns) {
|
340 | |
341 |
|
342 |
|
343 | if (this.client && ['sqlite3', 'mysql'].includes(this.client.dialect.name)) {
|
344 | return this;
|
345 | }
|
346 | columns = Array.isArray(columns)
|
347 | ? columns.map((column) => this.resolveKey(column))
|
348 | : this.resolveKey(columns);
|
349 | this.knexQuery.returning(columns);
|
350 | return this;
|
351 | }
|
352 | |
353 |
|
354 |
|
355 | ifDialect(dialects, matchCallback, noMatchCallback) {
|
356 | dialects = Array.isArray(dialects) ? dialects : [dialects];
|
357 | if (dialects.includes(this.client.dialect.name)) {
|
358 | matchCallback(this);
|
359 | }
|
360 | else if (noMatchCallback) {
|
361 | noMatchCallback(this);
|
362 | }
|
363 | return this;
|
364 | }
|
365 | |
366 |
|
367 |
|
368 | unlessDialect(dialects, matchCallback, noMatchCallback) {
|
369 | dialects = Array.isArray(dialects) ? dialects : [dialects];
|
370 | if (!dialects.includes(this.client.dialect.name)) {
|
371 | matchCallback(this);
|
372 | }
|
373 | else if (noMatchCallback) {
|
374 | noMatchCallback(this);
|
375 | }
|
376 | return this;
|
377 | }
|
378 | |
379 |
|
380 |
|
381 |
|
382 | withScopes(callback) {
|
383 | this.scopesWrapper = this.scopesWrapper || new ModelScopes(this);
|
384 | callback(this.scopesWrapper);
|
385 | return this;
|
386 | }
|
387 | |
388 |
|
389 |
|
390 |
|
391 | apply(callback) {
|
392 | return this.withScopes(callback);
|
393 | }
|
394 | |
395 |
|
396 |
|
397 | usePreloader(preloader) {
|
398 | this.preloader = preloader;
|
399 | return this;
|
400 | }
|
401 | |
402 |
|
403 |
|
404 | sideload(value) {
|
405 | this.sideloaded = value;
|
406 | return this;
|
407 | }
|
408 | |
409 |
|
410 |
|
411 |
|
412 | async first() {
|
413 | const isFetchCall = this.wrapResultsToModelInstances && this.knexQuery['_method'] === 'select';
|
414 | if (isFetchCall) {
|
415 | await this.model.$hooks.exec('before', 'find', this);
|
416 | }
|
417 | const result = await this.limit(1).execQuery();
|
418 | if (result[0] && isFetchCall) {
|
419 | await this.model.$hooks.exec('after', 'find', result[0]);
|
420 | }
|
421 | return result[0] || null;
|
422 | }
|
423 | |
424 |
|
425 |
|
426 |
|
427 | async firstOrFail() {
|
428 | const row = await this.first();
|
429 | if (!row) {
|
430 | throw new utils_1.Exception('Row not found', 404, 'E_ROW_NOT_FOUND');
|
431 | }
|
432 | return row;
|
433 | }
|
434 | |
435 |
|
436 |
|
437 | withAggregate(relationName, userCallback) {
|
438 | const subQuery = this.getRelationship(relationName).subQuery(this.client);
|
439 | subQuery.selfJoinCounter = this.joinCounter;
|
440 | |
441 |
|
442 |
|
443 | userCallback(subQuery);
|
444 | |
445 |
|
446 |
|
447 | if (!subQuery.hasAggregates) {
|
448 | throw new utils_1.Exception('"withAggregate" callback must use an aggregate function');
|
449 | }
|
450 | |
451 |
|
452 |
|
453 | if (!this.columns.length) {
|
454 | this.select(`${this.model.table}.*`);
|
455 | }
|
456 | |
457 |
|
458 |
|
459 | if (!subQuery.subQueryAlias) {
|
460 | throw new utils_1.Exception('"withAggregate" callback must define the alias for the aggregate query');
|
461 | }
|
462 | |
463 |
|
464 |
|
465 | this.select(subQuery.prepare());
|
466 | |
467 |
|
468 |
|
469 | this.joinCounter++;
|
470 | return this;
|
471 | }
|
472 | |
473 |
|
474 |
|
475 | withCount(relationName, userCallback) {
|
476 | this.withAggregate(relationName, (subQuery) => {
|
477 | if (typeof userCallback === 'function') {
|
478 | userCallback(subQuery);
|
479 | }
|
480 | |
481 |
|
482 |
|
483 | if (!subQuery.hasAggregates) {
|
484 | subQuery.count('*');
|
485 | }
|
486 | |
487 |
|
488 |
|
489 | if (!subQuery.subQueryAlias) {
|
490 | subQuery.as(`${relationName}_count`);
|
491 | }
|
492 | });
|
493 | return this;
|
494 | }
|
495 | |
496 |
|
497 |
|
498 | whereHas(relationName, callback, operator, value) {
|
499 | return this.addWhereHas(relationName, 'and', operator, value, callback);
|
500 | }
|
501 | |
502 |
|
503 |
|
504 | orWhereHas(relationName, callback, operator, value) {
|
505 | return this.addWhereHas(relationName, 'or', operator, value, callback);
|
506 | }
|
507 | |
508 |
|
509 |
|
510 | andWhereHas(relationName, callback, operator, value) {
|
511 | return this.addWhereHas(relationName, 'and', operator, value, callback);
|
512 | }
|
513 | |
514 |
|
515 |
|
516 | whereDoesntHave(relationName, callback, operator, value) {
|
517 | return this.addWhereHas(relationName, 'not', operator, value, callback);
|
518 | }
|
519 | |
520 |
|
521 |
|
522 | orWhereDoesntHave(relationName, callback, operator, value) {
|
523 | return this.addWhereHas(relationName, 'orNot', operator, value, callback);
|
524 | }
|
525 | |
526 |
|
527 |
|
528 | andWhereDoesntHave(relationName, callback, operator, value) {
|
529 | return this.addWhereHas(relationName, 'not', operator, value, callback);
|
530 | }
|
531 | |
532 |
|
533 |
|
534 | has(relationName, operator, value) {
|
535 | return this.addWhereHas(relationName, 'and', operator, value);
|
536 | }
|
537 | |
538 |
|
539 |
|
540 | orHas(relationName, operator, value) {
|
541 | return this.addWhereHas(relationName, 'or', operator, value);
|
542 | }
|
543 | |
544 |
|
545 |
|
546 | andHas(relationName, operator, value) {
|
547 | return this.addWhereHas(relationName, 'and', operator, value);
|
548 | }
|
549 | |
550 |
|
551 |
|
552 | doesntHave(relationName, operator, value) {
|
553 | return this.addWhereHas(relationName, 'not', operator, value);
|
554 | }
|
555 | |
556 |
|
557 |
|
558 | orDoesntHave(relationName, operator, value) {
|
559 | return this.addWhereHas(relationName, 'orNot', operator, value);
|
560 | }
|
561 | |
562 |
|
563 |
|
564 | andDoesntHave(relationName, operator, value) {
|
565 | return this.addWhereHas(relationName, 'not', operator, value);
|
566 | }
|
567 | |
568 |
|
569 |
|
570 | preload(relationName, userCallback) {
|
571 | this.preloader.load(relationName, userCallback);
|
572 | return this;
|
573 | }
|
574 | |
575 |
|
576 |
|
577 |
|
578 | increment(column, counter) {
|
579 | this.ensureCanPerformWrites();
|
580 | this.knexQuery.increment(this.resolveKey(column, true), counter);
|
581 | return this;
|
582 | }
|
583 | |
584 |
|
585 |
|
586 |
|
587 | decrement(column, counter) {
|
588 | this.ensureCanPerformWrites();
|
589 | this.knexQuery.decrement(this.resolveKey(column, true), counter);
|
590 | return this;
|
591 | }
|
592 | |
593 |
|
594 |
|
595 | update(column, value, returning) {
|
596 | this.ensureCanPerformWrites();
|
597 | if (value === undefined && returning === undefined) {
|
598 | this.knexQuery.update(this.resolveKey(column, true));
|
599 | }
|
600 | else if (returning === undefined) {
|
601 | this.knexQuery.update(this.resolveKey(column), value);
|
602 | }
|
603 | else {
|
604 | this.knexQuery.update(this.resolveKey(column), value, returning);
|
605 | }
|
606 | return this;
|
607 | }
|
608 | |
609 |
|
610 |
|
611 | del() {
|
612 | this.ensureCanPerformWrites();
|
613 | this.knexQuery.del();
|
614 | return this;
|
615 | }
|
616 | |
617 |
|
618 |
|
619 | delete() {
|
620 | return this.del();
|
621 | }
|
622 | |
623 |
|
624 |
|
625 | debug(debug) {
|
626 | this.debugQueries = debug;
|
627 | return this;
|
628 | }
|
629 | |
630 |
|
631 |
|
632 | timeout(time, options) {
|
633 | this.knexQuery['timeout'](time, options);
|
634 | return this;
|
635 | }
|
636 | |
637 |
|
638 |
|
639 | toQuery() {
|
640 | this.applyWhere();
|
641 | return this.knexQuery.toQuery();
|
642 | }
|
643 | |
644 |
|
645 |
|
646 | useTransaction(transaction) {
|
647 | this.knexQuery.transacting(transaction.knexClient);
|
648 | return this;
|
649 | }
|
650 | |
651 |
|
652 |
|
653 | async exec() {
|
654 | const isFetchCall = this.wrapResultsToModelInstances && this.knexQuery['_method'] === 'select';
|
655 | if (isFetchCall) {
|
656 | await this.model.$hooks.exec('before', 'fetch', this);
|
657 | }
|
658 | const result = await this.execQuery();
|
659 | if (isFetchCall) {
|
660 | await this.model.$hooks.exec('after', 'fetch', result);
|
661 | }
|
662 | return result;
|
663 | }
|
664 | |
665 |
|
666 |
|
667 | async paginate(page, perPage = 20) {
|
668 | const isFetchCall = this.wrapResultsToModelInstances && this.knexQuery['_method'] === 'select';
|
669 | |
670 |
|
671 |
|
672 | page = Number(page);
|
673 | perPage = Number(perPage);
|
674 | const countQuery = this.clone()
|
675 | .clearOrder()
|
676 | .clearLimit()
|
677 | .clearOffset()
|
678 | .clearSelect()
|
679 | .count('* as total')
|
680 | .pojo();
|
681 | |
682 |
|
683 |
|
684 |
|
685 | if (isFetchCall) {
|
686 | await this.model.$hooks.exec('before', 'paginate', [countQuery, this]);
|
687 | await this.model.$hooks.exec('before', 'fetch', this);
|
688 | }
|
689 | const aggregateResult = await countQuery.exec();
|
690 | const total = this.hasGroupBy ? aggregateResult.length : aggregateResult[0].total;
|
691 | const results = total > 0 ? await this.forPage(page, perPage).execQuery() : [];
|
692 | |
693 |
|
694 |
|
695 | const paginator = this.wrapResultsToModelInstances
|
696 | ? new Paginator_1.ModelPaginator(total, perPage, page, ...results)
|
697 | : new SimplePaginator_1.SimplePaginator(total, perPage, page, ...results);
|
698 | paginator.namingStrategy = this.model.namingStrategy;
|
699 | if (isFetchCall) {
|
700 | await this.model.$hooks.exec('after', 'paginate', paginator);
|
701 | await this.model.$hooks.exec('after', 'fetch', results);
|
702 | }
|
703 | return paginator;
|
704 | }
|
705 | |
706 |
|
707 |
|
708 | toSQL() {
|
709 | this.applyWhere();
|
710 | return this.knexQuery.toSQL();
|
711 | }
|
712 | |
713 |
|
714 |
|
715 |
|
716 | pojo() {
|
717 | this.wrapResultsToModelInstances = false;
|
718 | return this;
|
719 | }
|
720 | |
721 |
|
722 |
|
723 | then(resolve, reject) {
|
724 | return this.exec().then(resolve, reject);
|
725 | }
|
726 | |
727 |
|
728 |
|
729 | catch(reject) {
|
730 | return this.exec().catch(reject);
|
731 | }
|
732 | |
733 |
|
734 |
|
735 | finally(fullfilled) {
|
736 | return this.exec().finally(fullfilled);
|
737 | }
|
738 | |
739 |
|
740 |
|
741 | get [Symbol.toStringTag]() {
|
742 | return this.constructor.name;
|
743 | }
|
744 | }
|
745 | exports.ModelQueryBuilder = ModelQueryBuilder;
|
746 |
|
747 |
|
748 |
|
749 | Object.defineProperty(ModelQueryBuilder, "macros", {
|
750 | enumerable: true,
|
751 | configurable: true,
|
752 | writable: true,
|
753 | value: {}
|
754 | });
|
755 | Object.defineProperty(ModelQueryBuilder, "getters", {
|
756 | enumerable: true,
|
757 | configurable: true,
|
758 | writable: true,
|
759 | value: {}
|
760 | });
|