objection 3.2.0 → 3.4.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 +13 -13
- package/lib/model/AjvValidator.js +220 -13
- package/lib/model/Model.js +3 -1
- package/lib/model/modelId.js +10 -2
- package/lib/model/modelQueryProps.js +7 -0
- package/lib/queryBuilder/QueryBuilder.js +191 -8
- package/lib/queryBuilder/QueryBuilderOperationSupport.js +5 -0
- package/lib/queryBuilder/ReferenceBuilder.js +5 -3
- package/lib/queryBuilder/RelationExpression.js +13 -1
- package/lib/queryBuilder/StaticHookArguments.js +12 -1
- package/lib/queryBuilder/graph/GraphFetcher.js +1 -3
- package/lib/queryBuilder/graph/GraphOptions.js +30 -1
- package/lib/queryBuilder/graph/GraphUpsert.js +38 -1
- package/lib/queryBuilder/graph/insert/DeferredRelationGraphInsertAction.js +57 -0
- package/lib/queryBuilder/graph/insert/GraphInsert.js +107 -8
- package/lib/queryBuilder/graph/insert/GraphInsertAction.js +8 -1
- package/lib/queryBuilder/graph/patch/GraphPatchAction.js +8 -2
- package/lib/queryBuilder/join/JoinResultParser.js +22 -0
- package/lib/queryBuilder/join/RelationJoiner.js +80 -27
- package/lib/queryBuilder/join/utils.js +20 -2
- package/lib/queryBuilder/operations/InsertAndFetchOperation.js +7 -1
- package/lib/queryBuilder/operations/InsertGraphOperation.js +2 -2
- package/lib/queryBuilder/operations/InsertOperation.js +129 -13
- package/lib/queryBuilder/operations/QueryBuilderOperation.js +4 -0
- package/lib/queryBuilder/operations/ReturningOperation.js +28 -3
- 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 +4 -0
- package/lib/queryBuilder/operations/eager/JoinEagerOperation.js +2 -6
- package/lib/queryBuilder/operations/eager/WhereInEagerOperation.js +10 -33
- 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 +41 -34
- 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/whereGroups.js +125 -0
- package/lib/relations/Relation.js +5 -2
- package/lib/relations/RelationFindOperation.js +46 -2
- package/lib/relations/RelationProperty.js +3 -2
- package/lib/relations/hasMany/HasManyRelateOperation.js +5 -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 +104 -51
- package/lib/relations/manyToMany/find/ManyToManyFindOperation.js +4 -2
- 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 +12 -2
- package/lib/relations/manyToMany/update/ManyToManyUpdatePostgresOperation.js +12 -0
- package/lib/utils/assert.js +9 -0
- package/lib/utils/deprecate.js +6 -0
- package/lib/utils/identifierMapping.js +15 -1
- package/lib/utils/parseFieldExpression.js +28 -0
- package/lib/utils/wrapDbError.js +36 -0
- package/package.json +8 -7
- package/typings/objection/index.d.ts +281 -29
|
@@ -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) {
|
|
@@ -31,8 +32,10 @@ class HasManyRelateOperation extends RelateOperation {
|
|
|
31
32
|
.copyFrom(builder, /returning/)
|
|
32
33
|
.copyFrom(builder, builder.constructor.JoinSelector)
|
|
33
34
|
.copyFrom(builder, builder.constructor.WhereSelector)
|
|
34
|
-
.
|
|
35
|
-
|
|
35
|
+
.modify((query) => {
|
|
36
|
+
addWhereGroup(query, () => query.findByIds(this.ids));
|
|
37
|
+
addWhereGroup(query, () => query.modify(this.relation.modify));
|
|
38
|
+
});
|
|
36
39
|
}
|
|
37
40
|
}
|
|
38
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');
|
|
@@ -68,10 +75,39 @@ class ManyToManyRelation extends Relation {
|
|
|
68
75
|
return [];
|
|
69
76
|
}
|
|
70
77
|
|
|
78
|
+
isJoinTableRelatedTable(builder) {
|
|
79
|
+
return builder.tableNameFor(this.joinTable) === builder.tableNameFor(this.relatedModelClass);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Returns the alias of the join table in `findQuery`. If the join table is
|
|
83
|
+
// also the related table, it needs an alias of its own (#1803).
|
|
84
|
+
joinTableAliasFor(builder) {
|
|
85
|
+
const joinTableRef = builder.tableRefFor(this.joinTable);
|
|
86
|
+
|
|
87
|
+
if (this.isJoinTableRelatedTable(builder)) {
|
|
88
|
+
return this.ownerModelClass.joinTableAlias(joinTableRef);
|
|
89
|
+
} else {
|
|
90
|
+
return joinTableRef;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// The modify operations can't tell the join rows apart from the related rows
|
|
95
|
+
// if the join table is also the related table, so they would modify the
|
|
96
|
+
// wrong rows (#1803).
|
|
97
|
+
assertCanModifyJoinTable(builder, operationName) {
|
|
98
|
+
if (this.isJoinTableRelatedTable(builder)) {
|
|
99
|
+
throw this.createError(
|
|
100
|
+
`${operationName} is not supported when the join table is also the related table`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
71
105
|
findQuery(builder, owner) {
|
|
72
|
-
const joinTableOwnerRefs = this.joinTableOwnerProp.refs(builder);
|
|
73
106
|
const joinTable = builder.tableNameFor(this.joinTable);
|
|
74
|
-
const joinTableAlias =
|
|
107
|
+
const joinTableAlias = this.joinTableAliasFor(builder);
|
|
108
|
+
const joinTableOwnerRefs = this.joinTableOwnerProp
|
|
109
|
+
.refs(builder)
|
|
110
|
+
.map((ref) => ref.table(joinTableAlias));
|
|
75
111
|
|
|
76
112
|
let joinTableSelect = this.joinTableModelClass
|
|
77
113
|
.query()
|
|
@@ -85,13 +121,13 @@ class ManyToManyRelation extends Relation {
|
|
|
85
121
|
builder.join(joinTableSelect, (join) => {
|
|
86
122
|
this.relatedProp.forEach((i) => {
|
|
87
123
|
const relatedRef = this.relatedProp.ref(builder, i);
|
|
88
|
-
const joinTableRelatedRef = this.joinTableRelatedProp.ref(builder, i);
|
|
124
|
+
const joinTableRelatedRef = this.joinTableRelatedProp.ref(builder, i).table(joinTableAlias);
|
|
89
125
|
|
|
90
126
|
join.on(relatedRef, joinTableRelatedRef);
|
|
91
127
|
});
|
|
92
128
|
});
|
|
93
129
|
|
|
94
|
-
owner.buildFindQuery(builder, this, joinTableOwnerRefs);
|
|
130
|
+
addWhereGroup(builder, () => owner.buildFindQuery(builder, this, joinTableOwnerRefs));
|
|
95
131
|
return this.applyModify(builder);
|
|
96
132
|
}
|
|
97
133
|
|
|
@@ -160,47 +196,25 @@ class ManyToManyRelation extends Relation {
|
|
|
160
196
|
}
|
|
161
197
|
|
|
162
198
|
update(builder, owner) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
});
|
|
168
|
-
} else {
|
|
169
|
-
return new ManyToManyUpdateOperation('update', {
|
|
170
|
-
relation: this,
|
|
171
|
-
owner,
|
|
172
|
-
});
|
|
173
|
-
}
|
|
199
|
+
return new (getUpdateOperationClass(builder))('update', {
|
|
200
|
+
relation: this,
|
|
201
|
+
owner,
|
|
202
|
+
});
|
|
174
203
|
}
|
|
175
204
|
|
|
176
205
|
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
|
-
}
|
|
206
|
+
return new (getUpdateOperationClass(builder))('patch', {
|
|
207
|
+
modelOptions: { patch: true },
|
|
208
|
+
relation: this,
|
|
209
|
+
owner,
|
|
210
|
+
});
|
|
190
211
|
}
|
|
191
212
|
|
|
192
213
|
delete(builder, owner) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
});
|
|
198
|
-
} else {
|
|
199
|
-
return new ManyToManyDeleteOperation('delete', {
|
|
200
|
-
relation: this,
|
|
201
|
-
owner,
|
|
202
|
-
});
|
|
203
|
-
}
|
|
214
|
+
return new (getDeleteOperationClass(builder))('delete', {
|
|
215
|
+
relation: this,
|
|
216
|
+
owner,
|
|
217
|
+
});
|
|
204
218
|
}
|
|
205
219
|
|
|
206
220
|
relate(builder, owner) {
|
|
@@ -211,17 +225,12 @@ class ManyToManyRelation extends Relation {
|
|
|
211
225
|
}
|
|
212
226
|
|
|
213
227
|
unrelate(builder, owner) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
220
|
-
return new ManyToManyUnrelateOperation('unrelate', {
|
|
221
|
-
relation: this,
|
|
222
|
-
owner,
|
|
223
|
-
});
|
|
224
|
-
}
|
|
228
|
+
this.assertCanModifyJoinTable(builder, 'unrelate');
|
|
229
|
+
|
|
230
|
+
return new (getUnrelateOperationClass(builder))('unrelate', {
|
|
231
|
+
relation: this,
|
|
232
|
+
owner,
|
|
233
|
+
});
|
|
225
234
|
}
|
|
226
235
|
|
|
227
236
|
createJoinModels(ownerId, related) {
|
|
@@ -498,6 +507,50 @@ function finalizeJoinModelClass(ctx) {
|
|
|
498
507
|
return ctx;
|
|
499
508
|
}
|
|
500
509
|
|
|
510
|
+
// The modify operations need dialect specific code to only modify the join
|
|
511
|
+
// rows that match the filters, see the `ManyToMany*ModifyMixin` classes.
|
|
512
|
+
function getOperationClass(builder, { sqlite, mysql, postgres, other }) {
|
|
513
|
+
const knex = builder.knex();
|
|
514
|
+
|
|
515
|
+
if (isSqlite(knex)) {
|
|
516
|
+
return sqlite;
|
|
517
|
+
} else if (isMySql(knex)) {
|
|
518
|
+
return mysql;
|
|
519
|
+
} else if (isPostgres(knex)) {
|
|
520
|
+
return postgres;
|
|
521
|
+
} else {
|
|
522
|
+
return other;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function getUpdateOperationClass(builder) {
|
|
527
|
+
return getOperationClass(builder, {
|
|
528
|
+
sqlite: ManyToManyUpdateSqliteOperation,
|
|
529
|
+
mysql: ManyToManyUpdateMySqlOperation,
|
|
530
|
+
postgres: ManyToManyUpdatePostgresOperation,
|
|
531
|
+
other: ManyToManyUpdateOperation,
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function getUnrelateOperationClass(builder) {
|
|
536
|
+
return getOperationClass(builder, {
|
|
537
|
+
sqlite: ManyToManyUnrelateSqliteOperation,
|
|
538
|
+
mysql: ManyToManyUnrelateMySqlOperation,
|
|
539
|
+
postgres: ManyToManyUnrelatePostgresOperation,
|
|
540
|
+
other: ManyToManyUnrelateOperation,
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function getDeleteOperationClass(builder) {
|
|
545
|
+
// Deletes don't modify the join table, so only sqlite needs its own operation.
|
|
546
|
+
return getOperationClass(builder, {
|
|
547
|
+
sqlite: ManyToManyDeleteSqliteOperation,
|
|
548
|
+
mysql: ManyToManyDeleteOperation,
|
|
549
|
+
postgres: ManyToManyDeleteOperation,
|
|
550
|
+
other: ManyToManyDeleteOperation,
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
|
|
501
554
|
module.exports = {
|
|
502
555
|
ManyToManyRelation,
|
|
503
556
|
};
|
|
@@ -29,7 +29,7 @@ class ManyToManyFindOperation extends RelationFindOperation {
|
|
|
29
29
|
|
|
30
30
|
// Also select all extra columns.
|
|
31
31
|
for (const extra of this.relation.joinTableExtras) {
|
|
32
|
-
const joinTable =
|
|
32
|
+
const joinTable = this.relation.joinTableAliasFor(builder);
|
|
33
33
|
builder.select(`${joinTable}.${extra.joinTableCol} as ${extra.aliasCol}`);
|
|
34
34
|
}
|
|
35
35
|
}
|
|
@@ -88,7 +88,9 @@ class ManyToManyFindOperation extends RelationFindOperation {
|
|
|
88
88
|
// We must select the owner join columns so that we know for which owner model the related
|
|
89
89
|
// models belong to after the requests.
|
|
90
90
|
joinTableOwnerProp.forEach((i) => {
|
|
91
|
-
const joinTableOwnerRef = joinTableOwnerProp
|
|
91
|
+
const joinTableOwnerRef = joinTableOwnerProp
|
|
92
|
+
.ref(builder, i)
|
|
93
|
+
.table(this.relation.joinTableAliasFor(builder));
|
|
92
94
|
const propName = relatedModelClass.columnNameToPropertyName(this.ownerJoinColumnAlias[i]);
|
|
93
95
|
|
|
94
96
|
builder.select(joinTableOwnerRef.as(this.ownerJoinColumnAlias[i]));
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { ManyToManyUpdateOperationBase } = require('./ManyToManyUpdateOperationBase');
|
|
4
|
+
const { ManyToManyMySqlModifyMixin } = require('../ManyToManyMySqlModifyMixin');
|
|
5
|
+
|
|
6
|
+
class ManyToManyUpdateMySqlOperation extends ManyToManyMySqlModifyMixin(
|
|
7
|
+
ManyToManyUpdateOperationBase,
|
|
8
|
+
) {}
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
ManyToManyUpdateMySqlOperation,
|
|
12
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { UpdateOperation } = require('../../../queryBuilder/operations/UpdateOperation');
|
|
4
|
+
const { addWhereGroup } = require('../../../queryBuilder/whereGroups');
|
|
4
5
|
|
|
5
6
|
class ManyToManyUpdateOperationBase extends UpdateOperation {
|
|
6
7
|
constructor(name, opt) {
|
|
@@ -25,6 +26,10 @@ class ManyToManyUpdateOperationBase extends UpdateOperation {
|
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
if (this.hasExtraProps) {
|
|
30
|
+
this.relation.assertCanModifyJoinTable(builder, `${this.name} with extra properties`);
|
|
31
|
+
}
|
|
32
|
+
|
|
28
33
|
const res = super.onAdd(builder, args);
|
|
29
34
|
|
|
30
35
|
if (this.hasExtraProps) {
|
|
@@ -44,9 +49,14 @@ class ManyToManyUpdateOperationBase extends UpdateOperation {
|
|
|
44
49
|
.childQueryOf(builder)
|
|
45
50
|
.patch(this.joinTablePatch);
|
|
46
51
|
|
|
47
|
-
|
|
48
|
-
this.
|
|
52
|
+
addWhereGroup(joinTableUpdateQuery, () =>
|
|
53
|
+
this.applyModifyFilterForJoinTable(joinTableUpdateQuery),
|
|
49
54
|
);
|
|
55
|
+
addWhereGroup(joinTableUpdateQuery, () =>
|
|
56
|
+
joinTableUpdateQuery.modify(this.relation.joinTableModify),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
await joinTableUpdateQuery;
|
|
50
60
|
return result;
|
|
51
61
|
} else {
|
|
52
62
|
return result;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { ManyToManyUpdateOperationBase } = require('./ManyToManyUpdateOperationBase');
|
|
4
|
+
const { ManyToManyPostgresModifyMixin } = require('../ManyToManyPostgresModifyMixin');
|
|
5
|
+
|
|
6
|
+
class ManyToManyUpdatePostgresOperation extends ManyToManyPostgresModifyMixin(
|
|
7
|
+
ManyToManyUpdateOperationBase,
|
|
8
|
+
) {}
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
ManyToManyUpdatePostgresOperation,
|
|
12
|
+
};
|
package/lib/utils/assert.js
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
function assertHasIdColumn(modelClass) {
|
|
4
|
+
if (modelClass.getIdColumnArray().length === 0) {
|
|
5
|
+
throw new Error(`model ${modelClass.name} has no idColumn`);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
3
9
|
function assertHasId(model) {
|
|
10
|
+
assertHasIdColumn(model.constructor);
|
|
11
|
+
|
|
4
12
|
if (!model.$hasId()) {
|
|
5
13
|
const modelClass = model.constructor;
|
|
6
14
|
const ids = modelClass.getIdColumnArray().join(', ');
|
|
@@ -21,5 +29,6 @@ function assertIdNotUndefined(id, message) {
|
|
|
21
29
|
|
|
22
30
|
module.exports = {
|
|
23
31
|
assertHasId,
|
|
32
|
+
assertHasIdColumn,
|
|
24
33
|
assertIdNotUndefined,
|
|
25
34
|
};
|
package/lib/utils/deprecate.js
CHANGED
|
@@ -10,6 +10,12 @@ function deprecate(message) {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
// Only meant for tests that need to see a deprecation message again.
|
|
14
|
+
function resetDeprecations() {
|
|
15
|
+
LOGGED_DEPRECATIONS.clear();
|
|
16
|
+
}
|
|
17
|
+
|
|
13
18
|
module.exports = {
|
|
14
19
|
deprecate,
|
|
20
|
+
resetDeprecations,
|
|
15
21
|
};
|
|
@@ -168,10 +168,24 @@ function keyMapper(mapper) {
|
|
|
168
168
|
};
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
// Returns a function that only calls `mapper` for the part of the input string
|
|
172
|
+
// before the first `separator`, and leaves the rest untouched. For field
|
|
173
|
+
// expressions like `jsonColumn:some.jsonPath` this maps the column but keeps
|
|
174
|
+
// the json path as is. Column names can't contain `:`, so the first one marks
|
|
175
|
+
// the boundary.
|
|
176
|
+
function mapFirstPart(mapper, separator) {
|
|
177
|
+
return (str) => {
|
|
178
|
+
const idx = str.indexOf(separator);
|
|
179
|
+
return idx === -1 ? mapper(str) : mapper(str.slice(0, idx)) + str.slice(idx);
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
171
183
|
function snakeCaseMappers(opt = {}) {
|
|
184
|
+
const format = (str) => snakeCase(str, opt);
|
|
185
|
+
|
|
172
186
|
return {
|
|
173
187
|
parse: keyMapper(memoize((str) => camelCase(str, opt))),
|
|
174
|
-
format: keyMapper(memoize(
|
|
188
|
+
format: keyMapper(memoize(opt.preserveJsonKeys ? mapFirstPart(format, ':') : format)),
|
|
175
189
|
};
|
|
176
190
|
}
|
|
177
191
|
|
|
@@ -46,6 +46,34 @@ function freezeParsedExpr(parsedExpr) {
|
|
|
46
46
|
return parsedExpr;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
// Formats the json path of a parsed field expression as a Postgres text array
|
|
50
|
+
// literal, e.g. `{a,"",b}`. Keys that would otherwise be misread (empty keys,
|
|
51
|
+
// keys with whitespace, delimiters, quotes or backslashes and `NULL`) are
|
|
52
|
+
// double-quoted, with quotes and backslashes escaped.
|
|
53
|
+
function toPostgresJsonPath(access) {
|
|
54
|
+
const keys = access.map(({ ref }) => {
|
|
55
|
+
const key = String(ref);
|
|
56
|
+
return key === '' || /[{}",\\\s]/.test(key) || key.toUpperCase() === 'NULL'
|
|
57
|
+
? `"${key.replace(/["\\]/g, '\\$&')}"`
|
|
58
|
+
: key;
|
|
59
|
+
});
|
|
60
|
+
return `{${keys.join(',')}}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Formats the json path of a parsed field expression as a quoted string literal
|
|
64
|
+
// to be used in raw knex SQL, e.g. `'{a,b}'`. The path can't be passed as a
|
|
65
|
+
// binding, as knex doesn't keep the order of bindings in raw columns (e.g. in
|
|
66
|
+
// `whereIn(ref(...), values)`), so the keys need to be escaped instead: single
|
|
67
|
+
// quotes are doubled, and `?` is escaped so that knex doesn't read it as a
|
|
68
|
+
// binding placeholder. Backslashes are always doubled by `toPostgresJsonPath()`,
|
|
69
|
+
// so they can't escape the closing quote either.
|
|
70
|
+
function toPostgresJsonPathLiteral(access) {
|
|
71
|
+
const path = toPostgresJsonPath(access).replace(/'/g, "''").replace(/\?/g, '\\?');
|
|
72
|
+
return `'${path}'`;
|
|
73
|
+
}
|
|
74
|
+
|
|
49
75
|
module.exports = {
|
|
50
76
|
parseFieldExpression,
|
|
77
|
+
toPostgresJsonPath,
|
|
78
|
+
toPostgresJsonPathLiteral,
|
|
51
79
|
};
|