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.
Files changed (69) hide show
  1. package/README.md +16 -16
  2. package/lib/model/AjvValidator.js +173 -18
  3. package/lib/model/Model.js +3 -1
  4. package/lib/model/graph/ModelGraphNode.js +12 -0
  5. package/lib/model/modelToJson.js +2 -1
  6. package/lib/queryBuilder/QueryBuilder.js +339 -47
  7. package/lib/queryBuilder/QueryBuilderOperationSupport.js +5 -0
  8. package/lib/queryBuilder/ReferenceBuilder.js +5 -3
  9. package/lib/queryBuilder/RelationExpression.js +21 -9
  10. package/lib/queryBuilder/StaticHookArguments.js +12 -1
  11. package/lib/queryBuilder/graph/GraphFetcher.js +3 -1
  12. package/lib/queryBuilder/graph/GraphOptions.js +21 -2
  13. package/lib/queryBuilder/graph/GraphUpsert.js +52 -0
  14. package/lib/queryBuilder/graph/delete/GraphDeleteAction.js +20 -14
  15. package/lib/queryBuilder/graph/insert/DeferredRelationGraphInsertAction.js +57 -0
  16. package/lib/queryBuilder/graph/insert/GraphInsert.js +113 -8
  17. package/lib/queryBuilder/graph/insert/GraphInsertAction.js +10 -1
  18. package/lib/queryBuilder/graph/patch/GraphPatchAction.js +15 -3
  19. package/lib/queryBuilder/join/JoinResultParser.js +22 -0
  20. package/lib/queryBuilder/join/RelationJoiner.js +71 -5
  21. package/lib/queryBuilder/operations/DelegateOperation.js +4 -0
  22. package/lib/queryBuilder/operations/InsertAndFetchOperation.js +7 -1
  23. package/lib/queryBuilder/operations/InsertGraphOperation.js +2 -2
  24. package/lib/queryBuilder/operations/InsertOperation.js +121 -12
  25. package/lib/queryBuilder/operations/NoneOperation.js +37 -0
  26. package/lib/queryBuilder/operations/ObjectionToKnexConvertingOperation.js +5 -0
  27. package/lib/queryBuilder/operations/QueryBuilderOperation.js +4 -0
  28. package/lib/queryBuilder/operations/ReturningOperation.js +28 -3
  29. package/lib/queryBuilder/operations/UpdateAndFetchOperation.js +20 -0
  30. package/lib/queryBuilder/operations/UpdateOperation.js +3 -2
  31. package/lib/queryBuilder/operations/UpsertGraphOperation.js +5 -0
  32. package/lib/queryBuilder/operations/WhereGroupOperation.js +41 -0
  33. package/lib/queryBuilder/operations/eager/EagerOperation.js +7 -5
  34. package/lib/queryBuilder/operations/eager/JoinEagerOperation.js +4 -2
  35. package/lib/queryBuilder/operations/eager/WhereInEagerOperation.js +9 -20
  36. package/lib/queryBuilder/operations/jsonApi/WhereJsonHasPostgresOperation.js +3 -3
  37. package/lib/queryBuilder/operations/jsonApi/WhereJsonNotObjectPostgresOperation.js +1 -1
  38. package/lib/queryBuilder/operations/jsonApi/postgresJsonApi.js +45 -32
  39. package/lib/queryBuilder/operations/select/Selection.js +95 -4
  40. package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeMsSqlOperation.js +6 -0
  41. package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeOperation.js +5 -1
  42. package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeSqliteOperation.js +9 -0
  43. package/lib/queryBuilder/parsers/jsonFieldExpressionParser.js +81 -43
  44. package/lib/queryBuilder/parsers/jsonFieldExpressionParser.pegjs +5 -4
  45. package/lib/queryBuilder/transformations/WrapMysqlModifySubqueryTransformation.js +9 -0
  46. package/lib/queryBuilder/whereGroups.js +125 -0
  47. package/lib/relations/Relation.js +5 -2
  48. package/lib/relations/RelationProperty.js +7 -3
  49. package/lib/relations/belongsToOne/BelongsToOneInsertOperation.js +1 -0
  50. package/lib/relations/belongsToOne/BelongsToOneRelateOperation.js +5 -0
  51. package/lib/relations/hasMany/HasManyRelateOperation.js +6 -2
  52. package/lib/relations/hasMany/HasManyUnrelateOperation.js +5 -2
  53. package/lib/relations/manyToMany/ManyToManyModifyMixin.js +26 -3
  54. package/lib/relations/manyToMany/ManyToManyMySqlModifyMixin.js +66 -0
  55. package/lib/relations/manyToMany/ManyToManyPostgresModifyMixin.js +27 -0
  56. package/lib/relations/manyToMany/ManyToManyRelation.js +70 -48
  57. package/lib/relations/manyToMany/relate/ManyToManyRelateOperation.js +6 -0
  58. package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateMySqlOperation.js +16 -0
  59. package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperationBase.js +5 -1
  60. package/lib/relations/manyToMany/unrelate/ManyToManyUnrelatePostgresOperation.js +16 -0
  61. package/lib/relations/manyToMany/update/ManyToManyUpdateMySqlOperation.js +12 -0
  62. package/lib/relations/manyToMany/update/ManyToManyUpdateOperationBase.js +8 -2
  63. package/lib/relations/manyToMany/update/ManyToManyUpdatePostgresOperation.js +12 -0
  64. package/lib/utils/deprecate.js +6 -0
  65. package/lib/utils/identifierMapping.js +23 -3
  66. package/lib/utils/objectUtils.js +9 -5
  67. package/lib/utils/parseFieldExpression.js +28 -0
  68. package/package.json +22 -14
  69. 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
- return builder.modify(this.modify);
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
- if (!ref.tableName) {
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(ref.tableName);
195
+ const modelClass = modelClassResolver(tableName);
192
196
 
193
197
  if (!modelClass) {
194
- throw new ModelNotFoundError(ref.tableName);
198
+ throw new ModelNotFoundError(tableName);
195
199
  }
196
200
 
197
201
  const prop = modelClass.columnNameToPropertyName(ref.column);
@@ -22,6 +22,7 @@ class BelongsToOneInsertOperation extends RelationInsertOperation {
22
22
  }
23
23
 
24
24
  const relateOp = new BelongsToOneRelateOperation('relate', {
25
+ dontCopyReturning: true,
25
26
  relation: this.relation,
26
27
  owner: this.owner,
27
28
  });
@@ -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
- .findByIds(this.ids)
34
- .modify(this.relation.modify);
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
- .whereInComposite(relatedRefs, ownerValues)
23
- .modify(this.relation.modify);
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
- return new ManyToManyFindOperation('find', {
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
- if (isSqlite(builder.knex())) {
164
- return new ManyToManyUpdateSqliteOperation('update', {
165
- relation: this,
166
- owner,
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
- if (isSqlite(builder.knex())) {
178
- return new ManyToManyUpdateSqliteOperation('patch', {
179
- modelOptions: { patch: true },
180
- relation: this,
181
- owner,
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
- if (isSqlite(builder.knex())) {
194
- return new ManyToManyDeleteSqliteOperation('delete', {
195
- relation: this,
196
- owner,
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
- if (isSqlite(builder.knex())) {
215
- return new ManyToManyUnrelateSqliteOperation('unrelate', {
216
- relation: this,
217
- owner,
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
- return this.applyModifyFilterForJoinTable(unrelateQuery).modify(this.relation.joinTableModify);
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
+ };