objection
Version:
An SQL-friendly ORM for Node.js
105 lines (81 loc) • 3.6 kB
JavaScript
'use strict';
const { ManyToManyFindOperation } = require('./find/ManyToManyFindOperation');
// This mixin contains the shared code for all modify operations (update, delete, relate, unrelate)
// for ManyToManyRelation operations.
//
// The most important thing this mixin does is that it moves the filters from the main query
// into a subquery and then adds a single where clause that uses the subquery. This is done so
// that we are able to `innerJoin` the join table to the query. Most SQL engines don't allow
// joins in updates or deletes. Join table is joined so that queries can reference the join
// table columns.
const ManyToManyModifyMixin = (Operation) => {
return class extends Operation {
constructor(...args) {
super(...args);
this.modifyFilterSubquery = null;
}
get modifyMainQuery() {
return true;
}
// At this point `builder` should only have the user's own wheres and joins. There can
// be other operations (like orderBy) too, but those are meaningless with modify operations.
onBuild(builder) {
this.modifyFilterSubquery = this.createModifyFilterSubquery(builder);
if (this.modifyMainQuery) {
// We can now remove the where and join statements from the main query.
this.removeFiltersFromMainQuery(builder);
// Add a single where clause that uses the created subquery.
this.applyModifyFilterForRelatedTable(builder);
}
return super.onBuild(builder);
}
createModifyFilterSubquery(builder) {
const relatedModelClass = this.relation.relatedModelClass;
const builderClass = builder.constructor;
// Create an empty subquery.
const modifyFilterSubquery = relatedModelClass.query().childQueryOf(builder);
// Add the necessary joins and wheres so that only rows related to
// `this.owner` are selected.
this.relation.findQuery(modifyFilterSubquery, this.owner);
// Copy all where and join statements from the main query to the subquery.
modifyFilterSubquery
.copyFrom(builder, builderClass.WhereSelector)
.copyFrom(builder, builderClass.JoinSelector);
return modifyFilterSubquery.clearSelect();
}
removeFiltersFromMainQuery(builder) {
const builderClass = builder.constructor;
builder.clear(builderClass.WhereSelector);
builder.clear(builderClass.JoinSelector);
}
applyModifyFilterForRelatedTable(builder) {
const idRefs = this.relation.relatedModelClass.getIdRelationProperty().refs(builder);
const subquery = this.modifyFilterSubquery.clone().select(idRefs);
return builder.whereInComposite(idRefs, subquery);
}
applyModifyFilterForJoinTable(builder) {
const joinTableOwnerRefs = this.relation.joinTableOwnerProp.refs(builder);
const joinTableRelatedRefs = this.relation.joinTableRelatedProp.refs(builder);
const relatedRefs = this.relation.relatedProp.refs(builder);
const ownerValues = this.owner.getProps(this.relation);
const subquery = this.modifyFilterSubquery.clone().select(relatedRefs);
return builder
.whereInComposite(joinTableRelatedRefs, subquery)
.whereInComposite(joinTableOwnerRefs, ownerValues);
}
toFindOperation() {
return new ManyToManyFindOperation('find', {
relation: this.relation,
owner: this.owner,
});
}
clone() {
const clone = super.clone();
clone.modifyFilterSubquery = this.modifyFilterSubquery;
return clone;
}
};
};
module.exports = {
ManyToManyModifyMixin,
};