objection 3.5.0 → 3.7.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 +3 -3
- package/lib/model/AjvValidator.js +33 -10
- package/lib/model/modelClone.js +0 -2
- package/lib/queryBuilder/InternalOptions.js +2 -0
- package/lib/queryBuilder/QueryBuilder.js +56 -10
- package/lib/queryBuilder/RelationExpression.js +6 -4
- package/lib/queryBuilder/graph/GraphOptions.js +141 -22
- package/lib/queryBuilder/graph/GraphUpsert.js +57 -0
- package/lib/queryBuilder/graph/insert/GraphInsert.js +2 -0
- package/lib/queryBuilder/graph/patch/GraphPatchAction.js +3 -0
- package/lib/queryBuilder/operations/InstanceUpdateOperation.js +6 -1
- package/lib/queryBuilder/operations/UpdateOperation.js +11 -0
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeMsSqlOperation.js +3 -1
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeSqliteOperation.js +4 -9
- package/lib/queryBuilder/parsers/jsonFieldExpressionParser.js +434 -364
- package/lib/queryBuilder/parsers/relationExpressionParser.js +513 -548
- package/lib/queryBuilder/parsers/relationExpressionParser.pegjs +1 -1
- package/lib/queryBuilder/transformations/WrapMysqlModifySubqueryTransformation.js +4 -7
- package/lib/relations/Relation.js +7 -0
- package/lib/relations/RelationFindOperation.js +27 -40
- package/lib/relations/belongsToOne/BelongsToOneDeleteOperation.js +4 -2
- package/lib/relations/belongsToOne/BelongsToOneRelation.js +9 -0
- package/lib/relations/manyToMany/ManyToManyModifyMixin.js +41 -13
- package/lib/relations/manyToMany/ManyToManyMySqlModifyMixin.js +31 -42
- package/lib/relations/manyToMany/ManyToManyRelation.js +15 -18
- package/lib/relations/manyToMany/ManyToManySqliteModifyMixin.js +0 -13
- package/lib/relations/manyToMany/find/ManyToManyFindOperation.js +2 -3
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateMySqlOperation.js +1 -5
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperation.js +1 -5
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperationBase.js +9 -0
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateSqliteOperation.js +1 -5
- package/lib/relations/manyToMany/update/ManyToManyUpdateOperationBase.js +28 -2
- package/lib/utils/identifierMapping.js +8 -17
- package/lib/utils/objectUtils.js +109 -2
- package/lib/utils/parseFieldExpression.js +10 -3
- package/package.json +5 -2
- package/typings/objection/index.d.ts +56 -36
- package/lib/relations/manyToMany/ManyToManyPostgresModifyMixin.js +0 -27
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelatePostgresOperation.js +0 -16
- package/lib/relations/manyToMany/update/ManyToManyUpdatePostgresOperation.js +0 -12
- package/lib/utils/clone.js +0 -2329
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
function assertDuplicateRelation(node, expr) {
|
|
3
|
-
if (expr.$name
|
|
3
|
+
if (Object.prototype.hasOwnProperty.call(node, expr.$name)) {
|
|
4
4
|
console.warn(`Duplicate relation "${expr.$name}" in a relation expression. You should use "a.[b, c]" instead of "[a.b, a.c]". This will cause an error in objection 2.0`);
|
|
5
5
|
|
|
6
6
|
// TODO: enable for v2.0.
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
const { QueryTransformation } = require('./QueryTransformation');
|
|
4
4
|
const { isMySql } = require('../../utils/knexUtils');
|
|
5
|
-
const { once } = require('../../utils/objectUtils');
|
|
6
|
-
const getJoinBuilder = once(() => require('../JoinBuilder').JoinBuilder);
|
|
7
5
|
|
|
8
6
|
/**
|
|
9
7
|
* Mysql doesn't allow queries like this:
|
|
@@ -32,11 +30,10 @@ class WrapMysqlModifySubqueryTransformation extends QueryTransformation {
|
|
|
32
30
|
return query;
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return query;
|
|
33
|
+
// Subqueries in where groups and join clauses are converted with a
|
|
34
|
+
// partial builder as their parent. Use the query that they belong to.
|
|
35
|
+
while (parentQuery.isPartial() && parentQuery.parentQuery()) {
|
|
36
|
+
parentQuery = parentQuery.parentQuery();
|
|
40
37
|
}
|
|
41
38
|
|
|
42
39
|
// This transformation only applies to update and delete queries.
|
|
@@ -83,6 +83,12 @@ class Relation {
|
|
|
83
83
|
return false;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
// Whether owner models may legitimately lack the owner join property at
|
|
87
|
+
// `index`, so that it isn't reported as missing (#132).
|
|
88
|
+
isOptionalOwnerProp(_index) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
|
|
86
92
|
clone() {
|
|
87
93
|
const relation = new this.constructor(this.name, this.ownerModelClass);
|
|
88
94
|
|
|
@@ -97,6 +103,7 @@ class Relation {
|
|
|
97
103
|
relation.joinTableRelatedProp = this.joinTableRelatedProp;
|
|
98
104
|
relation.joinTableBeforeInsert = this.joinTableBeforeInsert;
|
|
99
105
|
relation.joinTableExtras = this.joinTableExtras;
|
|
106
|
+
relation.joinTableModify = this.joinTableModify;
|
|
100
107
|
|
|
101
108
|
return relation;
|
|
102
109
|
}
|
|
@@ -12,7 +12,6 @@ class RelationFindOperation extends FindOperation {
|
|
|
12
12
|
this.alwaysReturnArray = false;
|
|
13
13
|
this.assignResultToOwner = true;
|
|
14
14
|
this.relationProperty = opt.relationProperty || this.relation.name;
|
|
15
|
-
this.omitProps = [];
|
|
16
15
|
this.alias = null;
|
|
17
16
|
}
|
|
18
17
|
|
|
@@ -21,10 +20,9 @@ class RelationFindOperation extends FindOperation {
|
|
|
21
20
|
this.relation.findQuery(builder, this.owner);
|
|
22
21
|
|
|
23
22
|
if (this.owner.isModels) {
|
|
24
|
-
this.
|
|
23
|
+
this.warnOnInvalidOwnerProps();
|
|
25
24
|
|
|
26
25
|
if (this.assignResultToOwner) {
|
|
27
|
-
this.warnOnArrayOwnerProps();
|
|
28
26
|
this.selectMissingJoinColumns(builder);
|
|
29
27
|
}
|
|
30
28
|
}
|
|
@@ -70,11 +68,6 @@ class RelationFindOperation extends FindOperation {
|
|
|
70
68
|
|
|
71
69
|
onAfter3(builder, related) {
|
|
72
70
|
const isOneToOne = this.relation.isOneToOne();
|
|
73
|
-
const intOpt = builder.internalOptions();
|
|
74
|
-
|
|
75
|
-
if (!intOpt.keepImplicitJoinProps) {
|
|
76
|
-
this.omitImplicitJoinProps(related);
|
|
77
|
-
}
|
|
78
71
|
|
|
79
72
|
if (!this.alwaysReturnArray && isOneToOne && related.length <= 1) {
|
|
80
73
|
related = related[0] || undefined;
|
|
@@ -83,14 +76,27 @@ class RelationFindOperation extends FindOperation {
|
|
|
83
76
|
return super.onAfter3(builder, related);
|
|
84
77
|
}
|
|
85
78
|
|
|
79
|
+
warnOnInvalidOwnerProps() {
|
|
80
|
+
this.warnOnMissingOwnerProps();
|
|
81
|
+
|
|
82
|
+
if (this.assignResultToOwner) {
|
|
83
|
+
this.warnOnArrayOwnerProps();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
86
87
|
// Owner models without the join columns, e.g. loaded with a partial
|
|
87
88
|
// `select()`, silently get no related models (#1832). Only `undefined`
|
|
88
|
-
// is reported, as `null` is a legit value for a missing relation
|
|
89
|
+
// is reported, as `null` is a legit value for a missing relation, and
|
|
90
|
+
// owner props that may legitimately be unset are skipped (#132).
|
|
89
91
|
// TODO: Consider throwing an error instead in objection 4.0.
|
|
90
92
|
warnOnMissingOwnerProps() {
|
|
91
93
|
const { ownerProp } = this.relation;
|
|
92
94
|
|
|
93
95
|
for (let c = 0, lc = ownerProp.size; c < lc; ++c) {
|
|
96
|
+
if (this.relation.isOptionalOwnerProp(c)) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
94
100
|
if (this.owner.modelArray.some((owner) => ownerProp.getProp(owner, c) === undefined)) {
|
|
95
101
|
deprecate(
|
|
96
102
|
`Fetching relation "${this.relation.name}" of ${this.relation.ownerModelClass.name}: ` +
|
|
@@ -106,7 +112,11 @@ class RelationFindOperation extends FindOperation {
|
|
|
106
112
|
// assigned to their owners, so all owners silently get no related models
|
|
107
113
|
// (#2667). `$relatedQuery()` without assigning the results isn't affected.
|
|
108
114
|
warnOnArrayOwnerProps() {
|
|
109
|
-
const { ownerProp } = this.relation;
|
|
115
|
+
const { ownerProp, joinTable } = this.relation;
|
|
116
|
+
// Relations through a join table already are the alternative to arrays.
|
|
117
|
+
const advice = joinTable
|
|
118
|
+
? 'Map the relation from a column that holds a single key instead.'
|
|
119
|
+
: 'Use a ManyToManyRelation through a join table instead.';
|
|
110
120
|
|
|
111
121
|
for (let c = 0, lc = ownerProp.size; c < lc; ++c) {
|
|
112
122
|
if (this.owner.modelArray.some((owner) => Array.isArray(ownerProp.getProp(owner, c)))) {
|
|
@@ -114,7 +124,7 @@ class RelationFindOperation extends FindOperation {
|
|
|
114
124
|
`Fetching relation "${this.relation.name}" of ${this.relation.ownerModelClass.name}: ` +
|
|
115
125
|
`the join property "${ownerProp.propDescription(c)}" of some owner models holds ` +
|
|
116
126
|
'an array, which is not supported, so no related models are found for them. ' +
|
|
117
|
-
|
|
127
|
+
advice,
|
|
118
128
|
);
|
|
119
129
|
}
|
|
120
130
|
}
|
|
@@ -149,7 +159,7 @@ class RelationFindOperation extends FindOperation {
|
|
|
149
159
|
const col = relatedProp.cols[c];
|
|
150
160
|
|
|
151
161
|
if (!builder.hasSelectionAs(fullCol, col) && addedSelects.indexOf(fullCol) === -1) {
|
|
152
|
-
this.
|
|
162
|
+
this.omitFromResult(builder, prop);
|
|
153
163
|
addedSelects.push(fullCol);
|
|
154
164
|
}
|
|
155
165
|
}
|
|
@@ -165,34 +175,12 @@ class RelationFindOperation extends FindOperation {
|
|
|
165
175
|
}
|
|
166
176
|
}
|
|
167
177
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (!
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (!Array.isArray(related)) {
|
|
176
|
-
return this.omitImplicitJoinPropsFromOne(relatedModelClass, related);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (!related.length) {
|
|
180
|
-
return related;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
for (let i = 0, l = related.length; i < l; ++i) {
|
|
184
|
-
this.omitImplicitJoinPropsFromOne(relatedModelClass, related[i]);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
return related;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
omitImplicitJoinPropsFromOne(relatedModelClass, model) {
|
|
191
|
-
for (let c = 0, lc = this.omitProps.length; c < lc; ++c) {
|
|
192
|
-
relatedModelClass.omitImpl(model, this.omitProps[c]);
|
|
178
|
+
// Omits a property that was selected only to assign the related models to
|
|
179
|
+
// their owners from the result, before any `runAfter()` hooks are called.
|
|
180
|
+
omitFromResult(builder, prop) {
|
|
181
|
+
if (!builder.internalOptions().keepImplicitJoinProps) {
|
|
182
|
+
builder.addInternalSelection(prop);
|
|
193
183
|
}
|
|
194
|
-
|
|
195
|
-
return model;
|
|
196
184
|
}
|
|
197
185
|
|
|
198
186
|
clone() {
|
|
@@ -201,7 +189,6 @@ class RelationFindOperation extends FindOperation {
|
|
|
201
189
|
clone.alwaysReturnArray = this.alwaysReturnArray;
|
|
202
190
|
clone.assignResultToOwner = this.assignResultToOwner;
|
|
203
191
|
clone.relationProperty = this.relationProperty;
|
|
204
|
-
clone.omitProps = this.omitProps.slice();
|
|
205
192
|
clone.alias = this.alias;
|
|
206
193
|
|
|
207
194
|
return clone;
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
const { RelationDeleteOperation } = require('../RelationDeleteOperation');
|
|
4
4
|
|
|
5
5
|
class BelongsToOneDeleteOperation extends RelationDeleteOperation {
|
|
6
|
-
onAfter1(
|
|
7
|
-
|
|
6
|
+
onAfter1(builder, result) {
|
|
7
|
+
// A query resolved without executing it, e.g. with `none()`, deleted
|
|
8
|
+
// nothing, so the owners are left untouched.
|
|
9
|
+
if (this.owner.isModels && !builder.isExplicitlyResolvedOrRejected()) {
|
|
8
10
|
const ownerProp = this.relation.ownerProp;
|
|
9
11
|
|
|
10
12
|
for (const owner of this.owner.modelArray) {
|
|
@@ -11,6 +11,15 @@ class BelongsToOneRelation extends Relation {
|
|
|
11
11
|
return true;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
// The foreign key is unset when no related model was ever assigned, e.g. on
|
|
15
|
+
// models returned by `insert()` or created with `fromJson()`, where no
|
|
16
|
+
// related model is the right result. This can't be told apart from a
|
|
17
|
+
// foreign key left out of a partial `select()`. Foreign keys that are part
|
|
18
|
+
// of the id are always expected to be set (#132).
|
|
19
|
+
isOptionalOwnerProp(index) {
|
|
20
|
+
return !this.ownerModelClass.getIdPropertyArray().includes(this.ownerProp.props[index]);
|
|
21
|
+
}
|
|
22
|
+
|
|
14
23
|
insert(_, owner) {
|
|
15
24
|
return new BelongsToOneInsertOperation('insert', {
|
|
16
25
|
relation: this,
|
|
@@ -16,18 +16,27 @@ const ManyToManyModifyMixin = (Operation) => {
|
|
|
16
16
|
constructor(...args) {
|
|
17
17
|
super(...args);
|
|
18
18
|
this.modifyFilterSubquery = null;
|
|
19
|
+
this.joinRowFilterSubquery = null;
|
|
20
|
+
this.skipRelationFilter = false;
|
|
19
21
|
}
|
|
20
22
|
|
|
23
|
+
// Unrelate doesn't execute the main query, and updates of rows that are
|
|
24
|
+
// known to be related, like the patches of `upsertGraph()`, keep their own
|
|
25
|
+
// filters (#135).
|
|
21
26
|
get modifyMainQuery() {
|
|
22
|
-
return
|
|
27
|
+
return !this.modifiesJoinTableOnly && !this.skipRelationFilter;
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
// At this point `builder` should only have the user's own wheres and joins. There can
|
|
26
31
|
// be other operations (like orderBy) too, but those are meaningless with modify operations.
|
|
27
32
|
onBuild(builder) {
|
|
28
|
-
this.
|
|
33
|
+
if (this.modifiesJoinTable && !this.skipRelationFilter) {
|
|
34
|
+
this.joinRowFilterSubquery = this.createJoinRowFilterSubquery(builder);
|
|
35
|
+
}
|
|
29
36
|
|
|
30
37
|
if (this.modifyMainQuery) {
|
|
38
|
+
this.modifyFilterSubquery = this.createModifyFilterSubquery(builder);
|
|
39
|
+
|
|
31
40
|
// We can now remove the where and join statements from the main query.
|
|
32
41
|
this.removeFiltersFromMainQuery(builder);
|
|
33
42
|
|
|
@@ -71,22 +80,39 @@ const ManyToManyModifyMixin = (Operation) => {
|
|
|
71
80
|
return builder.whereInComposite(idRefs, subquery);
|
|
72
81
|
}
|
|
73
82
|
|
|
74
|
-
//
|
|
75
|
-
// reference join table columns, this also modifies join rows that don't match them
|
|
76
|
-
// but share a related row with one that does (#1853). The dialect specific mixins
|
|
77
|
-
// override this to only modify the join rows that match the filters themselves.
|
|
83
|
+
// Only modifies the join rows that match the filters (#1853).
|
|
78
84
|
applyModifyFilterForJoinTable(builder) {
|
|
79
85
|
const joinTableOwnerRefs = this.relation.joinTableOwnerProp.refs(builder);
|
|
80
|
-
const joinTableRelatedRefs = this.relation.joinTableRelatedProp.refs(builder);
|
|
81
|
-
|
|
82
|
-
const relatedRefs = this.relation.relatedProp.refs(builder);
|
|
83
86
|
const ownerValues = this.owner.getProps(this.relation);
|
|
84
87
|
|
|
85
|
-
const subquery = this.modifyFilterSubquery.clone().select(relatedRefs);
|
|
86
|
-
|
|
87
88
|
return builder
|
|
88
|
-
.whereInComposite(
|
|
89
|
-
.
|
|
89
|
+
.whereInComposite(joinTableOwnerRefs, ownerValues)
|
|
90
|
+
.whereExists(this.joinRowFilterSubquery);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Selects the related rows of a join row that match the filters. The join
|
|
94
|
+
// table isn't joined, so that the filters on its columns apply to the join
|
|
95
|
+
// row of the outer query.
|
|
96
|
+
createJoinRowFilterSubquery(builder) {
|
|
97
|
+
const { relation } = this;
|
|
98
|
+
const builderClass = builder.constructor;
|
|
99
|
+
const subquery = relation.relatedModelClass.query().childQueryOf(builder);
|
|
100
|
+
const joinTableRef = builder.tableRefFor(relation.joinTable);
|
|
101
|
+
|
|
102
|
+
addWhereGroup(subquery, () => {
|
|
103
|
+
relation.relatedProp.forEach((i) => {
|
|
104
|
+
const relatedRef = relation.relatedProp.ref(subquery, i);
|
|
105
|
+
const joinTableRelatedRef = relation.joinTableRelatedProp.ref(subquery, i);
|
|
106
|
+
|
|
107
|
+
subquery.where(relatedRef, joinTableRelatedRef.table(joinTableRef));
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return relation
|
|
112
|
+
.applyModify(subquery)
|
|
113
|
+
.copyFrom(builder, builderClass.WhereSelector)
|
|
114
|
+
.copyFrom(builder, builderClass.JoinSelector)
|
|
115
|
+
.select(1);
|
|
90
116
|
}
|
|
91
117
|
|
|
92
118
|
toFindOperation() {
|
|
@@ -108,6 +134,8 @@ const ManyToManyModifyMixin = (Operation) => {
|
|
|
108
134
|
clone() {
|
|
109
135
|
const clone = super.clone();
|
|
110
136
|
clone.modifyFilterSubquery = this.modifyFilterSubquery;
|
|
137
|
+
clone.joinRowFilterSubquery = this.joinRowFilterSubquery;
|
|
138
|
+
clone.skipRelationFilter = this.skipRelationFilter;
|
|
111
139
|
return clone;
|
|
112
140
|
}
|
|
113
141
|
};
|
|
@@ -1,66 +1,55 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { ManyToManyModifyMixin } = require('./ManyToManyModifyMixin');
|
|
4
|
-
const { isObject } = require('../../utils/objectUtils');
|
|
5
4
|
|
|
6
5
|
// We need to override this mixin for mysql because mysql doesn't allow a
|
|
7
|
-
// statement that modifies the join table to read the
|
|
8
|
-
// subquery,
|
|
6
|
+
// statement that modifies the join table to read the join table in a
|
|
7
|
+
// subquery (ER_UPDATE_TABLE_USED), e.g. through the user's filters, or the
|
|
8
|
+
// related table if a trigger on the join table modifies it
|
|
9
|
+
// (ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG).
|
|
9
10
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// has no row ids and the join table's primary key isn't known, so the rows are
|
|
15
|
-
// identified by the values of all their columns.
|
|
11
|
+
// So the owner and related ids of the join rows that match the filters are
|
|
12
|
+
// fetched in a separate query before the statement, which then only modifies
|
|
13
|
+
// the join rows with these ids. Unlike the subquery, these can't tell apart
|
|
14
|
+
// join rows that only differ in their other columns (#1853).
|
|
16
15
|
//
|
|
17
16
|
// https://stackoverflow.com/a/2314264/3729316
|
|
18
17
|
const ManyToManyMySqlModifyMixin = (Operation) => {
|
|
19
18
|
return class extends ManyToManyModifyMixin(Operation) {
|
|
20
19
|
applyModifyFilterForJoinTable(builder) {
|
|
21
|
-
const
|
|
22
|
-
const tableRef = builder.tableRefFor(this.relation.getJoinModelClass(builder));
|
|
23
|
-
|
|
20
|
+
const { joinTableOwnerProp, joinTableRelatedProp } = this.relation;
|
|
24
21
|
const ownerValues = this.owner.getProps(this.relation);
|
|
25
|
-
const subquery = this.
|
|
22
|
+
const subquery = this.joinRowFilterSubquery;
|
|
26
23
|
|
|
27
24
|
return builder
|
|
25
|
+
.whereInComposite(joinTableOwnerProp.refs(builder), ownerValues)
|
|
28
26
|
.runBefore(async (_, builder) => {
|
|
27
|
+
const idRefs = [
|
|
28
|
+
...joinTableOwnerProp.refs(builder),
|
|
29
|
+
...joinTableRelatedProp.refs(builder),
|
|
30
|
+
];
|
|
31
|
+
|
|
29
32
|
// Fetch the raw rows: no models, so that no hooks run for them.
|
|
30
|
-
const rows = await
|
|
33
|
+
const rows = await builder
|
|
34
|
+
.modelClass()
|
|
35
|
+
.query()
|
|
36
|
+
.childQueryOf(builder)
|
|
37
|
+
.copyFrom(builder, builder.constructor.WhereSelector)
|
|
38
|
+
.whereExists(subquery)
|
|
39
|
+
.select(idRefs)
|
|
40
|
+
.toKnexQuery();
|
|
31
41
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
toComparableValue(value),
|
|
40
|
-
]);
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
} else {
|
|
46
|
-
// Still execute the query so that it returns the expected
|
|
47
|
-
// result for the dialect, e.g. a count of 0 for deletes.
|
|
48
|
-
builder.where(false);
|
|
49
|
-
}
|
|
50
|
-
})
|
|
51
|
-
.whereInComposite(joinTableOwnerRefs, ownerValues);
|
|
42
|
+
// An empty list still executes the query, so that it returns the
|
|
43
|
+
// expected result for the dialect, e.g. a count of 0 for deletes.
|
|
44
|
+
builder.whereInComposite(
|
|
45
|
+
idRefs,
|
|
46
|
+
rows.map((row) => Object.values(row)),
|
|
47
|
+
);
|
|
48
|
+
});
|
|
52
49
|
}
|
|
53
50
|
};
|
|
54
51
|
};
|
|
55
52
|
|
|
56
|
-
// Some drivers return JSON columns parsed. Mysql parses strings that are
|
|
57
|
-
// compared to JSON columns, so the stringified value matches them again.
|
|
58
|
-
function toComparableValue(value) {
|
|
59
|
-
return isObject(value) && !(value instanceof Date) && !Buffer.isBuffer(value)
|
|
60
|
-
? JSON.stringify(value)
|
|
61
|
-
: value;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
53
|
module.exports = {
|
|
65
54
|
ManyToManyMySqlModifyMixin,
|
|
66
55
|
};
|
|
@@ -4,7 +4,7 @@ const { getModel } = require('../../model/getModel');
|
|
|
4
4
|
const { Relation } = require('../Relation');
|
|
5
5
|
const { RelationProperty } = require('../RelationProperty');
|
|
6
6
|
|
|
7
|
-
const { isSqlite, isMySql
|
|
7
|
+
const { isSqlite, isMySql } = require('../../utils/knexUtils');
|
|
8
8
|
const { inheritModel } = require('../../model/inheritModel');
|
|
9
9
|
const { resolveModel } = require('../../utils/resolveModel');
|
|
10
10
|
const { mapAfterAllReturn } = require('../../utils/promiseUtils');
|
|
@@ -21,14 +21,10 @@ const {
|
|
|
21
21
|
ManyToManyUnrelateSqliteOperation,
|
|
22
22
|
} = require('./unrelate/ManyToManyUnrelateSqliteOperation');
|
|
23
23
|
const { ManyToManyUnrelateMySqlOperation } = require('./unrelate/ManyToManyUnrelateMySqlOperation');
|
|
24
|
-
const {
|
|
25
|
-
ManyToManyUnrelatePostgresOperation,
|
|
26
|
-
} = require('./unrelate/ManyToManyUnrelatePostgresOperation');
|
|
27
24
|
|
|
28
25
|
const { ManyToManyUpdateOperation } = require('./update/ManyToManyUpdateOperation');
|
|
29
26
|
const { ManyToManyUpdateSqliteOperation } = require('./update/ManyToManyUpdateSqliteOperation');
|
|
30
27
|
const { ManyToManyUpdateMySqlOperation } = require('./update/ManyToManyUpdateMySqlOperation');
|
|
31
|
-
const { ManyToManyUpdatePostgresOperation } = require('./update/ManyToManyUpdatePostgresOperation');
|
|
32
28
|
|
|
33
29
|
const { ManyToManyDeleteOperation } = require('./delete/ManyToManyDeleteOperation');
|
|
34
30
|
const { ManyToManyDeleteSqliteOperation } = require('./delete/ManyToManyDeleteSqliteOperation');
|
|
@@ -93,7 +89,8 @@ class ManyToManyRelation extends Relation {
|
|
|
93
89
|
|
|
94
90
|
// The modify operations can't tell the join rows apart from the related rows
|
|
95
91
|
// if the join table is also the related table, so they would modify the
|
|
96
|
-
// wrong rows (#1803).
|
|
92
|
+
// wrong rows (#1803). Inserting and relating would insert the join rows as
|
|
93
|
+
// new related rows (#133).
|
|
97
94
|
assertCanModifyJoinTable(builder, operationName) {
|
|
98
95
|
if (this.isJoinTableRelatedTable(builder)) {
|
|
99
96
|
throw this.createError(
|
|
@@ -188,7 +185,9 @@ class ManyToManyRelation extends Relation {
|
|
|
188
185
|
});
|
|
189
186
|
}
|
|
190
187
|
|
|
191
|
-
insert(
|
|
188
|
+
insert(builder, owner) {
|
|
189
|
+
this.assertCanModifyJoinTable(builder, 'insert');
|
|
190
|
+
|
|
192
191
|
return new ManyToManyInsertOperation('insert', {
|
|
193
192
|
relation: this,
|
|
194
193
|
owner,
|
|
@@ -218,6 +217,8 @@ class ManyToManyRelation extends Relation {
|
|
|
218
217
|
}
|
|
219
218
|
|
|
220
219
|
relate(builder, owner) {
|
|
220
|
+
this.assertCanModifyJoinTable(builder, 'relate');
|
|
221
|
+
|
|
221
222
|
return new ManyToManyRelateOperation('relate', {
|
|
222
223
|
relation: this,
|
|
223
224
|
owner,
|
|
@@ -498,26 +499,25 @@ function parseBeforeInsert(ctx) {
|
|
|
498
499
|
}
|
|
499
500
|
|
|
500
501
|
function finalizeJoinModelClass(ctx) {
|
|
501
|
-
if
|
|
502
|
-
|
|
503
|
-
|
|
502
|
+
// We cannot know if the join table has a primary key. Therefore we set some
|
|
503
|
+
// known column as the idColumn of the join model class that we created. A
|
|
504
|
+
// `through.modelClass` is left untouched, even if it has no `idColumn` (#2305).
|
|
505
|
+
if (!ctx.mapping.join.through.modelClass) {
|
|
504
506
|
ctx.joinTableModelClass.idColumn = ctx.joinTableRelatedProp.cols;
|
|
505
507
|
}
|
|
506
508
|
|
|
507
509
|
return ctx;
|
|
508
510
|
}
|
|
509
511
|
|
|
510
|
-
// The modify operations need dialect specific code
|
|
511
|
-
//
|
|
512
|
-
function getOperationClass(builder, { sqlite, mysql,
|
|
512
|
+
// The modify operations need dialect specific code on sqlite and mysql, see
|
|
513
|
+
// the `ManyToMany*ModifyMixin` classes.
|
|
514
|
+
function getOperationClass(builder, { sqlite, mysql, other }) {
|
|
513
515
|
const knex = builder.knex();
|
|
514
516
|
|
|
515
517
|
if (isSqlite(knex)) {
|
|
516
518
|
return sqlite;
|
|
517
519
|
} else if (isMySql(knex)) {
|
|
518
520
|
return mysql;
|
|
519
|
-
} else if (isPostgres(knex)) {
|
|
520
|
-
return postgres;
|
|
521
521
|
} else {
|
|
522
522
|
return other;
|
|
523
523
|
}
|
|
@@ -527,7 +527,6 @@ function getUpdateOperationClass(builder) {
|
|
|
527
527
|
return getOperationClass(builder, {
|
|
528
528
|
sqlite: ManyToManyUpdateSqliteOperation,
|
|
529
529
|
mysql: ManyToManyUpdateMySqlOperation,
|
|
530
|
-
postgres: ManyToManyUpdatePostgresOperation,
|
|
531
530
|
other: ManyToManyUpdateOperation,
|
|
532
531
|
});
|
|
533
532
|
}
|
|
@@ -536,7 +535,6 @@ function getUnrelateOperationClass(builder) {
|
|
|
536
535
|
return getOperationClass(builder, {
|
|
537
536
|
sqlite: ManyToManyUnrelateSqliteOperation,
|
|
538
537
|
mysql: ManyToManyUnrelateMySqlOperation,
|
|
539
|
-
postgres: ManyToManyUnrelatePostgresOperation,
|
|
540
538
|
other: ManyToManyUnrelateOperation,
|
|
541
539
|
});
|
|
542
540
|
}
|
|
@@ -546,7 +544,6 @@ function getDeleteOperationClass(builder) {
|
|
|
546
544
|
return getOperationClass(builder, {
|
|
547
545
|
sqlite: ManyToManyDeleteSqliteOperation,
|
|
548
546
|
mysql: ManyToManyDeleteOperation,
|
|
549
|
-
postgres: ManyToManyDeleteOperation,
|
|
550
547
|
other: ManyToManyDeleteOperation,
|
|
551
548
|
});
|
|
552
549
|
}
|
|
@@ -15,19 +15,6 @@ const ManyToManySqliteModifyMixin = (Operation) => {
|
|
|
15
15
|
|
|
16
16
|
return builder.whereInComposite(rowIdRef, subquery);
|
|
17
17
|
}
|
|
18
|
-
|
|
19
|
-
applyModifyFilterForJoinTable(builder) {
|
|
20
|
-
const joinTableOwnerRefs = this.relation.joinTableOwnerProp.refs(builder);
|
|
21
|
-
const tableRef = builder.tableRefFor(this.relation.getJoinModelClass(builder));
|
|
22
|
-
const rowIdRef = `${tableRef}.${SQLITE_BUILTIN_ROW_ID}`;
|
|
23
|
-
|
|
24
|
-
const ownerValues = this.owner.getProps(this.relation);
|
|
25
|
-
const subquery = this.modifyFilterSubquery.clone().select(rowIdRef);
|
|
26
|
-
|
|
27
|
-
return builder
|
|
28
|
-
.whereInComposite(rowIdRef, subquery)
|
|
29
|
-
.whereInComposite(joinTableOwnerRefs, ownerValues);
|
|
30
|
-
}
|
|
31
18
|
};
|
|
32
19
|
};
|
|
33
20
|
|
|
@@ -36,7 +36,7 @@ class ManyToManyFindOperation extends RelationFindOperation {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
if (this.owner.isModels) {
|
|
39
|
-
this.
|
|
39
|
+
this.warnOnInvalidOwnerProps();
|
|
40
40
|
|
|
41
41
|
if (this.assignResultToOwner) {
|
|
42
42
|
this.selectMissingJoinColumns(builder);
|
|
@@ -122,8 +122,7 @@ class ManyToManyFindOperation extends RelationFindOperation {
|
|
|
122
122
|
const propName = relatedModelClass.columnNameToPropertyName(this.ownerJoinColumnAlias[i]);
|
|
123
123
|
|
|
124
124
|
builder.select(joinTableOwnerRef.as(this.ownerJoinColumnAlias[i]));
|
|
125
|
-
|
|
126
|
-
this.omitProps.push(propName);
|
|
125
|
+
this.omitFromResult(builder, propName);
|
|
127
126
|
});
|
|
128
127
|
|
|
129
128
|
super.selectMissingJoinColumns(builder);
|
|
@@ -5,11 +5,7 @@ const { ManyToManyMySqlModifyMixin } = require('../ManyToManyMySqlModifyMixin');
|
|
|
5
5
|
|
|
6
6
|
class ManyToManyUnrelateMySqlOperation extends ManyToManyMySqlModifyMixin(
|
|
7
7
|
ManyToManyUnrelateOperationBase,
|
|
8
|
-
) {
|
|
9
|
-
get modifyMainQuery() {
|
|
10
|
-
return false;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
8
|
+
) {}
|
|
13
9
|
|
|
14
10
|
module.exports = {
|
|
15
11
|
ManyToManyUnrelateMySqlOperation,
|
|
@@ -3,11 +3,7 @@
|
|
|
3
3
|
const { ManyToManyUnrelateOperationBase } = require('./ManyToManyUnrelateOperationBase');
|
|
4
4
|
const { ManyToManyModifyMixin } = require('../ManyToManyModifyMixin');
|
|
5
5
|
|
|
6
|
-
class ManyToManyUnrelateOperation extends ManyToManyModifyMixin(ManyToManyUnrelateOperationBase) {
|
|
7
|
-
get modifyMainQuery() {
|
|
8
|
-
return false;
|
|
9
|
-
}
|
|
10
|
-
}
|
|
6
|
+
class ManyToManyUnrelateOperation extends ManyToManyModifyMixin(ManyToManyUnrelateOperationBase) {}
|
|
11
7
|
|
|
12
8
|
module.exports = {
|
|
13
9
|
ManyToManyUnrelateOperation,
|
|
@@ -4,6 +4,15 @@ const { UnrelateOperation } = require('../../../queryBuilder/operations/Unrelate
|
|
|
4
4
|
const { addWhereGroup } = require('../../../queryBuilder/whereGroups');
|
|
5
5
|
|
|
6
6
|
class ManyToManyUnrelateOperationBase extends UnrelateOperation {
|
|
7
|
+
get modifiesJoinTable() {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// The main query is replaced by the query that `queryExecutor()` returns.
|
|
12
|
+
get modifiesJoinTableOnly() {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
|
|
7
16
|
queryExecutor(builder) {
|
|
8
17
|
const unrelateQuery = this.relation
|
|
9
18
|
.getJoinModelClass(builder.knex())
|
|
@@ -5,11 +5,7 @@ const { ManyToManySqliteModifyMixin } = require('../ManyToManySqliteModifyMixin'
|
|
|
5
5
|
|
|
6
6
|
class ManyToManyUnrelateSqliteOperation extends ManyToManySqliteModifyMixin(
|
|
7
7
|
ManyToManyUnrelateOperationBase,
|
|
8
|
-
) {
|
|
9
|
-
get modifyMainQuery() {
|
|
10
|
-
return false;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
8
|
+
) {}
|
|
13
9
|
|
|
14
10
|
module.exports = {
|
|
15
11
|
ManyToManyUnrelateSqliteOperation,
|