objection 3.6.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/lib/model/AjvValidator.js +33 -10
- package/lib/model/modelClone.js +0 -2
- package/lib/queryBuilder/QueryBuilder.js +56 -10
- package/lib/queryBuilder/RelationExpression.js +6 -4
- package/lib/queryBuilder/graph/GraphOptions.js +27 -55
- package/lib/queryBuilder/graph/GraphUpsert.js +22 -0
- package/lib/queryBuilder/graph/insert/GraphInsert.js +2 -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/relationExpressionParser.js +1 -1
- package/lib/queryBuilder/parsers/relationExpressionParser.pegjs +1 -1
- package/lib/queryBuilder/transformations/WrapMysqlModifySubqueryTransformation.js +4 -7
- package/lib/relations/Relation.js +1 -0
- package/lib/relations/RelationFindOperation.js +21 -39
- package/lib/relations/belongsToOne/BelongsToOneDeleteOperation.js +4 -2
- package/lib/relations/manyToMany/ManyToManyModifyMixin.js +39 -15
- package/lib/relations/manyToMany/ManyToManyMySqlModifyMixin.js +31 -42
- package/lib/relations/manyToMany/ManyToManyRelation.js +8 -16
- 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 +7 -1
- package/lib/utils/identifierMapping.js +8 -17
- package/lib/utils/objectUtils.js +38 -6
- package/lib/utils/parseFieldExpression.js +10 -3
- package/package.json +1 -1
- package/typings/objection/index.d.ts +46 -26
- 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
|
@@ -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,6 +76,14 @@ 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
89
|
// is reported, as `null` is a legit value for a missing relation, and
|
|
@@ -111,7 +112,11 @@ class RelationFindOperation extends FindOperation {
|
|
|
111
112
|
// assigned to their owners, so all owners silently get no related models
|
|
112
113
|
// (#2667). `$relatedQuery()` without assigning the results isn't affected.
|
|
113
114
|
warnOnArrayOwnerProps() {
|
|
114
|
-
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.';
|
|
115
120
|
|
|
116
121
|
for (let c = 0, lc = ownerProp.size; c < lc; ++c) {
|
|
117
122
|
if (this.owner.modelArray.some((owner) => Array.isArray(ownerProp.getProp(owner, c)))) {
|
|
@@ -119,7 +124,7 @@ class RelationFindOperation extends FindOperation {
|
|
|
119
124
|
`Fetching relation "${this.relation.name}" of ${this.relation.ownerModelClass.name}: ` +
|
|
120
125
|
`the join property "${ownerProp.propDescription(c)}" of some owner models holds ` +
|
|
121
126
|
'an array, which is not supported, so no related models are found for them. ' +
|
|
122
|
-
|
|
127
|
+
advice,
|
|
123
128
|
);
|
|
124
129
|
}
|
|
125
130
|
}
|
|
@@ -154,7 +159,7 @@ class RelationFindOperation extends FindOperation {
|
|
|
154
159
|
const col = relatedProp.cols[c];
|
|
155
160
|
|
|
156
161
|
if (!builder.hasSelectionAs(fullCol, col) && addedSelects.indexOf(fullCol) === -1) {
|
|
157
|
-
this.
|
|
162
|
+
this.omitFromResult(builder, prop);
|
|
158
163
|
addedSelects.push(fullCol);
|
|
159
164
|
}
|
|
160
165
|
}
|
|
@@ -170,34 +175,12 @@ class RelationFindOperation extends FindOperation {
|
|
|
170
175
|
}
|
|
171
176
|
}
|
|
172
177
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (!
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (!Array.isArray(related)) {
|
|
181
|
-
return this.omitImplicitJoinPropsFromOne(relatedModelClass, related);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (!related.length) {
|
|
185
|
-
return related;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
for (let i = 0, l = related.length; i < l; ++i) {
|
|
189
|
-
this.omitImplicitJoinPropsFromOne(relatedModelClass, related[i]);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return related;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
omitImplicitJoinPropsFromOne(relatedModelClass, model) {
|
|
196
|
-
for (let c = 0, lc = this.omitProps.length; c < lc; ++c) {
|
|
197
|
-
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);
|
|
198
183
|
}
|
|
199
|
-
|
|
200
|
-
return model;
|
|
201
184
|
}
|
|
202
185
|
|
|
203
186
|
clone() {
|
|
@@ -206,7 +189,6 @@ class RelationFindOperation extends FindOperation {
|
|
|
206
189
|
clone.alwaysReturnArray = this.alwaysReturnArray;
|
|
207
190
|
clone.assignResultToOwner = this.assignResultToOwner;
|
|
208
191
|
clone.relationProperty = this.relationProperty;
|
|
209
|
-
clone.omitProps = this.omitProps.slice();
|
|
210
192
|
clone.alias = this.alias;
|
|
211
193
|
|
|
212
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) {
|
|
@@ -16,21 +16,27 @@ const ManyToManyModifyMixin = (Operation) => {
|
|
|
16
16
|
constructor(...args) {
|
|
17
17
|
super(...args);
|
|
18
18
|
this.modifyFilterSubquery = null;
|
|
19
|
+
this.joinRowFilterSubquery = null;
|
|
19
20
|
this.skipRelationFilter = false;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
//
|
|
23
|
-
// `upsertGraph()`, keep their own
|
|
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).
|
|
24
26
|
get modifyMainQuery() {
|
|
25
|
-
return !this.skipRelationFilter;
|
|
27
|
+
return !this.modifiesJoinTableOnly && !this.skipRelationFilter;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
// At this point `builder` should only have the user's own wheres and joins. There can
|
|
29
31
|
// be other operations (like orderBy) too, but those are meaningless with modify operations.
|
|
30
32
|
onBuild(builder) {
|
|
31
|
-
this.
|
|
33
|
+
if (this.modifiesJoinTable && !this.skipRelationFilter) {
|
|
34
|
+
this.joinRowFilterSubquery = this.createJoinRowFilterSubquery(builder);
|
|
35
|
+
}
|
|
32
36
|
|
|
33
37
|
if (this.modifyMainQuery) {
|
|
38
|
+
this.modifyFilterSubquery = this.createModifyFilterSubquery(builder);
|
|
39
|
+
|
|
34
40
|
// We can now remove the where and join statements from the main query.
|
|
35
41
|
this.removeFiltersFromMainQuery(builder);
|
|
36
42
|
|
|
@@ -74,22 +80,39 @@ const ManyToManyModifyMixin = (Operation) => {
|
|
|
74
80
|
return builder.whereInComposite(idRefs, subquery);
|
|
75
81
|
}
|
|
76
82
|
|
|
77
|
-
//
|
|
78
|
-
// reference join table columns, this also modifies join rows that don't match them
|
|
79
|
-
// but share a related row with one that does (#1853). The dialect specific mixins
|
|
80
|
-
// override this to only modify the join rows that match the filters themselves.
|
|
83
|
+
// Only modifies the join rows that match the filters (#1853).
|
|
81
84
|
applyModifyFilterForJoinTable(builder) {
|
|
82
85
|
const joinTableOwnerRefs = this.relation.joinTableOwnerProp.refs(builder);
|
|
83
|
-
const joinTableRelatedRefs = this.relation.joinTableRelatedProp.refs(builder);
|
|
84
|
-
|
|
85
|
-
const relatedRefs = this.relation.relatedProp.refs(builder);
|
|
86
86
|
const ownerValues = this.owner.getProps(this.relation);
|
|
87
87
|
|
|
88
|
-
const subquery = this.modifyFilterSubquery.clone().select(relatedRefs);
|
|
89
|
-
|
|
90
88
|
return builder
|
|
91
|
-
.whereInComposite(
|
|
92
|
-
.
|
|
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);
|
|
93
116
|
}
|
|
94
117
|
|
|
95
118
|
toFindOperation() {
|
|
@@ -111,6 +134,7 @@ const ManyToManyModifyMixin = (Operation) => {
|
|
|
111
134
|
clone() {
|
|
112
135
|
const clone = super.clone();
|
|
113
136
|
clone.modifyFilterSubquery = this.modifyFilterSubquery;
|
|
137
|
+
clone.joinRowFilterSubquery = this.joinRowFilterSubquery;
|
|
114
138
|
clone.skipRelationFilter = this.skipRelationFilter;
|
|
115
139
|
return clone;
|
|
116
140
|
}
|
|
@@ -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');
|
|
@@ -503,26 +499,25 @@ function parseBeforeInsert(ctx) {
|
|
|
503
499
|
}
|
|
504
500
|
|
|
505
501
|
function finalizeJoinModelClass(ctx) {
|
|
506
|
-
if
|
|
507
|
-
|
|
508
|
-
|
|
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) {
|
|
509
506
|
ctx.joinTableModelClass.idColumn = ctx.joinTableRelatedProp.cols;
|
|
510
507
|
}
|
|
511
508
|
|
|
512
509
|
return ctx;
|
|
513
510
|
}
|
|
514
511
|
|
|
515
|
-
// The modify operations need dialect specific code
|
|
516
|
-
//
|
|
517
|
-
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 }) {
|
|
518
515
|
const knex = builder.knex();
|
|
519
516
|
|
|
520
517
|
if (isSqlite(knex)) {
|
|
521
518
|
return sqlite;
|
|
522
519
|
} else if (isMySql(knex)) {
|
|
523
520
|
return mysql;
|
|
524
|
-
} else if (isPostgres(knex)) {
|
|
525
|
-
return postgres;
|
|
526
521
|
} else {
|
|
527
522
|
return other;
|
|
528
523
|
}
|
|
@@ -532,7 +527,6 @@ function getUpdateOperationClass(builder) {
|
|
|
532
527
|
return getOperationClass(builder, {
|
|
533
528
|
sqlite: ManyToManyUpdateSqliteOperation,
|
|
534
529
|
mysql: ManyToManyUpdateMySqlOperation,
|
|
535
|
-
postgres: ManyToManyUpdatePostgresOperation,
|
|
536
530
|
other: ManyToManyUpdateOperation,
|
|
537
531
|
});
|
|
538
532
|
}
|
|
@@ -541,7 +535,6 @@ function getUnrelateOperationClass(builder) {
|
|
|
541
535
|
return getOperationClass(builder, {
|
|
542
536
|
sqlite: ManyToManyUnrelateSqliteOperation,
|
|
543
537
|
mysql: ManyToManyUnrelateMySqlOperation,
|
|
544
|
-
postgres: ManyToManyUnrelatePostgresOperation,
|
|
545
538
|
other: ManyToManyUnrelateOperation,
|
|
546
539
|
});
|
|
547
540
|
}
|
|
@@ -551,7 +544,6 @@ function getDeleteOperationClass(builder) {
|
|
|
551
544
|
return getOperationClass(builder, {
|
|
552
545
|
sqlite: ManyToManyDeleteSqliteOperation,
|
|
553
546
|
mysql: ManyToManyDeleteOperation,
|
|
554
|
-
postgres: ManyToManyDeleteOperation,
|
|
555
547
|
other: ManyToManyDeleteOperation,
|
|
556
548
|
});
|
|
557
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,
|
|
@@ -15,6 +15,10 @@ class ManyToManyUpdateOperationBase extends UpdateOperation {
|
|
|
15
15
|
this.joinTablePatchFilterQuery = null;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
get modifiesJoinTable() {
|
|
19
|
+
return this.hasExtraProps;
|
|
20
|
+
}
|
|
21
|
+
|
|
18
22
|
onAdd(builder, args) {
|
|
19
23
|
const obj = args[0];
|
|
20
24
|
|
|
@@ -47,7 +51,9 @@ class ManyToManyUpdateOperationBase extends UpdateOperation {
|
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
async onAfter1(builder, result) {
|
|
50
|
-
|
|
54
|
+
// A query resolved without updating anything, e.g. with `none()`, leaves
|
|
55
|
+
// the join rows untouched too.
|
|
56
|
+
if (this.hasExtraProps && !this.isResolvedWithoutUpdate(builder)) {
|
|
51
57
|
const joinTableUpdateQuery = this.relation
|
|
52
58
|
.getJoinModelClass(builder.knex())
|
|
53
59
|
.query()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { isObject
|
|
3
|
+
const { isObject } = require('./objectUtils');
|
|
4
4
|
|
|
5
5
|
// Super fast memoize for single argument functions.
|
|
6
6
|
function memoize(func) {
|
|
@@ -149,9 +149,8 @@ function mapLastPart(mapper, separator) {
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
// Returns a function that takes an object as an input and maps the object's keys
|
|
152
|
-
// using `mapper
|
|
153
|
-
|
|
154
|
-
function keyMapper(mapper, valueMapper) {
|
|
152
|
+
// using `mapper`. If the input is not an object, the input is returned unchanged.
|
|
153
|
+
function keyMapper(mapper) {
|
|
155
154
|
return (obj) => {
|
|
156
155
|
if (!isObject(obj) || Array.isArray(obj)) {
|
|
157
156
|
return obj;
|
|
@@ -162,7 +161,7 @@ function keyMapper(mapper, valueMapper) {
|
|
|
162
161
|
|
|
163
162
|
for (let i = 0, l = keys.length; i < l; ++i) {
|
|
164
163
|
const key = keys[i];
|
|
165
|
-
out[mapper(key)] =
|
|
164
|
+
out[mapper(key)] = obj[key];
|
|
166
165
|
}
|
|
167
166
|
|
|
168
167
|
return out;
|
|
@@ -190,16 +189,10 @@ function snakeCaseMappers(opt = {}) {
|
|
|
190
189
|
};
|
|
191
190
|
}
|
|
192
191
|
|
|
193
|
-
function knexIdentifierMappers({ parse, format, idSeparator = ':'
|
|
192
|
+
function knexIdentifierMappers({ parse, format, idSeparator = ':' } = {}) {
|
|
194
193
|
const formatId = memoize(mapLastPart(format, idSeparator));
|
|
195
194
|
const parseId = memoize(mapLastPart(parse, idSeparator));
|
|
196
195
|
const parseKeys = keyMapper(parseId);
|
|
197
|
-
// With `mapNestedKeys`, also map the keys of plain objects one level down, as
|
|
198
|
-
// in the rows knex returns with its `nestTables: true` option on MySQL:
|
|
199
|
-
// `{ tableName: { columnName: value } }`.
|
|
200
|
-
const parseRow = mapNestedKeys
|
|
201
|
-
? keyMapper(parseId, (value) => (isPlainObject(value) ? parseKeys(value) : value))
|
|
202
|
-
: parseKeys;
|
|
203
196
|
|
|
204
197
|
return {
|
|
205
198
|
wrapIdentifier(identifier, origWrap) {
|
|
@@ -211,12 +204,12 @@ function knexIdentifierMappers({ parse, format, idSeparator = ':', mapNestedKeys
|
|
|
211
204
|
const output = new Array(result.length);
|
|
212
205
|
|
|
213
206
|
for (let i = 0, l = result.length; i < l; ++i) {
|
|
214
|
-
output[i] =
|
|
207
|
+
output[i] = parseKeys(result[i]);
|
|
215
208
|
}
|
|
216
209
|
|
|
217
210
|
return output;
|
|
218
211
|
} else {
|
|
219
|
-
return
|
|
212
|
+
return parseKeys(result);
|
|
220
213
|
}
|
|
221
214
|
},
|
|
222
215
|
};
|
|
@@ -226,11 +219,10 @@ function knexSnakeCaseMappers(opt = {}) {
|
|
|
226
219
|
return knexIdentifierMappers({
|
|
227
220
|
parse: (str) => camelCase(str, opt),
|
|
228
221
|
format: (str) => snakeCase(str, opt),
|
|
229
|
-
mapNestedKeys: opt.mapNestedKeys,
|
|
230
222
|
});
|
|
231
223
|
}
|
|
232
224
|
|
|
233
|
-
function knexIdentifierMapping(colToProp
|
|
225
|
+
function knexIdentifierMapping(colToProp) {
|
|
234
226
|
const propToCol = Object.keys(colToProp).reduce((propToCol, column) => {
|
|
235
227
|
propToCol[colToProp[column]] = column;
|
|
236
228
|
return propToCol;
|
|
@@ -239,7 +231,6 @@ function knexIdentifierMapping(colToProp, { mapNestedKeys } = {}) {
|
|
|
239
231
|
return knexIdentifierMappers({
|
|
240
232
|
parse: (column) => colToProp[column] || column,
|
|
241
233
|
format: (prop) => propToCol[prop] || prop,
|
|
242
|
-
mapNestedKeys,
|
|
243
234
|
});
|
|
244
235
|
}
|
|
245
236
|
|