From c563e49c4c0f4c01bb8d6b3750d0b3bbbc48a6f4 Mon Sep 17 00:00:00 2001 From: Marcin L <62071967+marcinlee@users.noreply.github.com> Date: Thu, 21 Jul 2022 13:50:33 +0200 Subject: [PATCH 1/2] fix for ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG mysql error when a trigger on join table was operating on the related table - targeting mysql only - filters/modify on join table of m2m relations #2 - if subquery is not needed at all (e.g. a query with just a findById(s) operation - usually coming from graph upsert) - skip it - otherwise extract a subquery reading related ids to separate query run before the delete query for m2m unrelate operation This is an upgraded (to objection v3) version of: - https://github.com/ovos/objection.js/pull/3 - https://github.com/ovos/objection.js/pull/1 --- .../manyToMany/ManyToManyModifyMixin.js | 91 ++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/lib/relations/manyToMany/ManyToManyModifyMixin.js b/lib/relations/manyToMany/ManyToManyModifyMixin.js index 93f741d93..7ddfa950b 100644 --- a/lib/relations/manyToMany/ManyToManyModifyMixin.js +++ b/lib/relations/manyToMany/ManyToManyModifyMixin.js @@ -1,6 +1,10 @@ 'use strict'; const { ManyToManyFindOperation } = require('./find/ManyToManyFindOperation'); +const { isMySql } = require('../../utils/knexUtils'); + +const FindByIdSelector = /^findByIds?$/; +const RelateUnrelateSelector = /relate$/; // This mixin contains the shared code for all modify operations (update, delete, relate, unrelate) // for ManyToManyRelation operations. @@ -26,7 +30,7 @@ const ManyToManyModifyMixin = (Operation) => { onBuild(builder) { this.modifyFilterSubquery = this.createModifyFilterSubquery(builder); - if (this.modifyMainQuery) { + if (this.modifyMainQuery && this.modifyFilterSubquery) { // We can now remove the where and join statements from the main query. this.removeFiltersFromMainQuery(builder); @@ -38,6 +42,21 @@ const ManyToManyModifyMixin = (Operation) => { } createModifyFilterSubquery(builder) { + // Check if the subquery is needed (it may be not if there are no operations other than findById(s) on the main query) + // and only if passed builder belongs to joinTableModelClass + if (builder.modelClass() === this.relation.joinTableModelClass) { + const checkQuery = builder + .clone() + .toFindQuery() + .modify(this.relation.modify) + .clear(RelateUnrelateSelector) + .clear(FindByIdSelector) + .clearOrder(); + if (checkQuery.isSelectAll()) { + return null; + } + } + const relatedModelClass = this.relation.relatedModelClass; const builderClass = builder.constructor; @@ -71,6 +90,12 @@ const ManyToManyModifyMixin = (Operation) => { } applyModifyFilterForJoinTable(builder) { + const builderWithTriggerFix = this.applyManyToManyRelationTriggerFix(builder); + // null here means fix is not applicable + if (builderWithTriggerFix !== null) { + return builderWithTriggerFix; + } + const joinTableOwnerRefs = this.relation.joinTableOwnerProp.refs(builder); const joinTableRelatedRefs = this.relation.joinTableRelatedProp.refs(builder); @@ -84,6 +109,70 @@ const ManyToManyModifyMixin = (Operation) => { .whereInComposite(joinTableOwnerRefs, ownerValues); } + /** + * Workaround for ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG mysql error + * when a trigger on join table was operating on the related table + * - targeting mysql only + * - we return null if this fix is not applicable! + * - filters/modify on join table of m2m relations + * - if subquery is not needed at all (e.g. a query with just a findById(s) operation - usually coming from graph upsert) - skip it + * - otherwise extract a subquery reading related ids to separate query run before the delete query for m2m unrelate operation + * + * This is an upgraded (to objection v3) version of: + * - https://github.com/ovos/objection.js/pull/3 + * - https://github.com/ovos/objection.js/pull/1 + * Originally based on: + * - https://github.com/ovos/objection.js/pull/2 + */ + applyManyToManyRelationTriggerFix(builder) { + // this workaround is only needed for MySQL + if (!isMySql(builder.knex())) { + return null; + } + + if (this.modifyFilterSubquery && builder.isFind()) { + return null; + } + + const joinTableOwnerRefs = this.relation.joinTableOwnerProp.refs(builder); + const joinTableRelatedRefs = this.relation.joinTableRelatedProp.refs(builder); + const ownerIds = this.relation.ownerProp.getProps(this.owner.owner); + + if (this.modifyFilterSubquery) { + // if subquery is used (in a non-find query): + // extract the subquery selecting related ids to separate query run before the main query + // to avoid ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG mysql error + // when executing a db trigger on a join table which updates related table + const relatedRefs = this.relation.relatedProp.refs(builder); + const subquery = this.modifyFilterSubquery.clone().select(relatedRefs); + + builder + .runBefore(() => subquery.execute()) + .runBefore((related, builder) => { + if (!related.length) { + builder.resolve([]); + return; + } + builder.whereInComposite( + joinTableRelatedRefs, + related.map((m) => m.$values(this.relation.relatedProp.props)) + ); + }); + } else if (builder.parentQuery()) { + // if subquery is not used: + // rewrite findById(s) from related table to join table + builder.parentQuery().forEachOperation(FindByIdSelector, (op) => { + if (op.name === 'findByIds') { + builder.whereInComposite(joinTableRelatedRefs, op.ids); + } else { + builder.whereComposite(joinTableRelatedRefs, op.id); + } + }); + } + + return builder.whereComposite(joinTableOwnerRefs, ownerIds); + } + toFindOperation() { return new ManyToManyFindOperation('find', { relation: this.relation, From bd2d242d246d84fb99d161141eab287fa33dc2ba Mon Sep 17 00:00:00 2001 From: Maciej Holyszko <14310995+falkenhawk@users.noreply.github.com> Date: Wed, 10 Aug 2022 21:41:10 +0200 Subject: [PATCH 2/2] more adjustments needed for objection v3 --- lib/relations/manyToMany/ManyToManyModifyMixin.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/relations/manyToMany/ManyToManyModifyMixin.js b/lib/relations/manyToMany/ManyToManyModifyMixin.js index 7ddfa950b..c835d6777 100644 --- a/lib/relations/manyToMany/ManyToManyModifyMixin.js +++ b/lib/relations/manyToMany/ManyToManyModifyMixin.js @@ -136,7 +136,7 @@ const ManyToManyModifyMixin = (Operation) => { const joinTableOwnerRefs = this.relation.joinTableOwnerProp.refs(builder); const joinTableRelatedRefs = this.relation.joinTableRelatedProp.refs(builder); - const ownerIds = this.relation.ownerProp.getProps(this.owner.owner); + const ownerValues = this.owner.getProps(this.relation); if (this.modifyFilterSubquery) { // if subquery is used (in a non-find query): @@ -170,7 +170,7 @@ const ManyToManyModifyMixin = (Operation) => { }); } - return builder.whereComposite(joinTableOwnerRefs, ownerIds); + return builder.whereInComposite(joinTableOwnerRefs, ownerValues); } toFindOperation() {