outlet-orm 2.5.0 → 3.1.0
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 +674 -313
- package/package.json +1 -1
- package/src/DatabaseConnection.js +464 -110
- package/src/Model.js +1118 -659
- package/src/QueryBuilder.js +794 -710
- package/src/index.js +9 -1
- package/types/index.d.ts +126 -16
package/src/QueryBuilder.js
CHANGED
|
@@ -1,710 +1,794 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Query Builder for constructing and executing database queries
|
|
3
|
-
*/
|
|
4
|
-
class QueryBuilder {
|
|
5
|
-
constructor(model) {
|
|
6
|
-
this.model = model;
|
|
7
|
-
this.wheres = [];
|
|
8
|
-
this.orders = [];
|
|
9
|
-
this.limitValue = null;
|
|
10
|
-
this.offsetValue = null;
|
|
11
|
-
this.selectedColumns = ['*'];
|
|
12
|
-
this.withRelations = [];
|
|
13
|
-
this.withConstraints = {};
|
|
14
|
-
this.joins = [];
|
|
15
|
-
this.distinctFlag = false;
|
|
16
|
-
this.groupBys = [];
|
|
17
|
-
this.havings = [];
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
this.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
*
|
|
56
|
-
* @returns {this}
|
|
57
|
-
*/
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
*
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
*
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
*
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
*
|
|
127
|
-
* @
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
*
|
|
140
|
-
* @
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
*
|
|
151
|
-
* @param {
|
|
152
|
-
* @
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if (
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
*
|
|
209
|
-
* @param {
|
|
210
|
-
* @
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
*
|
|
280
|
-
* @param {
|
|
281
|
-
* @
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
this.
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
*
|
|
341
|
-
* @param {
|
|
342
|
-
* @returns {this}
|
|
343
|
-
*/
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
*
|
|
351
|
-
* @
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
return this;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
* @
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
*
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
const
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
*
|
|
481
|
-
* @
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
*
|
|
556
|
-
* @
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
*
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
*
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
*
|
|
637
|
-
* @param {
|
|
638
|
-
* @param {
|
|
639
|
-
* @returns {Promise<
|
|
640
|
-
*/
|
|
641
|
-
async
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Query Builder for constructing and executing database queries
|
|
3
|
+
*/
|
|
4
|
+
class QueryBuilder {
|
|
5
|
+
constructor(model) {
|
|
6
|
+
this.model = model;
|
|
7
|
+
this.wheres = [];
|
|
8
|
+
this.orders = [];
|
|
9
|
+
this.limitValue = null;
|
|
10
|
+
this.offsetValue = null;
|
|
11
|
+
this.selectedColumns = ['*'];
|
|
12
|
+
this.withRelations = [];
|
|
13
|
+
this.withConstraints = {};
|
|
14
|
+
this.joins = [];
|
|
15
|
+
this.distinctFlag = false;
|
|
16
|
+
this.groupBys = [];
|
|
17
|
+
this.havings = [];
|
|
18
|
+
this._showHidden = false;
|
|
19
|
+
this._withTrashed = false;
|
|
20
|
+
this._onlyTrashed = false;
|
|
21
|
+
this._excludedScopes = [];
|
|
22
|
+
this._excludeAllScopes = false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Apply global scopes to the query
|
|
27
|
+
* @private
|
|
28
|
+
*/
|
|
29
|
+
_applyGlobalScopes() {
|
|
30
|
+
if (this._excludeAllScopes) return;
|
|
31
|
+
|
|
32
|
+
const scopes = this.model.globalScopes || {};
|
|
33
|
+
for (const [name, scopeFn] of Object.entries(scopes)) {
|
|
34
|
+
if (!this._excludedScopes.includes(name)) {
|
|
35
|
+
scopeFn(this);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Apply soft delete constraints
|
|
42
|
+
* @private
|
|
43
|
+
*/
|
|
44
|
+
_applySoftDeleteConstraints() {
|
|
45
|
+
if (!this.model.softDeletes) return;
|
|
46
|
+
|
|
47
|
+
if (this._onlyTrashed) {
|
|
48
|
+
this.whereNotNull(this.model.DELETED_AT);
|
|
49
|
+
} else if (!this._withTrashed) {
|
|
50
|
+
this.whereNull(this.model.DELETED_AT);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Include soft deleted records
|
|
56
|
+
* @returns {this}
|
|
57
|
+
*/
|
|
58
|
+
withTrashed() {
|
|
59
|
+
this._withTrashed = true;
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Only get soft deleted records
|
|
65
|
+
* @returns {this}
|
|
66
|
+
*/
|
|
67
|
+
onlyTrashed() {
|
|
68
|
+
this._onlyTrashed = true;
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Query without a specific global scope
|
|
74
|
+
* @param {string} name
|
|
75
|
+
* @returns {this}
|
|
76
|
+
*/
|
|
77
|
+
withoutGlobalScope(name) {
|
|
78
|
+
this._excludedScopes.push(name);
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Query without all global scopes
|
|
84
|
+
* @returns {this}
|
|
85
|
+
*/
|
|
86
|
+
withoutGlobalScopes() {
|
|
87
|
+
this._excludeAllScopes = true;
|
|
88
|
+
return this;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Select specific columns
|
|
93
|
+
* @param {...string} columns
|
|
94
|
+
* @returns {this}
|
|
95
|
+
*/
|
|
96
|
+
select(...columns) {
|
|
97
|
+
this.selectedColumns = columns;
|
|
98
|
+
return this;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Convenience alias to pass an array of columns
|
|
103
|
+
* @param {string[]} cols
|
|
104
|
+
* @returns {this}
|
|
105
|
+
*/
|
|
106
|
+
columns(cols) {
|
|
107
|
+
if (Array.isArray(cols)) {
|
|
108
|
+
this.selectedColumns = cols;
|
|
109
|
+
}
|
|
110
|
+
return this;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Select distinct
|
|
115
|
+
* @returns {this}
|
|
116
|
+
*/
|
|
117
|
+
distinct() {
|
|
118
|
+
this.distinctFlag = true;
|
|
119
|
+
return this;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Add a basic where clause
|
|
124
|
+
* @param {string} column
|
|
125
|
+
* @param {string|any} operator
|
|
126
|
+
* @param {any} value
|
|
127
|
+
* @returns {this}
|
|
128
|
+
*/
|
|
129
|
+
where(column, operator, value) {
|
|
130
|
+
if (arguments.length === 2) {
|
|
131
|
+
value = operator;
|
|
132
|
+
operator = '=';
|
|
133
|
+
}
|
|
134
|
+
this.wheres.push({ column, operator, value, type: 'basic', boolean: 'and' });
|
|
135
|
+
return this;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Add a where in clause
|
|
140
|
+
* @param {string} column
|
|
141
|
+
* @param {Array} values
|
|
142
|
+
* @returns {this}
|
|
143
|
+
*/
|
|
144
|
+
whereIn(column, values) {
|
|
145
|
+
this.wheres.push({ column, values, type: 'in', boolean: 'and' });
|
|
146
|
+
return this;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Add a where not in clause
|
|
151
|
+
* @param {string} column
|
|
152
|
+
* @param {Array} values
|
|
153
|
+
* @returns {this}
|
|
154
|
+
*/
|
|
155
|
+
whereNotIn(column, values) {
|
|
156
|
+
this.wheres.push({ column, values, type: 'notIn', boolean: 'and' });
|
|
157
|
+
return this;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Add a where null clause
|
|
162
|
+
* @param {string} column
|
|
163
|
+
* @returns {this}
|
|
164
|
+
*/
|
|
165
|
+
whereNull(column) {
|
|
166
|
+
this.wheres.push({ column, type: 'null', boolean: 'and' });
|
|
167
|
+
return this;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Add a where not null clause
|
|
172
|
+
* @param {string} column
|
|
173
|
+
* @returns {this}
|
|
174
|
+
*/
|
|
175
|
+
whereNotNull(column) {
|
|
176
|
+
this.wheres.push({ column, type: 'notNull', boolean: 'and' });
|
|
177
|
+
return this;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Add an or where clause
|
|
182
|
+
* @param {string} column
|
|
183
|
+
* @param {string|any} operator
|
|
184
|
+
* @param {any} value
|
|
185
|
+
* @returns {this}
|
|
186
|
+
*/
|
|
187
|
+
orWhere(column, operator, value) {
|
|
188
|
+
if (arguments.length === 2) {
|
|
189
|
+
value = operator;
|
|
190
|
+
operator = '=';
|
|
191
|
+
}
|
|
192
|
+
this.wheres.push({ column, operator, value, type: 'basic', boolean: 'or' });
|
|
193
|
+
return this;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Add a where between clause
|
|
198
|
+
* @param {string} column
|
|
199
|
+
* @param {Array} values
|
|
200
|
+
* @returns {this}
|
|
201
|
+
*/
|
|
202
|
+
whereBetween(column, values) {
|
|
203
|
+
this.wheres.push({ column, values, type: 'between', boolean: 'and' });
|
|
204
|
+
return this;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Add a where like clause
|
|
209
|
+
* @param {string} column
|
|
210
|
+
* @param {string} value
|
|
211
|
+
* @returns {this}
|
|
212
|
+
*/
|
|
213
|
+
whereLike(column, value) {
|
|
214
|
+
this.wheres.push({ column, value, type: 'like', boolean: 'and' });
|
|
215
|
+
return this;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Filter parents where the given relation has at least one matching record.
|
|
220
|
+
* Implements via INNER JOIN and applying the related where clauses.
|
|
221
|
+
* @param {string} relationName
|
|
222
|
+
* @param {(qb: QueryBuilder) => void} [callback]
|
|
223
|
+
* @returns {this}
|
|
224
|
+
*/
|
|
225
|
+
whereHas(relationName, callback) {
|
|
226
|
+
// Create a dummy parent instance to construct the relation
|
|
227
|
+
const parent = new this.model();
|
|
228
|
+
const fn = parent[relationName];
|
|
229
|
+
if (typeof fn !== 'function') {
|
|
230
|
+
throw new Error(`Relation '${relationName}' is not defined on ${this.model.name}`);
|
|
231
|
+
}
|
|
232
|
+
const relation = fn.call(parent);
|
|
233
|
+
if (!relation?.related || !relation?.foreignKey || !relation?.localKey) {
|
|
234
|
+
throw new Error(`Invalid relation '${relationName}' on ${this.model.name}`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const parentTable = this.model.table;
|
|
238
|
+
const relatedClass = relation.related;
|
|
239
|
+
const relatedTable = relatedClass.table;
|
|
240
|
+
|
|
241
|
+
// Heuristic to detect relation direction
|
|
242
|
+
const relatedDerivedFK = `${relatedTable.replace(/s$/, '')}_id`;
|
|
243
|
+
|
|
244
|
+
// Build ON condition depending on relation type
|
|
245
|
+
let onLeft, onRight;
|
|
246
|
+
if (relation.foreignKey === relatedDerivedFK) {
|
|
247
|
+
// belongsTo: parent has FK to related
|
|
248
|
+
onLeft = `${relatedTable}.${relation.localKey}`; // related.ownerKey
|
|
249
|
+
onRight = `${parentTable}.${relation.foreignKey}`; // parent.foreignKey
|
|
250
|
+
} else {
|
|
251
|
+
// hasOne/hasMany: related has FK to parent
|
|
252
|
+
onLeft = `${relatedTable}.${relation.foreignKey}`; // related.foreignKey -> parent
|
|
253
|
+
onRight = `${parentTable}.${relation.localKey}`; // parent.localKey (usually PK)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Ensure the join exists
|
|
257
|
+
this.join(relatedTable, onLeft, '=', onRight);
|
|
258
|
+
|
|
259
|
+
if (typeof callback === 'function') {
|
|
260
|
+
const relatedQB = new QueryBuilder(relatedClass);
|
|
261
|
+
callback(relatedQB);
|
|
262
|
+
|
|
263
|
+
// Prefix related wheres with table name when necessary
|
|
264
|
+
for (const w of relatedQB.wheres) {
|
|
265
|
+
const clone = { ...w };
|
|
266
|
+
if (clone.column && !/\./.test(clone.column)) {
|
|
267
|
+
clone.column = `${relatedTable}.${clone.column}`;
|
|
268
|
+
}
|
|
269
|
+
this.wheres.push(clone);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return this;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Filter parents that have related rows count matching operator and count
|
|
278
|
+
* @param {string} relationName
|
|
279
|
+
* @param {string|number} operatorOrCount
|
|
280
|
+
* @param {number} [count]
|
|
281
|
+
* @returns {this}
|
|
282
|
+
*/
|
|
283
|
+
has(relationName, operatorOrCount = '>=', count = 1) {
|
|
284
|
+
let operator = operatorOrCount;
|
|
285
|
+
if (typeof operatorOrCount === 'number') {
|
|
286
|
+
operator = '>=';
|
|
287
|
+
count = operatorOrCount;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Reuse whereHas join logic without extra wheres
|
|
291
|
+
this.whereHas(relationName);
|
|
292
|
+
|
|
293
|
+
const parentTable = this.model.table;
|
|
294
|
+
const parentPk = this.model.primaryKey || 'id';
|
|
295
|
+
|
|
296
|
+
// Group by parent primary key and having count
|
|
297
|
+
if (!this.groupBys.includes(`${parentTable}.${parentPk}`)) {
|
|
298
|
+
this.groupBys.push(`${parentTable}.${parentPk}`);
|
|
299
|
+
}
|
|
300
|
+
this.havings.push({ type: 'count', column: '*', operator, value: count });
|
|
301
|
+
return this;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Filter parents that do not have related rows (no callback support for now)
|
|
306
|
+
* @param {string} relationName
|
|
307
|
+
* @returns {this}
|
|
308
|
+
*/
|
|
309
|
+
whereDoesntHave(relationName) {
|
|
310
|
+
const parent = new this.model();
|
|
311
|
+
const fn = parent[relationName];
|
|
312
|
+
if (typeof fn !== 'function') {
|
|
313
|
+
throw new Error(`Relation '${relationName}' is not defined on ${this.model.name}`);
|
|
314
|
+
}
|
|
315
|
+
const relation = fn.call(parent);
|
|
316
|
+
const relatedClass = relation.related;
|
|
317
|
+
const relatedTable = relatedClass.table;
|
|
318
|
+
const parentTable = this.model.table;
|
|
319
|
+
|
|
320
|
+
// Heuristic to detect direction as above
|
|
321
|
+
const relatedDerivedFK = `${relatedTable.replace(/s$/, '')}_id`;
|
|
322
|
+
let onLeft, onRight;
|
|
323
|
+
if (relation.foreignKey === relatedDerivedFK) {
|
|
324
|
+
onLeft = `${relatedTable}.${relation.localKey}`;
|
|
325
|
+
onRight = `${parentTable}.${relation.foreignKey}`;
|
|
326
|
+
} else {
|
|
327
|
+
onLeft = `${relatedTable}.${relation.foreignKey}`;
|
|
328
|
+
onRight = `${parentTable}.${relation.localKey}`;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// LEFT JOIN and ensure null on related PK
|
|
332
|
+
this.leftJoin(relatedTable, onLeft, '=', onRight);
|
|
333
|
+
const relatedPk = relatedClass.primaryKey || 'id';
|
|
334
|
+
this.whereNull(`${relatedTable}.${relatedPk}`);
|
|
335
|
+
return this;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Add an order by clause
|
|
340
|
+
* @param {string} column
|
|
341
|
+
* @param {string} direction
|
|
342
|
+
* @returns {this}
|
|
343
|
+
*/
|
|
344
|
+
orderBy(column, direction = 'asc') {
|
|
345
|
+
this.orders.push({ column, direction: direction.toLowerCase() });
|
|
346
|
+
return this;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Typo-friendly alias for orderBy
|
|
351
|
+
* @param {string} column
|
|
352
|
+
* @param {string} direction
|
|
353
|
+
* @returns {this}
|
|
354
|
+
*/
|
|
355
|
+
ordrer(column, direction = 'asc') {
|
|
356
|
+
return this.orderBy(column, direction);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Set the limit
|
|
361
|
+
* @param {number} value
|
|
362
|
+
* @returns {this}
|
|
363
|
+
*/
|
|
364
|
+
limit(value) {
|
|
365
|
+
this.limitValue = value;
|
|
366
|
+
return this;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Set the offset
|
|
371
|
+
* @param {number} value
|
|
372
|
+
* @returns {this}
|
|
373
|
+
*/
|
|
374
|
+
offset(value) {
|
|
375
|
+
this.offsetValue = value;
|
|
376
|
+
return this;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Group by columns
|
|
381
|
+
* @param {...string} columns
|
|
382
|
+
* @returns {this}
|
|
383
|
+
*/
|
|
384
|
+
groupBy(...columns) {
|
|
385
|
+
this.groupBys.push(...columns);
|
|
386
|
+
return this;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Having clause (basic)
|
|
391
|
+
* @param {string} column
|
|
392
|
+
* @param {string} operator
|
|
393
|
+
* @param {any} value
|
|
394
|
+
* @returns {this}
|
|
395
|
+
*/
|
|
396
|
+
having(column, operator, value) {
|
|
397
|
+
this.havings.push({ type: 'basic', column, operator, value });
|
|
398
|
+
return this;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Set the number of records to skip
|
|
403
|
+
* @param {number} value
|
|
404
|
+
* @returns {this}
|
|
405
|
+
*/
|
|
406
|
+
skip(value) {
|
|
407
|
+
return this.offset(value);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Set the number of records to take
|
|
412
|
+
* @param {number} value
|
|
413
|
+
* @returns {this}
|
|
414
|
+
*/
|
|
415
|
+
take(value) {
|
|
416
|
+
return this.limit(value);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Eager load relations
|
|
421
|
+
* @param {...string} relations
|
|
422
|
+
* @returns {this}
|
|
423
|
+
*/
|
|
424
|
+
with(...relations) {
|
|
425
|
+
// Support forms: with('a', 'b') | with(['a','b']) | with({ a: cb })
|
|
426
|
+
if (relations.length === 1 && Array.isArray(relations[0])) {
|
|
427
|
+
this.withRelations.push(...relations[0]);
|
|
428
|
+
} else if (relations.length === 1 && typeof relations[0] === 'object' && !Array.isArray(relations[0])) {
|
|
429
|
+
const obj = relations[0];
|
|
430
|
+
for (const [name, cb] of Object.entries(obj)) {
|
|
431
|
+
this.withRelations.push(name);
|
|
432
|
+
if (typeof cb === 'function') this.withConstraints[name] = cb;
|
|
433
|
+
}
|
|
434
|
+
} else {
|
|
435
|
+
this.withRelations.push(...relations);
|
|
436
|
+
}
|
|
437
|
+
return this;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* withCount helper: adds subquery count columns
|
|
442
|
+
* Supports: withCount('rel') or withCount(['a','b'])
|
|
443
|
+
* @param {string|string[]} rels
|
|
444
|
+
* @returns {this}
|
|
445
|
+
*/
|
|
446
|
+
withCount(rels) {
|
|
447
|
+
const list = Array.isArray(rels) ? rels : [rels];
|
|
448
|
+
for (const name of list) {
|
|
449
|
+
// Build simple subquery for hasOne/hasMany/belongsTo/belongsToMany
|
|
450
|
+
const parent = new this.model();
|
|
451
|
+
const fn = parent[name];
|
|
452
|
+
if (typeof fn !== 'function') continue;
|
|
453
|
+
const relation = fn.call(parent);
|
|
454
|
+
const parentTable = this.model.table;
|
|
455
|
+
const relatedClass = relation.related;
|
|
456
|
+
const relatedTable = relatedClass.table;
|
|
457
|
+
|
|
458
|
+
let sub = '';
|
|
459
|
+
if (relation instanceof require('./Relations/BelongsToManyRelation')) {
|
|
460
|
+
// belongsToMany: count from pivot
|
|
461
|
+
sub = `(SELECT COUNT(*) FROM ${relation.pivot} WHERE ${relation.pivot}.${relation.foreignPivotKey} = ${parentTable}.${relation.parentKey}) AS ${name}_count`;
|
|
462
|
+
} else if (relation.child) {
|
|
463
|
+
// belongsTo
|
|
464
|
+
const ownerKey = relation.ownerKey || relatedClass.primaryKey || 'id';
|
|
465
|
+
sub = `(SELECT COUNT(*) FROM ${relatedTable} WHERE ${relatedTable}.${ownerKey} = ${parentTable}.${relation.foreignKey}) AS ${name}_count`;
|
|
466
|
+
} else {
|
|
467
|
+
// hasOne/hasMany
|
|
468
|
+
sub = `(SELECT COUNT(*) FROM ${relatedTable} WHERE ${relatedTable}.${relation.foreignKey} = ${parentTable}.${relation.localKey}) AS ${name}_count`;
|
|
469
|
+
}
|
|
470
|
+
this.selectedColumns.push(sub);
|
|
471
|
+
}
|
|
472
|
+
return this;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Add a join clause
|
|
477
|
+
* @param {string} table
|
|
478
|
+
* @param {string} first
|
|
479
|
+
* @param {string} operator
|
|
480
|
+
* @param {string} second
|
|
481
|
+
* @returns {this}
|
|
482
|
+
*/
|
|
483
|
+
join(table, first, operator, second) {
|
|
484
|
+
if (arguments.length === 3) {
|
|
485
|
+
second = operator;
|
|
486
|
+
operator = '=';
|
|
487
|
+
}
|
|
488
|
+
this.joins.push({ table, first, operator, second, type: 'inner' });
|
|
489
|
+
return this;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Add a left join clause
|
|
494
|
+
* @param {string} table
|
|
495
|
+
* @param {string} first
|
|
496
|
+
* @param {string} operator
|
|
497
|
+
* @param {string} second
|
|
498
|
+
* @returns {this}
|
|
499
|
+
*/
|
|
500
|
+
leftJoin(table, first, operator, second) {
|
|
501
|
+
if (arguments.length === 3) {
|
|
502
|
+
second = operator;
|
|
503
|
+
operator = '=';
|
|
504
|
+
}
|
|
505
|
+
this.joins.push({ table, first, operator, second, type: 'left' });
|
|
506
|
+
return this;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Execute the query and get all results
|
|
511
|
+
* @returns {Promise<Array>}
|
|
512
|
+
*/
|
|
513
|
+
async get() {
|
|
514
|
+
// Apply global scopes and soft delete constraints
|
|
515
|
+
this._applyGlobalScopes();
|
|
516
|
+
this._applySoftDeleteConstraints();
|
|
517
|
+
|
|
518
|
+
const rows = await this.model.connection.select(
|
|
519
|
+
this.model.table,
|
|
520
|
+
this.buildQuery()
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
const instances = rows.map(row => this.hydrate(row));
|
|
524
|
+
|
|
525
|
+
if (this.withRelations.length > 0) {
|
|
526
|
+
await this.eagerLoadRelations(instances);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return instances;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Get the first result
|
|
534
|
+
* @returns {Promise<Model|null>}
|
|
535
|
+
*/
|
|
536
|
+
async first() {
|
|
537
|
+
this.limit(1);
|
|
538
|
+
const results = await this.get();
|
|
539
|
+
return results[0] || null;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Get the first result or throw an exception
|
|
544
|
+
* @returns {Promise<Model>}
|
|
545
|
+
*/
|
|
546
|
+
async firstOrFail() {
|
|
547
|
+
const result = await this.first();
|
|
548
|
+
if (!result) {
|
|
549
|
+
throw new Error(`Model not found in table ${this.model.table}`);
|
|
550
|
+
}
|
|
551
|
+
return result;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Paginate the results
|
|
556
|
+
* @param {number} page
|
|
557
|
+
* @param {number} perPage
|
|
558
|
+
* @returns {Promise<Object>}
|
|
559
|
+
*/
|
|
560
|
+
async paginate(page = 1, perPage = 15) {
|
|
561
|
+
const offset = (page - 1) * perPage;
|
|
562
|
+
|
|
563
|
+
// Apply scopes for count
|
|
564
|
+
this._applyGlobalScopes();
|
|
565
|
+
this._applySoftDeleteConstraints();
|
|
566
|
+
|
|
567
|
+
const total = await this.count();
|
|
568
|
+
const data = await this.offset(offset).limit(perPage).get();
|
|
569
|
+
|
|
570
|
+
return {
|
|
571
|
+
data,
|
|
572
|
+
total,
|
|
573
|
+
per_page: perPage,
|
|
574
|
+
current_page: page,
|
|
575
|
+
last_page: Math.ceil(total / perPage),
|
|
576
|
+
from: total > 0 ? offset + 1 : null,
|
|
577
|
+
to: offset + data.length
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Get the count of records
|
|
583
|
+
* @returns {Promise<number>}
|
|
584
|
+
*/
|
|
585
|
+
async count() {
|
|
586
|
+
// Apply scopes for count
|
|
587
|
+
this._applyGlobalScopes();
|
|
588
|
+
this._applySoftDeleteConstraints();
|
|
589
|
+
|
|
590
|
+
const result = await this.model.connection.count(
|
|
591
|
+
this.model.table,
|
|
592
|
+
this.buildQuery()
|
|
593
|
+
);
|
|
594
|
+
return result;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Check if any records exist
|
|
599
|
+
* @returns {Promise<boolean>}
|
|
600
|
+
*/
|
|
601
|
+
async exists() {
|
|
602
|
+
const count = await this.count();
|
|
603
|
+
return count > 0;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Insert records
|
|
608
|
+
* @param {Object|Array<Object>} data
|
|
609
|
+
* @returns {Promise<any>}
|
|
610
|
+
*/
|
|
611
|
+
async insert(data) {
|
|
612
|
+
if (Array.isArray(data)) {
|
|
613
|
+
return this.model.connection.insertMany(this.model.table, data);
|
|
614
|
+
}
|
|
615
|
+
return this.model.connection.insert(this.model.table, data);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Update records
|
|
620
|
+
* @param {Object} attributes
|
|
621
|
+
* @returns {Promise<any>}
|
|
622
|
+
*/
|
|
623
|
+
async update(attributes) {
|
|
624
|
+
if (this.model.timestamps) {
|
|
625
|
+
attributes.updated_at = new Date();
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
return this.model.connection.update(
|
|
629
|
+
this.model.table,
|
|
630
|
+
attributes,
|
|
631
|
+
this.buildQuery()
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Update records and fetch the first updated model, optionally eager loading relations
|
|
637
|
+
* @param {Object} attributes
|
|
638
|
+
* @param {string[]} [relations]
|
|
639
|
+
* @returns {Promise<Model|null>}
|
|
640
|
+
*/
|
|
641
|
+
async updateAndFetch(attributes, relations = []) {
|
|
642
|
+
await this.update(attributes);
|
|
643
|
+
const qb = this.clone();
|
|
644
|
+
if (relations?.length) {
|
|
645
|
+
qb.with(...relations);
|
|
646
|
+
}
|
|
647
|
+
return qb.first();
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* Delete records
|
|
652
|
+
* @returns {Promise<any>}
|
|
653
|
+
*/
|
|
654
|
+
async delete() {
|
|
655
|
+
return this.model.connection.delete(
|
|
656
|
+
this.model.table,
|
|
657
|
+
this.buildQuery()
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Increment a column's value
|
|
663
|
+
* @param {string} column
|
|
664
|
+
* @param {number} amount
|
|
665
|
+
* @returns {Promise<any>}
|
|
666
|
+
*/
|
|
667
|
+
async increment(column, amount = 1) {
|
|
668
|
+
return this.model.connection.increment(
|
|
669
|
+
this.model.table,
|
|
670
|
+
column,
|
|
671
|
+
this.buildQuery(),
|
|
672
|
+
amount
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Decrement a column's value
|
|
678
|
+
* @param {string} column
|
|
679
|
+
* @param {number} amount
|
|
680
|
+
* @returns {Promise<any>}
|
|
681
|
+
*/
|
|
682
|
+
async decrement(column, amount = 1) {
|
|
683
|
+
return this.model.connection.decrement(
|
|
684
|
+
this.model.table,
|
|
685
|
+
column,
|
|
686
|
+
this.buildQuery(),
|
|
687
|
+
amount
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Create a model instance from a database row
|
|
693
|
+
* @param {Object} row
|
|
694
|
+
* @returns {Model}
|
|
695
|
+
*/
|
|
696
|
+
hydrate(row) {
|
|
697
|
+
const instance = new this.model();
|
|
698
|
+
instance.attributes = row;
|
|
699
|
+
instance.original = { ...row };
|
|
700
|
+
instance.exists = true;
|
|
701
|
+
instance._showHidden = this._showHidden;
|
|
702
|
+
return instance;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* Eager load relations for a collection of models
|
|
707
|
+
* @param {Array<Model>} instances
|
|
708
|
+
* @returns {Promise<void>}
|
|
709
|
+
*/
|
|
710
|
+
async eagerLoadRelations(instances) {
|
|
711
|
+
if (instances.length === 0) return;
|
|
712
|
+
|
|
713
|
+
for (const relationName of this.withRelations) {
|
|
714
|
+
await this.loadRelationPath(instances, relationName, this.withConstraints[relationName]);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Load a relation path with support for nested relations (dot notation)
|
|
720
|
+
* @param {Array<Model>} models
|
|
721
|
+
* @param {string} path
|
|
722
|
+
* @param {*} constraint
|
|
723
|
+
* @returns {Promise<void>}
|
|
724
|
+
*/
|
|
725
|
+
async loadRelationPath(models, path, constraint) {
|
|
726
|
+
if (models.length === 0) return;
|
|
727
|
+
|
|
728
|
+
const segments = path.split('.');
|
|
729
|
+
const head = segments[0];
|
|
730
|
+
const tail = segments.slice(1).join('.');
|
|
731
|
+
|
|
732
|
+
// Load head relation eagerly
|
|
733
|
+
const relationInstance = models[0][head];
|
|
734
|
+
if (typeof relationInstance === 'function') {
|
|
735
|
+
const relation = relationInstance.call(models[0]);
|
|
736
|
+
if (relation && typeof relation.eagerLoad === 'function') {
|
|
737
|
+
await relation.eagerLoad(models, head, constraint);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
if (tail) {
|
|
742
|
+
// Collect all related models from the loaded relations
|
|
743
|
+
const relatedModels = models.flatMap(model => {
|
|
744
|
+
const rel = model.relations[head];
|
|
745
|
+
return Array.isArray(rel) ? rel : (rel ? [rel] : []);
|
|
746
|
+
}).filter(Boolean);
|
|
747
|
+
|
|
748
|
+
if (relatedModels.length > 0) {
|
|
749
|
+
// Recursively load the remaining path on related models
|
|
750
|
+
await this.loadRelationPath(relatedModels, tail, null);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* Build the query object
|
|
757
|
+
* @returns {Object}
|
|
758
|
+
*/
|
|
759
|
+
buildQuery() {
|
|
760
|
+
return {
|
|
761
|
+
columns: this.selectedColumns,
|
|
762
|
+
wheres: this.wheres,
|
|
763
|
+
orders: this.orders,
|
|
764
|
+
joins: this.joins,
|
|
765
|
+
distinct: this.distinctFlag,
|
|
766
|
+
groupBys: this.groupBys,
|
|
767
|
+
havings: this.havings,
|
|
768
|
+
limit: this.limitValue,
|
|
769
|
+
offset: this.offsetValue
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* Clone the query builder
|
|
775
|
+
* @returns {QueryBuilder}
|
|
776
|
+
*/
|
|
777
|
+
clone() {
|
|
778
|
+
const cloned = new QueryBuilder(this.model);
|
|
779
|
+
cloned.wheres = [...this.wheres];
|
|
780
|
+
cloned.orders = [...this.orders];
|
|
781
|
+
cloned.limitValue = this.limitValue;
|
|
782
|
+
cloned.offsetValue = this.offsetValue;
|
|
783
|
+
cloned.selectedColumns = [...this.selectedColumns];
|
|
784
|
+
cloned.withRelations = [...this.withRelations];
|
|
785
|
+
cloned.withConstraints = { ...this.withConstraints };
|
|
786
|
+
cloned.joins = [...this.joins];
|
|
787
|
+
cloned.distinctFlag = this.distinctFlag;
|
|
788
|
+
cloned.groupBys = [...this.groupBys];
|
|
789
|
+
cloned.havings = [...this.havings];
|
|
790
|
+
return cloned;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
module.exports = QueryBuilder;
|