@bunyad/orm 0.2.0-beta.1 → 0.2.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/casts.d.ts +16 -3
- package/dist/casts.js +150 -4
- package/dist/eager.js +130 -11
- package/dist/factory.d.ts +20 -1
- package/dist/factory.js +143 -37
- package/dist/index.d.ts +21 -1
- package/dist/index.js +3 -1
- package/dist/model-events.d.ts +8 -2
- package/dist/model-events.js +27 -4
- package/dist/model-query.d.ts +75 -29
- package/dist/model-query.js +252 -133
- package/dist/model.d.ts +164 -34
- package/dist/model.js +708 -44
- package/dist/relations.d.ts +187 -11
- package/dist/relations.js +815 -89
- package/dist/schema-types.d.ts +27 -0
- package/dist/schema-types.js +145 -0
- package/dist/test-drivers.d.ts +10 -0
- package/dist/test-drivers.js +40 -0
- package/dist/typed-names.d.ts +53 -0
- package/dist/typed-names.js +4 -0
- package/package.json +3 -3
package/dist/model-query.js
CHANGED
|
@@ -5,7 +5,7 @@ import { bootIfNotBooted, fireModelEvent, hasModelEventListeners, } from "./mode
|
|
|
5
5
|
import { getGlobalScopes, hasGlobalScopes, } from "./scopes.js";
|
|
6
6
|
import { resolveModelBuilder } from "./decorators.js";
|
|
7
7
|
import { deletedAtColumn, normalizeWithRelations, nowForConnection, parseRelationAlias, singular, usesSoftDeletes, } from "./model-helpers.js";
|
|
8
|
-
import { BelongsTo, BelongsToMany, HasMany, HasManyThrough, HasOne, MorphMany, MorphOne, MorphTo, MorphToMany, morphTypeFor, resolveRelation, } from "./relations.js";
|
|
8
|
+
import { BelongsTo, BelongsToMany, HasMany, HasManyThrough, HasOne, MorphMany, MorphOne, MorphTo, MorphToMany, morphTypeFor, resolveRelation, resolveAggregateRelation, applyPivotWheres, } from "./relations.js";
|
|
9
9
|
import { eagerLoadAggregates, eagerLoadModels } from "./eager.js";
|
|
10
10
|
import {} from "./casts.js";
|
|
11
11
|
import { Model, ModelNotFoundException, attributesNeedModelCasts, filterFillable, resolveConnection, } from "./model.js";
|
|
@@ -19,7 +19,17 @@ export function unwrapModelQuery(query) {
|
|
|
19
19
|
const target = query[MODEL_QUERY_TARGET];
|
|
20
20
|
return target ?? query;
|
|
21
21
|
}
|
|
22
|
-
/**
|
|
22
|
+
/** `whereNot('col', v)` / `whereNot('col', op, v)` become a negated one-condition group. */
|
|
23
|
+
function notGroupCallback(callbackOrColumn, opOrValue, value) {
|
|
24
|
+
if (typeof callbackOrColumn === "function")
|
|
25
|
+
return callbackOrColumn;
|
|
26
|
+
return (q) => {
|
|
27
|
+
if (value === undefined)
|
|
28
|
+
q.where(callbackOrColumn, opOrValue);
|
|
29
|
+
else
|
|
30
|
+
q.where(callbackOrColumn, String(opOrValue), value);
|
|
31
|
+
};
|
|
32
|
+
}
|
|
23
33
|
const MQ_EMPTY = Object.freeze([]);
|
|
24
34
|
const MQ_EMPTY_OBJ = Object.freeze({});
|
|
25
35
|
/** Copy-on-write: return a mutable array (clone shared MQ_EMPTY). */
|
|
@@ -526,15 +536,22 @@ export class ModelQuery {
|
|
|
526
536
|
whereUlid(column, value) {
|
|
527
537
|
return this.where(column, value);
|
|
528
538
|
}
|
|
529
|
-
|
|
530
|
-
whereNot(callback) {
|
|
539
|
+
whereNot(callbackOrColumn, opOrValue, value) {
|
|
531
540
|
this.#simple = false;
|
|
532
|
-
(this.#nestedGroups = mqMut(this.#nestedGroups)).push({
|
|
541
|
+
(this.#nestedGroups = mqMut(this.#nestedGroups)).push({
|
|
542
|
+
boolean: "and",
|
|
543
|
+
not: true,
|
|
544
|
+
callback: notGroupCallback(callbackOrColumn, opOrValue, value),
|
|
545
|
+
});
|
|
533
546
|
return this;
|
|
534
547
|
}
|
|
535
|
-
orWhereNot(
|
|
548
|
+
orWhereNot(callbackOrColumn, opOrValue, value) {
|
|
536
549
|
this.#simple = false;
|
|
537
|
-
(this.#nestedGroups = mqMut(this.#nestedGroups)).push({
|
|
550
|
+
(this.#nestedGroups = mqMut(this.#nestedGroups)).push({
|
|
551
|
+
boolean: "or",
|
|
552
|
+
not: true,
|
|
553
|
+
callback: notGroupCallback(callbackOrColumn, opOrValue, value),
|
|
554
|
+
});
|
|
538
555
|
return this;
|
|
539
556
|
}
|
|
540
557
|
firstWhere(column, opOrValue, value) {
|
|
@@ -581,120 +598,132 @@ export class ModelQuery {
|
|
|
581
598
|
const model = await this.sole();
|
|
582
599
|
return model[column];
|
|
583
600
|
}
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
601
|
+
#pushAggregate(relation, fn, column, constraint, alias) {
|
|
602
|
+
const parsed = parseRelationAlias(relation);
|
|
603
|
+
const base = parsed.relation;
|
|
604
|
+
const suffix = fn === "count" || fn === "exists" ? fn : `${fn}_${column}`;
|
|
605
|
+
this.#simple = false;
|
|
606
|
+
(this.#withAggregates = mqMut(this.#withAggregates)).push({
|
|
607
|
+
relation: base,
|
|
608
|
+
alias: alias ?? parsed.alias ?? `${base}_${suffix}`,
|
|
609
|
+
fn,
|
|
610
|
+
column: fn === "count" || fn === "exists" ? undefined : column,
|
|
611
|
+
constraint,
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
/** `['posts', 'comments as c' => fn]` — string, list, or map of relation → constraint / alias. */
|
|
615
|
+
#pushAggregates(specs, fn, column) {
|
|
616
|
+
for (const arg of specs) {
|
|
587
617
|
if (Array.isArray(arg)) {
|
|
588
|
-
this
|
|
618
|
+
this.#pushAggregates(arg, fn, column);
|
|
589
619
|
continue;
|
|
590
620
|
}
|
|
591
621
|
if (typeof arg === "string") {
|
|
592
|
-
|
|
593
|
-
this.#simple = false;
|
|
594
|
-
(this.#withAggregates = mqMut(this.#withAggregates)).push({
|
|
595
|
-
relation: parsed.relation,
|
|
596
|
-
alias: parsed.alias ?? `${parsed.relation}_count`,
|
|
597
|
-
fn: "count",
|
|
598
|
-
});
|
|
599
|
-
(this.#withCounts = mqMut(this.#withCounts)).push(arg);
|
|
622
|
+
this.#pushAggregate(arg, fn, column);
|
|
600
623
|
continue;
|
|
601
624
|
}
|
|
602
625
|
for (const [relation, value] of Object.entries(arg)) {
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
// default alias
|
|
626
|
+
if (typeof value === "function") {
|
|
627
|
+
this.#pushAggregate(relation, fn, column, value);
|
|
606
628
|
}
|
|
607
629
|
else if (typeof value === "string") {
|
|
608
|
-
|
|
630
|
+
this.#pushAggregate(relation, fn, column, undefined, value);
|
|
609
631
|
}
|
|
610
|
-
else if (typeof value === "
|
|
611
|
-
|
|
632
|
+
else if (value && typeof value === "object" && value.as) {
|
|
633
|
+
this.#pushAggregate(relation, fn, column, undefined, value.as);
|
|
612
634
|
}
|
|
613
|
-
else
|
|
614
|
-
|
|
635
|
+
else {
|
|
636
|
+
this.#pushAggregate(relation, fn, column);
|
|
615
637
|
}
|
|
616
|
-
this.#simple = false;
|
|
617
|
-
(this.#withAggregates = mqMut(this.#withAggregates)).push({ relation, alias, fn: "count" });
|
|
618
|
-
(this.#withCounts = mqMut(this.#withCounts)).push(relation);
|
|
619
638
|
}
|
|
620
639
|
}
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* `withCount` — `'posts'`, `'posts as post_total'`, a list, or a map such as
|
|
643
|
+
* `{ "payments as paid": (q) => q.where("status", "paid") }`.
|
|
644
|
+
*/
|
|
645
|
+
withCount(...relations) {
|
|
646
|
+
this.#pushAggregates(relations, "count");
|
|
621
647
|
return this;
|
|
622
648
|
}
|
|
623
|
-
/** `withSum($relation, $column)
|
|
649
|
+
/** `withSum($relation, $column)` — relation may be a constrained map. */
|
|
624
650
|
withSum(relation, column) {
|
|
625
|
-
|
|
626
|
-
const base = parsed.relation;
|
|
627
|
-
this.#simple = false;
|
|
628
|
-
(this.#withAggregates = mqMut(this.#withAggregates)).push({
|
|
629
|
-
relation: base,
|
|
630
|
-
alias: parsed.alias ?? `${base}_sum_${column}`,
|
|
631
|
-
fn: "sum",
|
|
632
|
-
column,
|
|
633
|
-
});
|
|
651
|
+
this.#pushAggregates([relation], "sum", column);
|
|
634
652
|
return this;
|
|
635
653
|
}
|
|
636
654
|
withAvg(relation, column) {
|
|
637
|
-
|
|
638
|
-
const base = parsed.relation;
|
|
639
|
-
this.#simple = false;
|
|
640
|
-
(this.#withAggregates = mqMut(this.#withAggregates)).push({
|
|
641
|
-
relation: base,
|
|
642
|
-
alias: parsed.alias ?? `${base}_avg_${column}`,
|
|
643
|
-
fn: "avg",
|
|
644
|
-
column,
|
|
645
|
-
});
|
|
655
|
+
this.#pushAggregates([relation], "avg", column);
|
|
646
656
|
return this;
|
|
647
657
|
}
|
|
648
658
|
withMin(relation, column) {
|
|
649
|
-
|
|
650
|
-
const base = parsed.relation;
|
|
651
|
-
this.#simple = false;
|
|
652
|
-
(this.#withAggregates = mqMut(this.#withAggregates)).push({
|
|
653
|
-
relation: base,
|
|
654
|
-
alias: parsed.alias ?? `${base}_min_${column}`,
|
|
655
|
-
fn: "min",
|
|
656
|
-
column,
|
|
657
|
-
});
|
|
659
|
+
this.#pushAggregates([relation], "min", column);
|
|
658
660
|
return this;
|
|
659
661
|
}
|
|
660
662
|
withMax(relation, column) {
|
|
661
|
-
|
|
662
|
-
const base = parsed.relation;
|
|
663
|
-
this.#simple = false;
|
|
664
|
-
(this.#withAggregates = mqMut(this.#withAggregates)).push({
|
|
665
|
-
relation: base,
|
|
666
|
-
alias: parsed.alias ?? `${base}_max_${column}`,
|
|
667
|
-
fn: "max",
|
|
668
|
-
column,
|
|
669
|
-
});
|
|
663
|
+
this.#pushAggregates([relation], "max", column);
|
|
670
664
|
return this;
|
|
671
665
|
}
|
|
672
666
|
/** `withExists($relation)`. */
|
|
673
667
|
withExists(...relations) {
|
|
674
|
-
|
|
675
|
-
const parsed = parseRelationAlias(raw);
|
|
676
|
-
this.#simple = false;
|
|
677
|
-
(this.#withAggregates = mqMut(this.#withAggregates)).push({
|
|
678
|
-
relation: parsed.relation,
|
|
679
|
-
alias: parsed.alias ?? `${parsed.relation}_exists`,
|
|
680
|
-
fn: "exists",
|
|
681
|
-
});
|
|
682
|
-
}
|
|
668
|
+
this.#pushAggregates(relations, "exists");
|
|
683
669
|
return this;
|
|
684
670
|
}
|
|
685
671
|
/** `withAggregate($relation, $column, $function)`. */
|
|
686
672
|
withAggregate(relation, column, fn) {
|
|
687
|
-
|
|
688
|
-
|
|
673
|
+
this.#pushAggregates([relation], fn, column);
|
|
674
|
+
return this;
|
|
675
|
+
}
|
|
676
|
+
// ── Subqueries, unions and joins on subqueries (forwarded to the query builder) ──
|
|
677
|
+
#sub(query) {
|
|
678
|
+
return query instanceof ModelQuery ? query.toBase() : query;
|
|
679
|
+
}
|
|
680
|
+
#extra(apply) {
|
|
689
681
|
this.#simple = false;
|
|
690
|
-
(this.#
|
|
691
|
-
relation: base,
|
|
692
|
-
alias: parsed.alias ?? `${base}_${fn}_${column}`,
|
|
693
|
-
fn,
|
|
694
|
-
column: fn === "count" ? undefined : column,
|
|
695
|
-
});
|
|
682
|
+
(this.#builderExtras = mqMut(this.#builderExtras)).push(apply);
|
|
696
683
|
return this;
|
|
697
684
|
}
|
|
685
|
+
/** `selectSub($query, $as)` — add a subquery as a selected column. */
|
|
686
|
+
selectSub(query, as) {
|
|
687
|
+
return this.#extra((q) => void q.selectSub(this.#sub(query), as));
|
|
688
|
+
}
|
|
689
|
+
/** `whereExists($query)` and its `or` / `not` variants. */
|
|
690
|
+
whereExists(query) {
|
|
691
|
+
return this.#extra((q) => void q.whereExists(this.#sub(query)));
|
|
692
|
+
}
|
|
693
|
+
orWhereExists(query) {
|
|
694
|
+
return this.#extra((q) => void q.orWhereExists(this.#sub(query)));
|
|
695
|
+
}
|
|
696
|
+
whereNotExists(query) {
|
|
697
|
+
return this.#extra((q) => void q.whereNotExists(this.#sub(query)));
|
|
698
|
+
}
|
|
699
|
+
orWhereNotExists(query) {
|
|
700
|
+
return this.#extra((q) => void q.orWhereNotExists(this.#sub(query)));
|
|
701
|
+
}
|
|
702
|
+
/** `union` / `unionAll` — the other query must select the same columns. */
|
|
703
|
+
union(query) {
|
|
704
|
+
return this.#extra((q) => void q.union(this.#sub(query)));
|
|
705
|
+
}
|
|
706
|
+
unionAll(query) {
|
|
707
|
+
return this.#extra((q) => void q.unionAll(this.#sub(query)));
|
|
708
|
+
}
|
|
709
|
+
/** `joinSub($query, $as, $first, $op, $second)`. */
|
|
710
|
+
joinSub(query, as, first, op, second) {
|
|
711
|
+
return this.#extra((q) => void q.joinSub(this.#sub(query), as, first, op, second));
|
|
712
|
+
}
|
|
713
|
+
leftJoinSub(query, as, first, op, second) {
|
|
714
|
+
return this.#extra((q) => void q.leftJoinSub(this.#sub(query), as, first, op, second));
|
|
715
|
+
}
|
|
716
|
+
rightJoinSub(query, as, first, op, second) {
|
|
717
|
+
return this.#extra((q) => void q.rightJoinSub(this.#sub(query), as, first, op, second));
|
|
718
|
+
}
|
|
719
|
+
/** `crossJoin($table)`. */
|
|
720
|
+
crossJoin(table) {
|
|
721
|
+
return this.#extra((q) => void q.crossJoin(table));
|
|
722
|
+
}
|
|
723
|
+
/** `groupByRaw($sql, $bindings)`. */
|
|
724
|
+
groupByRaw(sql, bindings = []) {
|
|
725
|
+
return this.#extra((q) => void q.groupByRaw(sql, bindings));
|
|
726
|
+
}
|
|
698
727
|
/** `join` (hydrated models still from the base table). */
|
|
699
728
|
join(table, first, op, second) {
|
|
700
729
|
this.#simple = false;
|
|
@@ -1059,7 +1088,7 @@ export class ModelQuery {
|
|
|
1059
1088
|
(this.#orders = mqMut(this.#orders)).push({ raw: sql });
|
|
1060
1089
|
return this;
|
|
1061
1090
|
}
|
|
1062
|
-
/**
|
|
1091
|
+
/** Alias for `orderByRaw`. */
|
|
1063
1092
|
orderBySql(sql) {
|
|
1064
1093
|
return this.orderByRaw(sql);
|
|
1065
1094
|
}
|
|
@@ -1102,6 +1131,21 @@ export class ModelQuery {
|
|
|
1102
1131
|
take(value) {
|
|
1103
1132
|
return this.limit(value);
|
|
1104
1133
|
}
|
|
1134
|
+
/**
|
|
1135
|
+
* Remove and return this query's limit / offset. Eager loading calls it so a
|
|
1136
|
+
* `with({ posts: (q) => q.limit(3) })` limit applies per parent, not to the
|
|
1137
|
+
* whole batched query.
|
|
1138
|
+
*/
|
|
1139
|
+
takePaging() {
|
|
1140
|
+
const out = {};
|
|
1141
|
+
if (this.#limitValue !== undefined)
|
|
1142
|
+
out.limit = this.#limitValue;
|
|
1143
|
+
if (this.#offsetValue !== undefined)
|
|
1144
|
+
out.offset = this.#offsetValue;
|
|
1145
|
+
this.#limitValue = undefined;
|
|
1146
|
+
this.#offsetValue = undefined;
|
|
1147
|
+
return out;
|
|
1148
|
+
}
|
|
1105
1149
|
offset(value) {
|
|
1106
1150
|
this.#offsetValue = value;
|
|
1107
1151
|
return this;
|
|
@@ -1132,7 +1176,7 @@ export class ModelQuery {
|
|
|
1132
1176
|
return this;
|
|
1133
1177
|
}
|
|
1134
1178
|
/**
|
|
1135
|
-
* OR `LIKE %term%` across columns (
|
|
1179
|
+
* OR `LIKE %term%` across columns (a list `search`).
|
|
1136
1180
|
* Empty/whitespace term is a no-op.
|
|
1137
1181
|
*/
|
|
1138
1182
|
search(columns, term) {
|
|
@@ -1344,7 +1388,7 @@ export class ModelQuery {
|
|
|
1344
1388
|
const params = [];
|
|
1345
1389
|
// Fingerprint (no values) — reuse quoted SQL for repeated list shapes.
|
|
1346
1390
|
let key = this.#withTrashed ? "T" : this.#onlyTrashed ? "O" : "N";
|
|
1347
|
-
key += `\u001e${this.model.table}\u001e${plan.columns}`;
|
|
1391
|
+
key += `\u001e${conn.driver}\u001e${this.model.table}\u001e${plan.columns}`;
|
|
1348
1392
|
for (const w of plan.scopeWheres) {
|
|
1349
1393
|
key += `\u001eG\u001e${w.boolean}\u001e${w.column}\u001e${w.op}`;
|
|
1350
1394
|
if (w.op !== "__null__" && w.op !== "__notnull__")
|
|
@@ -1538,8 +1582,9 @@ export class ModelQuery {
|
|
|
1538
1582
|
#compileHasExistsSelect(conn = this.#queryConnection()) {
|
|
1539
1583
|
const dialect = conn.dialect;
|
|
1540
1584
|
const params = [];
|
|
1585
|
+
// The compiled SQL is dialect specific (identifier quoting), so the driver is part of the key.
|
|
1541
1586
|
let key = this.#withTrashed ? "T" : this.#onlyTrashed ? "O" : "N";
|
|
1542
|
-
key += `\u001e${this.model.table}`;
|
|
1587
|
+
key += `\u001e${conn.driver}\u001e${this.model.table}`;
|
|
1543
1588
|
for (const w of this.#wheres) {
|
|
1544
1589
|
key += `\u001e${w.boolean}\u001e${w.column}\u001e${w.op}`;
|
|
1545
1590
|
if (w.op !== "__null__" && w.op !== "__notnull__")
|
|
@@ -1692,18 +1737,21 @@ export class ModelQuery {
|
|
|
1692
1737
|
alias: `${relation}_count`,
|
|
1693
1738
|
fn: "count",
|
|
1694
1739
|
column: undefined,
|
|
1740
|
+
constraint: undefined,
|
|
1695
1741
|
}));
|
|
1696
1742
|
if (aggregates.length > 0) {
|
|
1697
1743
|
q = q.select(`${this.model.table}.*`);
|
|
1698
1744
|
for (const agg of aggregates) {
|
|
1699
|
-
const meta =
|
|
1700
|
-
if (!meta)
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
if (!expr)
|
|
1704
|
-
continue;
|
|
1745
|
+
const meta = resolveAggregateRelation(this.model, agg.relation);
|
|
1746
|
+
if (!meta) {
|
|
1747
|
+
throw new Error(`Cannot aggregate unknown or unsupported relation [${agg.relation}] on ${this.model.name}.`);
|
|
1748
|
+
}
|
|
1705
1749
|
const alias = wrapSqlName(this.model.getConnection().dialect, agg.alias);
|
|
1706
|
-
|
|
1750
|
+
const built = this.#constrainedAggregate(meta, agg);
|
|
1751
|
+
if (!built) {
|
|
1752
|
+
throw new Error(`Relation [${agg.relation}] (${meta.kind}) does not support aggregates yet.`);
|
|
1753
|
+
}
|
|
1754
|
+
q = q.selectRaw(`(${built.sql}) as ${alias}`, built.bindings);
|
|
1707
1755
|
}
|
|
1708
1756
|
}
|
|
1709
1757
|
if (this.#limitValue !== undefined)
|
|
@@ -1716,6 +1764,26 @@ export class ModelQuery {
|
|
|
1716
1764
|
q = q.sharedLock();
|
|
1717
1765
|
return q;
|
|
1718
1766
|
}
|
|
1767
|
+
/**
|
|
1768
|
+
* Apply this model's global scopes onto a correlated subquery (`whereHas`, `withCount`, …),
|
|
1769
|
+
* qualifying columns with `table` so joined pivot / parent columns never clash.
|
|
1770
|
+
* Honors `withoutGlobalScopes()` called inside the user's constraint closure.
|
|
1771
|
+
*/
|
|
1772
|
+
applyGlobalScopesToSubquery(q, table) {
|
|
1773
|
+
if (this.#withoutGlobalScopes === true)
|
|
1774
|
+
return q;
|
|
1775
|
+
if (!hasGlobalScopes(this.model))
|
|
1776
|
+
return q;
|
|
1777
|
+
for (const [name, scope] of getGlobalScopes(this.model)) {
|
|
1778
|
+
if (this.#withoutGlobalScopes?.has(name))
|
|
1779
|
+
continue;
|
|
1780
|
+
const temp = new ModelQuery(this.model, { withoutGlobalScopes: true });
|
|
1781
|
+
scope(temp);
|
|
1782
|
+
temp.qualifyColumns(table);
|
|
1783
|
+
q = temp.applyConstraintsTo(q);
|
|
1784
|
+
}
|
|
1785
|
+
return q;
|
|
1786
|
+
}
|
|
1719
1787
|
#applyGlobalScopesTo(q) {
|
|
1720
1788
|
if (this.#withoutGlobalScopes === true)
|
|
1721
1789
|
return q;
|
|
@@ -1910,18 +1978,27 @@ export class ModelQuery {
|
|
|
1910
1978
|
meta.kind === "morphToMany") {
|
|
1911
1979
|
let applyRelated;
|
|
1912
1980
|
let relatedQuery;
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1981
|
+
const scoped = hasGlobalScopes(meta.related);
|
|
1982
|
+
const relatedRef = meta.kind === "has" && meta.related.table === this.model.table
|
|
1983
|
+
? `${meta.related.table}_has`
|
|
1984
|
+
: meta.related.table;
|
|
1985
|
+
if (callback || scoped) {
|
|
1986
|
+
const rq = meta.related.newQuery();
|
|
1987
|
+
callback?.(rq);
|
|
1918
1988
|
if (this.#relatedConstraintsAreMergeable(rq)) {
|
|
1919
|
-
|
|
1989
|
+
const flat = this.#bindFlatRelatedApply(rq);
|
|
1990
|
+
applyRelated = scoped
|
|
1991
|
+
? (sub) => {
|
|
1992
|
+
flat(sub);
|
|
1993
|
+
rq.applyGlobalScopesToSubquery(sub, relatedRef);
|
|
1994
|
+
}
|
|
1995
|
+
: flat;
|
|
1920
1996
|
}
|
|
1921
1997
|
else {
|
|
1922
1998
|
relatedQuery = rq;
|
|
1923
1999
|
applyRelated = (sub) => {
|
|
1924
2000
|
rq.applyConstraintsTo(sub);
|
|
2001
|
+
rq.applyGlobalScopesToSubquery(sub, relatedRef);
|
|
1925
2002
|
};
|
|
1926
2003
|
}
|
|
1927
2004
|
}
|
|
@@ -1974,6 +2051,9 @@ export class ModelQuery {
|
|
|
1974
2051
|
sub.from(relatedTable);
|
|
1975
2052
|
sub.whereColumn(`${relatedTable}.${foreignKey}`, `${parentTable}.${localKey}`);
|
|
1976
2053
|
}
|
|
2054
|
+
if (meta.typeColumn) {
|
|
2055
|
+
sub.where(`${selfAlias ?? relatedTable}.${meta.typeColumn}`, meta.morphType);
|
|
2056
|
+
}
|
|
1977
2057
|
applyRelated?.(sub);
|
|
1978
2058
|
};
|
|
1979
2059
|
}
|
|
@@ -1999,6 +2079,8 @@ export class ModelQuery {
|
|
|
1999
2079
|
if (morphTypeColumn && morphTypes) {
|
|
2000
2080
|
sub.whereIn(`${pivotTable}.${morphTypeColumn}`, morphTypes);
|
|
2001
2081
|
}
|
|
2082
|
+
if (meta.kind === "belongsToMany")
|
|
2083
|
+
applyPivotWheres(sub, pivotTable, meta.pivotWheres);
|
|
2002
2084
|
applyRelated?.(sub);
|
|
2003
2085
|
sub.select(`${pivotTable}.${foreignPivotKey}`);
|
|
2004
2086
|
};
|
|
@@ -2012,6 +2094,8 @@ export class ModelQuery {
|
|
|
2012
2094
|
sub.whereIn(`${pivotTable}.${morphTypeColumn}`, morphTypes);
|
|
2013
2095
|
sub.whereColumn(`${pivotTable}.${pivotTenantKey}`, `${parentTable}.${parentTenantKey}`);
|
|
2014
2096
|
}
|
|
2097
|
+
if (meta.kind === "belongsToMany")
|
|
2098
|
+
applyPivotWheres(sub, pivotTable, meta.pivotWheres);
|
|
2015
2099
|
applyRelated?.(sub);
|
|
2016
2100
|
};
|
|
2017
2101
|
}
|
|
@@ -2396,6 +2480,8 @@ export class ModelQuery {
|
|
|
2396
2480
|
sub.from(relatedTable);
|
|
2397
2481
|
sub.whereColumn(`${relatedTable}.${meta.foreignKey}`, `${parentTable}.${meta.localKey}`);
|
|
2398
2482
|
}
|
|
2483
|
+
if (meta.typeColumn)
|
|
2484
|
+
sub.where(`${relatedTable}.${meta.typeColumn}`, meta.morphType);
|
|
2399
2485
|
}
|
|
2400
2486
|
else if (meta.kind === "belongsToMany" ||
|
|
2401
2487
|
meta.kind === "morphToMany") {
|
|
@@ -2522,45 +2608,78 @@ export class ModelQuery {
|
|
|
2522
2608
|
}
|
|
2523
2609
|
return constraint.not ? q.whereNotExists(wrap) : q.whereExists(wrap);
|
|
2524
2610
|
}
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
return `SELECT 1 FROM ${relatedTable} as ${alias} WHERE ${where} LIMIT 1`;
|
|
2611
|
+
/** Correlated aggregate subquery for `withCount` / `withSum` / … (optional constraint closure). */
|
|
2612
|
+
#constrainedAggregate(meta, agg) {
|
|
2613
|
+
const parentTable = this.model.table;
|
|
2614
|
+
const sub = this.#baseTable();
|
|
2615
|
+
const related = meta.related;
|
|
2616
|
+
const relatedTable = related.table;
|
|
2617
|
+
// Column prefix for the related side (aliased when the relation is self-referencing).
|
|
2618
|
+
let relatedRef = relatedTable;
|
|
2619
|
+
switch (meta.kind) {
|
|
2620
|
+
case "has": {
|
|
2621
|
+
if (relatedTable === parentTable) {
|
|
2622
|
+
relatedRef = `${relatedTable}_has`;
|
|
2623
|
+
sub.from(`${relatedTable} as ${relatedRef}`);
|
|
2539
2624
|
}
|
|
2540
|
-
|
|
2625
|
+
else {
|
|
2626
|
+
sub.from(relatedTable);
|
|
2627
|
+
}
|
|
2628
|
+
sub.whereColumn(`${relatedRef}.${meta.foreignKey}`, `${parentTable}.${meta.localKey}`);
|
|
2629
|
+
if (meta.typeColumn)
|
|
2630
|
+
sub.where(`${relatedRef}.${meta.typeColumn}`, meta.morphType);
|
|
2631
|
+
break;
|
|
2541
2632
|
}
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2633
|
+
case "belongsTo":
|
|
2634
|
+
sub.from(relatedTable);
|
|
2635
|
+
sub.whereColumn(`${relatedTable}.${meta.ownerKey}`, `${parentTable}.${meta.foreignKey}`);
|
|
2636
|
+
break;
|
|
2637
|
+
case "belongsToMany":
|
|
2638
|
+
case "morphToMany":
|
|
2639
|
+
sub.from(relatedTable);
|
|
2640
|
+
sub.join(meta.pivotTable, `${meta.pivotTable}.${meta.relatedPivotKey}`, "=", `${relatedTable}.${related.primaryKey}`);
|
|
2641
|
+
sub.whereColumn(`${meta.pivotTable}.${meta.foreignPivotKey}`, `${parentTable}.${meta.localKey}`);
|
|
2642
|
+
if (meta.kind === "morphToMany") {
|
|
2643
|
+
sub.whereIn(`${meta.pivotTable}.${meta.morphTypeColumn}`, meta.morphTypes);
|
|
2644
|
+
}
|
|
2645
|
+
else {
|
|
2646
|
+
applyPivotWheres(sub, meta.pivotTable, meta.pivotWheres);
|
|
2647
|
+
}
|
|
2648
|
+
break;
|
|
2649
|
+
case "hasManyThrough":
|
|
2650
|
+
case "hasOneThrough": {
|
|
2651
|
+
const through = meta.through.table;
|
|
2652
|
+
sub.from(relatedTable);
|
|
2653
|
+
sub.join(through, `${through}.${meta.secondLocalKey}`, "=", `${relatedTable}.${meta.secondKey}`);
|
|
2654
|
+
sub.whereColumn(`${through}.${meta.firstKey}`, `${parentTable}.${meta.localKey}`);
|
|
2655
|
+
if (usesSoftDeletes(meta.through)) {
|
|
2656
|
+
sub.whereNull(`${through}.${deletedAtColumn(meta.through)}`);
|
|
2657
|
+
}
|
|
2658
|
+
break;
|
|
2545
2659
|
}
|
|
2546
|
-
|
|
2660
|
+
default:
|
|
2661
|
+
return null;
|
|
2547
2662
|
}
|
|
2548
|
-
if (
|
|
2549
|
-
|
|
2550
|
-
if (fn === "exists") {
|
|
2551
|
-
return `SELECT 1 FROM ${meta.related.table} WHERE ${where} LIMIT 1`;
|
|
2552
|
-
}
|
|
2553
|
-
return `SELECT ${agg} FROM ${meta.related.table} WHERE ${where}`;
|
|
2663
|
+
if (usesSoftDeletes(related)) {
|
|
2664
|
+
sub.whereNull(`${relatedRef}.${deletedAtColumn(related)}`);
|
|
2554
2665
|
}
|
|
2555
|
-
if (
|
|
2556
|
-
|
|
2666
|
+
if (agg.constraint || hasGlobalScopes(related)) {
|
|
2667
|
+
const rq = related.newQuery();
|
|
2668
|
+
agg.constraint?.(rq);
|
|
2669
|
+
rq.applyConstraintsTo(sub);
|
|
2670
|
+
rq.applyGlobalScopesToSubquery(sub, relatedRef);
|
|
2671
|
+
}
|
|
2672
|
+
if (agg.fn === "exists") {
|
|
2673
|
+
sub.select("1");
|
|
2674
|
+
sub.limit(1);
|
|
2557
2675
|
}
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2676
|
+
else {
|
|
2677
|
+
const column = agg.column
|
|
2678
|
+
? agg.column.includes(".") ? agg.column : `${relatedRef}.${agg.column}`
|
|
2679
|
+
: "*";
|
|
2680
|
+
sub.selectRaw(agg.fn === "count" ? "COUNT(*)" : `${agg.fn.toUpperCase()}(${column})`);
|
|
2562
2681
|
}
|
|
2563
|
-
return
|
|
2682
|
+
return { sql: sub.toSql(), bindings: sub.getBindings() };
|
|
2564
2683
|
}
|
|
2565
2684
|
first() {
|
|
2566
2685
|
const fast = this.#trySimpleFirst();
|