ManyToManyModifyMixin.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. // This mixin contains the shared code for all modify operations (update, delete, relate, unrelate)
  2. // for ManyToManyRelation operations.
  3. //
  4. // The most important thing this mixin does is that it moves the filters from the main query
  5. // into a subquery and then adds a single where clause that uses the subquery. This is done so
  6. // that we are able to `innerJoin` the join table to the query. Most SQL engines don't allow
  7. // joins in updates or deletes. Join table is joined so that queries can reference the join
  8. // table columns.
  9. const ManyToManyModifyMixin = Operation => {
  10. return class extends Operation {
  11. constructor(...args) {
  12. super(...args);
  13. this.modifyFilterSubquery = null;
  14. }
  15. get modifyMainQuery() {
  16. return true;
  17. }
  18. // At this point `builder` should only have the user's own wheres and joins. There can
  19. // be other operations (like orderBy) too, but those are meaningless with modify operations.
  20. onBuild(builder) {
  21. this.modifyFilterSubquery = this.createModifyFilterSubquery(builder);
  22. if (this.modifyMainQuery) {
  23. // We can now remove the where and join statements from the main query.
  24. this.removeFiltersFromMainQuery(builder);
  25. // Add a single where clause that uses the created subquery.
  26. this.applyModifyFilterForRelatedTable(builder);
  27. }
  28. return super.onBuild(builder);
  29. }
  30. createModifyFilterSubquery(builder) {
  31. const relatedModelClass = this.relation.relatedModelClass;
  32. const builderClass = builder.constructor;
  33. const ownerProp = this.relation.ownerProp;
  34. const ownerIds = [ownerProp.getProps(this.owner)];
  35. // Create an empty subquery.
  36. const modifyFilterSubquery = relatedModelClass.query().childQueryOf(builder);
  37. // Add the necessary joins and wheres so that only rows related to
  38. // `this.owner` are selected.
  39. this.relation.findQuery(modifyFilterSubquery, { ownerIds });
  40. // Copy all where and join statements from the main query to the subquery.
  41. modifyFilterSubquery
  42. .copyFrom(builder, builderClass.WhereSelector)
  43. .copyFrom(builder, builderClass.JoinSelector);
  44. return modifyFilterSubquery;
  45. }
  46. removeFiltersFromMainQuery(builder) {
  47. const builderClass = builder.constructor;
  48. builder.clear(builderClass.WhereSelector);
  49. builder.clear(builderClass.JoinSelector);
  50. }
  51. applyModifyFilterForRelatedTable(builder) {
  52. const idRefs = this.relation.relatedModelClass.getIdRelationProperty().refs(builder);
  53. const subquery = this.modifyFilterSubquery.clone().select(idRefs);
  54. return builder.whereInComposite(idRefs, subquery);
  55. }
  56. applyModifyFilterForJoinTable(builder) {
  57. const joinTableOwnerRefs = this.relation.joinTableOwnerProp.refs(builder);
  58. const joinTableRelatedRefs = this.relation.joinTableRelatedProp.refs(builder);
  59. const relatedRefs = this.relation.relatedProp.refs(builder);
  60. const ownerIds = this.relation.ownerProp.getProps(this.owner);
  61. const subquery = this.modifyFilterSubquery.clone().select(relatedRefs);
  62. return builder
  63. .whereInComposite(joinTableRelatedRefs, subquery)
  64. .whereComposite(joinTableOwnerRefs, ownerIds);
  65. }
  66. };
  67. };
  68. module.exports = ManyToManyModifyMixin;