objection 3.1.5 → 3.3.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 +16 -16
- package/lib/model/AjvValidator.js +173 -18
- package/lib/model/Model.js +3 -1
- package/lib/model/graph/ModelGraphNode.js +12 -0
- package/lib/model/modelToJson.js +2 -1
- package/lib/queryBuilder/QueryBuilder.js +339 -47
- package/lib/queryBuilder/QueryBuilderOperationSupport.js +5 -0
- package/lib/queryBuilder/ReferenceBuilder.js +5 -3
- package/lib/queryBuilder/RelationExpression.js +21 -9
- package/lib/queryBuilder/StaticHookArguments.js +12 -1
- package/lib/queryBuilder/graph/GraphFetcher.js +3 -1
- package/lib/queryBuilder/graph/GraphOptions.js +21 -2
- package/lib/queryBuilder/graph/GraphUpsert.js +52 -0
- package/lib/queryBuilder/graph/delete/GraphDeleteAction.js +20 -14
- package/lib/queryBuilder/graph/insert/DeferredRelationGraphInsertAction.js +57 -0
- package/lib/queryBuilder/graph/insert/GraphInsert.js +113 -8
- package/lib/queryBuilder/graph/insert/GraphInsertAction.js +10 -1
- package/lib/queryBuilder/graph/patch/GraphPatchAction.js +15 -3
- package/lib/queryBuilder/join/JoinResultParser.js +22 -0
- package/lib/queryBuilder/join/RelationJoiner.js +71 -5
- package/lib/queryBuilder/operations/DelegateOperation.js +4 -0
- package/lib/queryBuilder/operations/InsertAndFetchOperation.js +7 -1
- package/lib/queryBuilder/operations/InsertGraphOperation.js +2 -2
- package/lib/queryBuilder/operations/InsertOperation.js +121 -12
- package/lib/queryBuilder/operations/NoneOperation.js +37 -0
- package/lib/queryBuilder/operations/ObjectionToKnexConvertingOperation.js +5 -0
- package/lib/queryBuilder/operations/QueryBuilderOperation.js +4 -0
- package/lib/queryBuilder/operations/ReturningOperation.js +28 -3
- package/lib/queryBuilder/operations/UpdateAndFetchOperation.js +20 -0
- package/lib/queryBuilder/operations/UpdateOperation.js +3 -2
- package/lib/queryBuilder/operations/UpsertGraphOperation.js +5 -0
- package/lib/queryBuilder/operations/WhereGroupOperation.js +41 -0
- package/lib/queryBuilder/operations/eager/EagerOperation.js +7 -5
- package/lib/queryBuilder/operations/eager/JoinEagerOperation.js +4 -2
- package/lib/queryBuilder/operations/eager/WhereInEagerOperation.js +9 -20
- package/lib/queryBuilder/operations/jsonApi/WhereJsonHasPostgresOperation.js +3 -3
- package/lib/queryBuilder/operations/jsonApi/WhereJsonNotObjectPostgresOperation.js +1 -1
- package/lib/queryBuilder/operations/jsonApi/postgresJsonApi.js +45 -32
- package/lib/queryBuilder/operations/select/Selection.js +95 -4
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeMsSqlOperation.js +6 -0
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeOperation.js +5 -1
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeSqliteOperation.js +9 -0
- package/lib/queryBuilder/parsers/jsonFieldExpressionParser.js +81 -43
- package/lib/queryBuilder/parsers/jsonFieldExpressionParser.pegjs +5 -4
- package/lib/queryBuilder/transformations/WrapMysqlModifySubqueryTransformation.js +9 -0
- package/lib/queryBuilder/whereGroups.js +125 -0
- package/lib/relations/Relation.js +5 -2
- package/lib/relations/RelationProperty.js +7 -3
- package/lib/relations/belongsToOne/BelongsToOneInsertOperation.js +1 -0
- package/lib/relations/belongsToOne/BelongsToOneRelateOperation.js +5 -0
- package/lib/relations/hasMany/HasManyRelateOperation.js +6 -2
- package/lib/relations/hasMany/HasManyUnrelateOperation.js +5 -2
- package/lib/relations/manyToMany/ManyToManyModifyMixin.js +26 -3
- package/lib/relations/manyToMany/ManyToManyMySqlModifyMixin.js +66 -0
- package/lib/relations/manyToMany/ManyToManyPostgresModifyMixin.js +27 -0
- package/lib/relations/manyToMany/ManyToManyRelation.js +70 -48
- package/lib/relations/manyToMany/relate/ManyToManyRelateOperation.js +6 -0
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateMySqlOperation.js +16 -0
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperationBase.js +5 -1
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelatePostgresOperation.js +16 -0
- package/lib/relations/manyToMany/update/ManyToManyUpdateMySqlOperation.js +12 -0
- package/lib/relations/manyToMany/update/ManyToManyUpdateOperationBase.js +8 -2
- package/lib/relations/manyToMany/update/ManyToManyUpdatePostgresOperation.js +12 -0
- package/lib/utils/deprecate.js +6 -0
- package/lib/utils/identifierMapping.js +23 -3
- package/lib/utils/objectUtils.js +9 -5
- package/lib/utils/parseFieldExpression.js +28 -0
- package/package.json +22 -14
- package/typings/objection/index.d.ts +339 -68
|
@@ -2,6 +2,8 @@
|
|
|
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);
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Mysql doesn't allow queries like this:
|
|
@@ -30,6 +32,13 @@ class WrapMysqlModifySubqueryTransformation extends QueryTransformation {
|
|
|
30
32
|
return query;
|
|
31
33
|
}
|
|
32
34
|
|
|
35
|
+
// This transformation should not apply to join builder, otherwise it causes:
|
|
36
|
+
// "TypeError: parentQuery.isUpdate is not a function"
|
|
37
|
+
const JoinBuilder = getJoinBuilder();
|
|
38
|
+
if (parentQuery instanceof JoinBuilder) {
|
|
39
|
+
return query;
|
|
40
|
+
}
|
|
41
|
+
|
|
33
42
|
// This transformation only applies to update and delete queries.
|
|
34
43
|
if (!parentQuery.isUpdate() && !parentQuery.isDelete()) {
|
|
35
44
|
return query;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { WhereGroupOperation } = require('./operations/WhereGroupOperation');
|
|
4
|
+
const { isObject, isFunction } = require('../utils/objectUtils');
|
|
5
|
+
const { isKnexRaw } = require('../utils/knexUtils');
|
|
6
|
+
|
|
7
|
+
// Where operations of a query can come from different sources: the user, a
|
|
8
|
+
// relation's owner condition, the relation's `modify`/`filter` and so on. Each
|
|
9
|
+
// source forms its own "where group". All operations the user adds belong to
|
|
10
|
+
// the default group, and `addWhereGroup` assigns the operations added inside
|
|
11
|
+
// its callback to a new, separate group.
|
|
12
|
+
//
|
|
13
|
+
// Knex simply joins all where clauses with their `and` or `or` boolean. Since
|
|
14
|
+
// `and` binds tighter than `or`, a user's `orWhere` would escape the relation's
|
|
15
|
+
// owner condition: `where "ownerId" in (1) and a or b`. This would find, update
|
|
16
|
+
// and delete rows of other owners too. See issues #2191 and #1909.
|
|
17
|
+
//
|
|
18
|
+
// To prevent this, `wrapWhereGroups` is called after all `onBuild` hooks have
|
|
19
|
+
// run. If a query contains more than one where group, every group that may
|
|
20
|
+
// contain an `or` at its top level is wrapped in parentheses, so that the
|
|
21
|
+
// groups are always ANDed together: `where "ownerId" in (1) and (a or b)`.
|
|
22
|
+
// Groups without an `or` are left alone so that their SQL doesn't change.
|
|
23
|
+
function addWhereGroup(builder, callback) {
|
|
24
|
+
const parentWhereGroup = builder._whereGroup;
|
|
25
|
+
builder._whereGroup = {};
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
return callback(builder);
|
|
29
|
+
} finally {
|
|
30
|
+
builder._whereGroup = parentWhereGroup;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function wrapWhereGroups(builder) {
|
|
35
|
+
const WhereSelector = builder.constructor.WhereSelector;
|
|
36
|
+
const whereOps = new Set();
|
|
37
|
+
const groups = new Map();
|
|
38
|
+
|
|
39
|
+
builder.forEachOperation(WhereSelector, (op) => {
|
|
40
|
+
// Only consider the top-most where operations. The descendants of a
|
|
41
|
+
// where operation are built as part of it.
|
|
42
|
+
if (op.isAncestorInSet(whereOps)) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
whereOps.add(op);
|
|
47
|
+
|
|
48
|
+
const group = getWhereGroup(op);
|
|
49
|
+
let ops = groups.get(group);
|
|
50
|
+
|
|
51
|
+
if (!ops) {
|
|
52
|
+
ops = [];
|
|
53
|
+
groups.set(group, ops);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
ops.push(op);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (groups.size < 2) {
|
|
60
|
+
return builder;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for (const [group, ops] of groups) {
|
|
64
|
+
if (ops.some(mayContainTopLevelOr)) {
|
|
65
|
+
wrapOperations(builder, group, ops);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return builder;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getWhereGroup(op) {
|
|
73
|
+
for (let it = op; it; it = it.parentOperation) {
|
|
74
|
+
if (it.whereGroup) {
|
|
75
|
+
return it.whereGroup;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function mayContainTopLevelOr(op) {
|
|
83
|
+
if (op instanceof WhereGroupOperation) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// orWhere, orWhereIn, orWhereRaw, orWhereExists, orWhereJsonSupersetOf etc.
|
|
88
|
+
if (/^or[A-Z]/.test(op.name)) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Raw SQL isn't parenthesized by knex and can contain `or`.
|
|
93
|
+
if (/Raw$/.test(op.name)) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return Array.isArray(op.args) && op.args.length === 1 && isRaw(op.args[0]);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function isRaw(arg) {
|
|
101
|
+
return isKnexRaw(arg) || (isObject(arg) && isFunction(arg.toKnexRaw));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function wrapOperations(builder, group, ops) {
|
|
105
|
+
const groupOp = new WhereGroupOperation('where');
|
|
106
|
+
groupOp.whereGroup = group;
|
|
107
|
+
|
|
108
|
+
builder.replaceOperation(ops[0], groupOp);
|
|
109
|
+
|
|
110
|
+
for (let i = 1; i < ops.length; ++i) {
|
|
111
|
+
builder.removeOperation(ops[i]);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
for (const op of ops) {
|
|
115
|
+
op.parentOperation = null;
|
|
116
|
+
op.adderHookName = null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
groupOp.operations = ops;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = {
|
|
123
|
+
addWhereGroup,
|
|
124
|
+
wrapWhereGroups,
|
|
125
|
+
};
|
|
@@ -10,6 +10,7 @@ const { resolveModel } = require('../utils/resolveModel');
|
|
|
10
10
|
const { get, isFunction } = require('../utils/objectUtils');
|
|
11
11
|
const { mapAfterAllReturn } = require('../utils/promiseUtils');
|
|
12
12
|
const { createModifier } = require('../utils/createModifier');
|
|
13
|
+
const { addWhereGroup } = require('../queryBuilder/whereGroups');
|
|
13
14
|
|
|
14
15
|
class Relation {
|
|
15
16
|
constructor(relationName, OwnerClass) {
|
|
@@ -115,14 +116,16 @@ class Relation {
|
|
|
115
116
|
|
|
116
117
|
findQuery(builder, owner) {
|
|
117
118
|
const relatedRefs = this.relatedProp.refs(builder);
|
|
118
|
-
owner.buildFindQuery(builder, this, relatedRefs);
|
|
119
|
+
addWhereGroup(builder, () => owner.buildFindQuery(builder, this, relatedRefs));
|
|
119
120
|
|
|
120
121
|
return this.applyModify(builder);
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
applyModify(builder) {
|
|
124
125
|
try {
|
|
125
|
-
|
|
126
|
+
// Keep the relation's `modify` in a separate where group so that an
|
|
127
|
+
// `orWhere` in it can't escape the owner condition. See `whereGroups.js`.
|
|
128
|
+
return addWhereGroup(builder, () => builder.modify(this.modify));
|
|
126
129
|
} catch (err) {
|
|
127
130
|
if (err instanceof this.relatedModelClass.ModifierNotFoundError) {
|
|
128
131
|
throw this.createError(err.message);
|
|
@@ -184,14 +184,18 @@ function createRefs(refs) {
|
|
|
184
184
|
|
|
185
185
|
function createPaths(refs, modelClassResolver) {
|
|
186
186
|
return refs.map((ref) => {
|
|
187
|
-
|
|
187
|
+
// References created with `SomeModel.ref(column)` have no explicit table
|
|
188
|
+
// name, so fall back to the table name of their model class.
|
|
189
|
+
const tableName = ref.tableName || (ref.modelClass && ref.modelClass.getTableName());
|
|
190
|
+
|
|
191
|
+
if (!tableName) {
|
|
188
192
|
throw new InvalidReferenceError();
|
|
189
193
|
}
|
|
190
194
|
|
|
191
|
-
const modelClass = modelClassResolver(
|
|
195
|
+
const modelClass = modelClassResolver(tableName);
|
|
192
196
|
|
|
193
197
|
if (!modelClass) {
|
|
194
|
-
throw new ModelNotFoundError(
|
|
198
|
+
throw new ModelNotFoundError(tableName);
|
|
195
199
|
}
|
|
196
200
|
|
|
197
201
|
const prop = modelClass.columnNameToPropertyName(ref.column);
|
|
@@ -35,6 +35,11 @@ class BelongsToOneRelateOperation extends RelateOperation {
|
|
|
35
35
|
.query()
|
|
36
36
|
.childQueryOf(builder)
|
|
37
37
|
.patch(patch)
|
|
38
|
+
.modify((query) => {
|
|
39
|
+
if (!this.opt.dontCopyReturning) {
|
|
40
|
+
query.copyFrom(builder, /returning/);
|
|
41
|
+
}
|
|
42
|
+
})
|
|
38
43
|
.whereInComposite(ownerIdProp.refs(builder), ownerIds);
|
|
39
44
|
}
|
|
40
45
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { normalizeIds } = require('../../utils/normalizeIds');
|
|
4
4
|
const { RelateOperation } = require('../../queryBuilder/operations/RelateOperation');
|
|
5
|
+
const { addWhereGroup } = require('../../queryBuilder/whereGroups');
|
|
5
6
|
|
|
6
7
|
class HasManyRelateOperation extends RelateOperation {
|
|
7
8
|
onAdd(_, args) {
|
|
@@ -28,10 +29,13 @@ class HasManyRelateOperation extends RelateOperation {
|
|
|
28
29
|
.query()
|
|
29
30
|
.childQueryOf(builder)
|
|
30
31
|
.patch(patch)
|
|
32
|
+
.copyFrom(builder, /returning/)
|
|
31
33
|
.copyFrom(builder, builder.constructor.JoinSelector)
|
|
32
34
|
.copyFrom(builder, builder.constructor.WhereSelector)
|
|
33
|
-
.
|
|
34
|
-
|
|
35
|
+
.modify((query) => {
|
|
36
|
+
addWhereGroup(query, () => query.findByIds(this.ids));
|
|
37
|
+
addWhereGroup(query, () => query.modify(this.relation.modify));
|
|
38
|
+
});
|
|
35
39
|
}
|
|
36
40
|
}
|
|
37
41
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { UnrelateOperation } = require('../../queryBuilder/operations/UnrelateOperation');
|
|
4
|
+
const { addWhereGroup } = require('../../queryBuilder/whereGroups');
|
|
4
5
|
|
|
5
6
|
class HasManyUnrelateOperation extends UnrelateOperation {
|
|
6
7
|
queryExecutor(builder) {
|
|
@@ -19,8 +20,10 @@ class HasManyUnrelateOperation extends UnrelateOperation {
|
|
|
19
20
|
.patch(patch)
|
|
20
21
|
.copyFrom(builder, builder.constructor.JoinSelector)
|
|
21
22
|
.copyFrom(builder, builder.constructor.WhereSelector)
|
|
22
|
-
.
|
|
23
|
-
|
|
23
|
+
.modify((query) => {
|
|
24
|
+
addWhereGroup(query, () => query.whereInComposite(relatedRefs, ownerValues));
|
|
25
|
+
addWhereGroup(query, () => query.modify(this.relation.modify));
|
|
26
|
+
});
|
|
24
27
|
}
|
|
25
28
|
}
|
|
26
29
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { ManyToManyFindOperation } = require('./find/ManyToManyFindOperation');
|
|
4
|
+
const { addWhereGroup } = require('../../queryBuilder/whereGroups');
|
|
4
5
|
|
|
5
6
|
// This mixin contains the shared code for all modify operations (update, delete, relate, unrelate)
|
|
6
7
|
// for ManyToManyRelation operations.
|
|
@@ -31,7 +32,7 @@ const ManyToManyModifyMixin = (Operation) => {
|
|
|
31
32
|
this.removeFiltersFromMainQuery(builder);
|
|
32
33
|
|
|
33
34
|
// Add a single where clause that uses the created subquery.
|
|
34
|
-
this.applyModifyFilterForRelatedTable(builder);
|
|
35
|
+
addWhereGroup(builder, () => this.applyModifyFilterForRelatedTable(builder));
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
return super.onBuild(builder);
|
|
@@ -70,6 +71,10 @@ const ManyToManyModifyMixin = (Operation) => {
|
|
|
70
71
|
return builder.whereInComposite(idRefs, subquery);
|
|
71
72
|
}
|
|
72
73
|
|
|
74
|
+
// Modifies the join rows of all related rows that match the filters. If the filters
|
|
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.
|
|
73
78
|
applyModifyFilterForJoinTable(builder) {
|
|
74
79
|
const joinTableOwnerRefs = this.relation.joinTableOwnerProp.refs(builder);
|
|
75
80
|
const joinTableRelatedRefs = this.relation.joinTableRelatedProp.refs(builder);
|
|
@@ -85,10 +90,19 @@ const ManyToManyModifyMixin = (Operation) => {
|
|
|
85
90
|
}
|
|
86
91
|
|
|
87
92
|
toFindOperation() {
|
|
88
|
-
|
|
93
|
+
const opt = {
|
|
89
94
|
relation: this.relation,
|
|
90
95
|
owner: this.owner,
|
|
91
|
-
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
if (this.modifyMainQuery && this.modifyFilterSubquery) {
|
|
99
|
+
// `onBuild` has already moved the main query's filters into the
|
|
100
|
+
// subquery and removed them. Apply them to the find query too.
|
|
101
|
+
// See issue #2266.
|
|
102
|
+
return new ManyToManyModifyFindOperation('find', { ...opt, modifyOperation: this });
|
|
103
|
+
} else {
|
|
104
|
+
return new ManyToManyFindOperation('find', opt);
|
|
105
|
+
}
|
|
92
106
|
}
|
|
93
107
|
|
|
94
108
|
clone() {
|
|
@@ -99,6 +113,15 @@ const ManyToManyModifyMixin = (Operation) => {
|
|
|
99
113
|
};
|
|
100
114
|
};
|
|
101
115
|
|
|
116
|
+
class ManyToManyModifyFindOperation extends ManyToManyFindOperation {
|
|
117
|
+
onBuild(builder) {
|
|
118
|
+
super.onBuild(builder);
|
|
119
|
+
addWhereGroup(builder, () =>
|
|
120
|
+
this.opt.modifyOperation.applyModifyFilterForRelatedTable(builder),
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
102
125
|
module.exports = {
|
|
103
126
|
ManyToManyModifyMixin,
|
|
104
127
|
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { ManyToManyModifyMixin } = require('./ManyToManyModifyMixin');
|
|
4
|
+
const { isObject } = require('../../utils/objectUtils');
|
|
5
|
+
|
|
6
|
+
// We need to override this mixin for mysql because mysql doesn't allow a
|
|
7
|
+
// statement that modifies the join table to read the related table in a
|
|
8
|
+
// subquery, if a trigger on the join table modifies the related table:
|
|
9
|
+
//
|
|
10
|
+
// ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG
|
|
11
|
+
//
|
|
12
|
+
// So the join rows that match the filters are fetched in a separate query
|
|
13
|
+
// before the main query, which then only modifies these rows (#1853). Mysql
|
|
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.
|
|
16
|
+
//
|
|
17
|
+
// https://stackoverflow.com/a/2314264/3729316
|
|
18
|
+
const ManyToManyMySqlModifyMixin = (Operation) => {
|
|
19
|
+
return class extends ManyToManyModifyMixin(Operation) {
|
|
20
|
+
applyModifyFilterForJoinTable(builder) {
|
|
21
|
+
const joinTableOwnerRefs = this.relation.joinTableOwnerProp.refs(builder);
|
|
22
|
+
const tableRef = builder.tableRefFor(this.relation.getJoinModelClass(builder));
|
|
23
|
+
|
|
24
|
+
const ownerValues = this.owner.getProps(this.relation);
|
|
25
|
+
const subquery = this.modifyFilterSubquery.clone().select(`${tableRef}.*`);
|
|
26
|
+
|
|
27
|
+
return builder
|
|
28
|
+
.runBefore(async (_, builder) => {
|
|
29
|
+
// Fetch the raw rows: no models, so that no hooks run for them.
|
|
30
|
+
const rows = await subquery.toKnexQuery();
|
|
31
|
+
|
|
32
|
+
if (rows.length) {
|
|
33
|
+
builder.where((builder) => {
|
|
34
|
+
for (const row of rows) {
|
|
35
|
+
builder.orWhere((builder) => {
|
|
36
|
+
for (const [column, value] of Object.entries(row)) {
|
|
37
|
+
builder.whereRaw('?? <=> ?', [
|
|
38
|
+
`${tableRef}.${column}`,
|
|
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);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
|
|
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
|
+
module.exports = {
|
|
65
|
+
ManyToManyMySqlModifyMixin,
|
|
66
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { ManyToManyModifyMixin } = require('./ManyToManyModifyMixin');
|
|
4
|
+
|
|
5
|
+
// Like `ManyToManySqliteModifyMixin`, this identifies the join rows to modify
|
|
6
|
+
// by their physical row id, so that only the join rows that match the filters
|
|
7
|
+
// are modified (#1853). `tableoid` makes `ctid` unique for partitioned tables.
|
|
8
|
+
const ManyToManyPostgresModifyMixin = (Operation) => {
|
|
9
|
+
return class extends ManyToManyModifyMixin(Operation) {
|
|
10
|
+
applyModifyFilterForJoinTable(builder) {
|
|
11
|
+
const joinTableOwnerRefs = this.relation.joinTableOwnerProp.refs(builder);
|
|
12
|
+
const tableRef = builder.tableRefFor(this.relation.getJoinModelClass(builder));
|
|
13
|
+
const rowIdRefs = [`${tableRef}.tableoid`, `${tableRef}.ctid`];
|
|
14
|
+
|
|
15
|
+
const ownerValues = this.owner.getProps(this.relation);
|
|
16
|
+
const subquery = this.modifyFilterSubquery.clone().select(rowIdRefs);
|
|
17
|
+
|
|
18
|
+
return builder
|
|
19
|
+
.whereInComposite(rowIdRefs, subquery)
|
|
20
|
+
.whereInComposite(joinTableOwnerRefs, ownerValues);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
module.exports = {
|
|
26
|
+
ManyToManyPostgresModifyMixin,
|
|
27
|
+
};
|
|
@@ -4,12 +4,13 @@ const { getModel } = require('../../model/getModel');
|
|
|
4
4
|
const { Relation } = require('../Relation');
|
|
5
5
|
const { RelationProperty } = require('../RelationProperty');
|
|
6
6
|
|
|
7
|
-
const { isSqlite } = require('../../utils/knexUtils');
|
|
7
|
+
const { isSqlite, isMySql, isPostgres } = require('../../utils/knexUtils');
|
|
8
8
|
const { inheritModel } = require('../../model/inheritModel');
|
|
9
9
|
const { resolveModel } = require('../../utils/resolveModel');
|
|
10
10
|
const { mapAfterAllReturn } = require('../../utils/promiseUtils');
|
|
11
11
|
const { isFunction, isObject, isString } = require('../../utils/objectUtils');
|
|
12
12
|
const { createModifier } = require('../../utils/createModifier');
|
|
13
|
+
const { addWhereGroup } = require('../../queryBuilder/whereGroups');
|
|
13
14
|
|
|
14
15
|
const { ManyToManyFindOperation } = require('./find/ManyToManyFindOperation');
|
|
15
16
|
const { ManyToManyInsertOperation } = require('./insert/ManyToManyInsertOperation');
|
|
@@ -19,9 +20,15 @@ const { ManyToManyUnrelateOperation } = require('./unrelate/ManyToManyUnrelateOp
|
|
|
19
20
|
const {
|
|
20
21
|
ManyToManyUnrelateSqliteOperation,
|
|
21
22
|
} = require('./unrelate/ManyToManyUnrelateSqliteOperation');
|
|
23
|
+
const { ManyToManyUnrelateMySqlOperation } = require('./unrelate/ManyToManyUnrelateMySqlOperation');
|
|
24
|
+
const {
|
|
25
|
+
ManyToManyUnrelatePostgresOperation,
|
|
26
|
+
} = require('./unrelate/ManyToManyUnrelatePostgresOperation');
|
|
22
27
|
|
|
23
28
|
const { ManyToManyUpdateOperation } = require('./update/ManyToManyUpdateOperation');
|
|
24
29
|
const { ManyToManyUpdateSqliteOperation } = require('./update/ManyToManyUpdateSqliteOperation');
|
|
30
|
+
const { ManyToManyUpdateMySqlOperation } = require('./update/ManyToManyUpdateMySqlOperation');
|
|
31
|
+
const { ManyToManyUpdatePostgresOperation } = require('./update/ManyToManyUpdatePostgresOperation');
|
|
25
32
|
|
|
26
33
|
const { ManyToManyDeleteOperation } = require('./delete/ManyToManyDeleteOperation');
|
|
27
34
|
const { ManyToManyDeleteSqliteOperation } = require('./delete/ManyToManyDeleteSqliteOperation');
|
|
@@ -91,7 +98,7 @@ class ManyToManyRelation extends Relation {
|
|
|
91
98
|
});
|
|
92
99
|
});
|
|
93
100
|
|
|
94
|
-
owner.buildFindQuery(builder, this, joinTableOwnerRefs);
|
|
101
|
+
addWhereGroup(builder, () => owner.buildFindQuery(builder, this, joinTableOwnerRefs));
|
|
95
102
|
return this.applyModify(builder);
|
|
96
103
|
}
|
|
97
104
|
|
|
@@ -160,47 +167,25 @@ class ManyToManyRelation extends Relation {
|
|
|
160
167
|
}
|
|
161
168
|
|
|
162
169
|
update(builder, owner) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
});
|
|
168
|
-
} else {
|
|
169
|
-
return new ManyToManyUpdateOperation('update', {
|
|
170
|
-
relation: this,
|
|
171
|
-
owner,
|
|
172
|
-
});
|
|
173
|
-
}
|
|
170
|
+
return new (getUpdateOperationClass(builder))('update', {
|
|
171
|
+
relation: this,
|
|
172
|
+
owner,
|
|
173
|
+
});
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
patch(builder, owner) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
});
|
|
183
|
-
} else {
|
|
184
|
-
return new ManyToManyUpdateOperation('patch', {
|
|
185
|
-
modelOptions: { patch: true },
|
|
186
|
-
relation: this,
|
|
187
|
-
owner,
|
|
188
|
-
});
|
|
189
|
-
}
|
|
177
|
+
return new (getUpdateOperationClass(builder))('patch', {
|
|
178
|
+
modelOptions: { patch: true },
|
|
179
|
+
relation: this,
|
|
180
|
+
owner,
|
|
181
|
+
});
|
|
190
182
|
}
|
|
191
183
|
|
|
192
184
|
delete(builder, owner) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
});
|
|
198
|
-
} else {
|
|
199
|
-
return new ManyToManyDeleteOperation('delete', {
|
|
200
|
-
relation: this,
|
|
201
|
-
owner,
|
|
202
|
-
});
|
|
203
|
-
}
|
|
185
|
+
return new (getDeleteOperationClass(builder))('delete', {
|
|
186
|
+
relation: this,
|
|
187
|
+
owner,
|
|
188
|
+
});
|
|
204
189
|
}
|
|
205
190
|
|
|
206
191
|
relate(builder, owner) {
|
|
@@ -211,17 +196,10 @@ class ManyToManyRelation extends Relation {
|
|
|
211
196
|
}
|
|
212
197
|
|
|
213
198
|
unrelate(builder, owner) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
});
|
|
219
|
-
} else {
|
|
220
|
-
return new ManyToManyUnrelateOperation('unrelate', {
|
|
221
|
-
relation: this,
|
|
222
|
-
owner,
|
|
223
|
-
});
|
|
224
|
-
}
|
|
199
|
+
return new (getUnrelateOperationClass(builder))('unrelate', {
|
|
200
|
+
relation: this,
|
|
201
|
+
owner,
|
|
202
|
+
});
|
|
225
203
|
}
|
|
226
204
|
|
|
227
205
|
createJoinModels(ownerId, related) {
|
|
@@ -498,6 +476,50 @@ function finalizeJoinModelClass(ctx) {
|
|
|
498
476
|
return ctx;
|
|
499
477
|
}
|
|
500
478
|
|
|
479
|
+
// The modify operations need dialect specific code to only modify the join
|
|
480
|
+
// rows that match the filters, see the `ManyToMany*ModifyMixin` classes.
|
|
481
|
+
function getOperationClass(builder, { sqlite, mysql, postgres, other }) {
|
|
482
|
+
const knex = builder.knex();
|
|
483
|
+
|
|
484
|
+
if (isSqlite(knex)) {
|
|
485
|
+
return sqlite;
|
|
486
|
+
} else if (isMySql(knex)) {
|
|
487
|
+
return mysql;
|
|
488
|
+
} else if (isPostgres(knex)) {
|
|
489
|
+
return postgres;
|
|
490
|
+
} else {
|
|
491
|
+
return other;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function getUpdateOperationClass(builder) {
|
|
496
|
+
return getOperationClass(builder, {
|
|
497
|
+
sqlite: ManyToManyUpdateSqliteOperation,
|
|
498
|
+
mysql: ManyToManyUpdateMySqlOperation,
|
|
499
|
+
postgres: ManyToManyUpdatePostgresOperation,
|
|
500
|
+
other: ManyToManyUpdateOperation,
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function getUnrelateOperationClass(builder) {
|
|
505
|
+
return getOperationClass(builder, {
|
|
506
|
+
sqlite: ManyToManyUnrelateSqliteOperation,
|
|
507
|
+
mysql: ManyToManyUnrelateMySqlOperation,
|
|
508
|
+
postgres: ManyToManyUnrelatePostgresOperation,
|
|
509
|
+
other: ManyToManyUnrelateOperation,
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function getDeleteOperationClass(builder) {
|
|
514
|
+
// Deletes don't modify the join table, so only sqlite needs its own operation.
|
|
515
|
+
return getOperationClass(builder, {
|
|
516
|
+
sqlite: ManyToManyDeleteSqliteOperation,
|
|
517
|
+
mysql: ManyToManyDeleteOperation,
|
|
518
|
+
postgres: ManyToManyDeleteOperation,
|
|
519
|
+
other: ManyToManyDeleteOperation,
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
|
|
501
523
|
module.exports = {
|
|
502
524
|
ManyToManyRelation,
|
|
503
525
|
};
|
|
@@ -17,6 +17,7 @@ class ManyToManyRelateOperation extends RelateOperation {
|
|
|
17
17
|
const joinModelClass = this.relation.getJoinModelClass(builder.knex());
|
|
18
18
|
const ownerValues = this.owner.getSplitProps(builder, this.relation);
|
|
19
19
|
const joinModels = [];
|
|
20
|
+
const isReturning = !this.opt.dontCopyReturning && builder.has(/returning/);
|
|
20
21
|
|
|
21
22
|
for (const ownerValue of ownerValues) {
|
|
22
23
|
for (const relatedValue of this.ids) {
|
|
@@ -43,6 +44,11 @@ class ManyToManyRelateOperation extends RelateOperation {
|
|
|
43
44
|
})
|
|
44
45
|
.insert(joinModels)
|
|
45
46
|
.runAfter((models) => {
|
|
47
|
+
if (isReturning) {
|
|
48
|
+
// Return the inserted join rows if `returning` was used.
|
|
49
|
+
return models;
|
|
50
|
+
}
|
|
51
|
+
|
|
46
52
|
return Array.isArray(models) ? models.length : 1;
|
|
47
53
|
});
|
|
48
54
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { ManyToManyUnrelateOperationBase } = require('./ManyToManyUnrelateOperationBase');
|
|
4
|
+
const { ManyToManyMySqlModifyMixin } = require('../ManyToManyMySqlModifyMixin');
|
|
5
|
+
|
|
6
|
+
class ManyToManyUnrelateMySqlOperation extends ManyToManyMySqlModifyMixin(
|
|
7
|
+
ManyToManyUnrelateOperationBase,
|
|
8
|
+
) {
|
|
9
|
+
get modifyMainQuery() {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = {
|
|
15
|
+
ManyToManyUnrelateMySqlOperation,
|
|
16
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { UnrelateOperation } = require('../../../queryBuilder/operations/UnrelateOperation');
|
|
4
|
+
const { addWhereGroup } = require('../../../queryBuilder/whereGroups');
|
|
4
5
|
|
|
5
6
|
class ManyToManyUnrelateOperationBase extends UnrelateOperation {
|
|
6
7
|
queryExecutor(builder) {
|
|
@@ -10,7 +11,10 @@ class ManyToManyUnrelateOperationBase extends UnrelateOperation {
|
|
|
10
11
|
.childQueryOf(builder)
|
|
11
12
|
.delete();
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
addWhereGroup(unrelateQuery, () => this.applyModifyFilterForJoinTable(unrelateQuery));
|
|
15
|
+
addWhereGroup(unrelateQuery, () => unrelateQuery.modify(this.relation.joinTableModify));
|
|
16
|
+
|
|
17
|
+
return unrelateQuery;
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
/* istanbul ignore next */
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { ManyToManyUnrelateOperationBase } = require('./ManyToManyUnrelateOperationBase');
|
|
4
|
+
const { ManyToManyPostgresModifyMixin } = require('../ManyToManyPostgresModifyMixin');
|
|
5
|
+
|
|
6
|
+
class ManyToManyUnrelatePostgresOperation extends ManyToManyPostgresModifyMixin(
|
|
7
|
+
ManyToManyUnrelateOperationBase,
|
|
8
|
+
) {
|
|
9
|
+
get modifyMainQuery() {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = {
|
|
15
|
+
ManyToManyUnrelatePostgresOperation,
|
|
16
|
+
};
|