implement simple partial update

This commit is contained in:
Pierre Noël 2022-09-06 17:54:42 +02:00
parent e29c069e2a
commit b7a1e312b4

View File

@ -13,6 +13,7 @@ const {
isArray, isArray,
isNull, isNull,
map, map,
uniqBy,
} = require('lodash/fp'); } = require('lodash/fp');
const types = require('../types'); const types = require('../types');
const { createField } = require('../fields'); const { createField } = require('../fields');
@ -26,6 +27,13 @@ const toIds = (value) => castArray(value || []).map(toId);
const isValidId = (value) => isString(value) || isInteger(value); const isValidId = (value) => isString(value) || isInteger(value);
const toAssocs = (data) => { const toAssocs = (data) => {
if (data?.connect || data?.disconnect) {
return {
connect: toAssocs(data.connect),
disconnect: toAssocs(data.disconnect),
};
}
return castArray(data) return castArray(data)
.filter((datum) => !isNil(datum)) .filter((datum) => !isNil(datum))
.map((datum) => { .map((datum) => {
@ -651,68 +659,82 @@ const createEntityManager = (db) => {
const onlyDeleteRelation = isNull(data[attributeName]); const onlyDeleteRelation = isNull(data[attributeName]);
let insert; let insert;
if (!onlyDeleteRelation) { if (onlyDeleteRelation) {
// clear previous associations in the joinTable
await this.createQueryBuilder(joinTable.name)
.delete()
.where({ [joinColumn.name]: id })
.where(joinTable.on || {})
.execute();
} else {
const cleanRelationData = toAssocs(data[attributeName]); const cleanRelationData = toAssocs(data[attributeName]);
const isPartialUpdate = cleanRelationData.connect || cleanRelationData.disconnect; const connect = cleanRelationData.connect || [];
const disconnect = cleanRelationData.disconnect || [];
const isPartialUpdate = connect.length || disconnect.length;
if (isPartialUpdate) { if (isPartialUpdate) {
// partial update case const idsToRemove = map('id', connect.concat(disconnect));
insert = await this.createQueryBuilder(joinTable.name).select().orderBy('order'); const existingRelsToDelete = await this.createQueryBuilder(joinTable.name)
.select([inverseJoinColumn.name, 'order'])
.where({
[joinColumn.name]: id,
[inverseJoinColumn.name]: { $in: idsToRemove },
})
.where(joinTable.on || {})
.execute();
if (isArray(cleanRelationData.connect)) { await this.createQueryBuilder(joinTable.name)
for (const connectItem of cleanRelationData.connect) { .delete()
const { start, end, after, before } = connectItem; .where({
let wasInserted = false; [joinColumn.name]: id,
const relationToInsert = { [inverseJoinColumn.name]: { $in: idsToRemove },
})
.where(joinTable.on || {})
.execute();
for (const relToDelete of existingRelsToDelete) {
await this.createQueryBuilder(joinTable.name)
.update({ order: db.getConnection().raw('?? - 1', 'order') })
.where({
[joinColumn.name]: id, [joinColumn.name]: id,
[inverseJoinColumn.name]: connectItem.id, order: { $gt: relToDelete.order },
}; })
.where(joinTable.on || {})
insert = insert.reduce((acc, rel, i, arr) => { .execute();
const currRelId = String(rel[inverseJoinColumn.name]);
const nextRelId = String(arr[i + 1]?.[inverseJoinColumn.name]);
if (currRelId === String(connectItem.id)) {
return acc;
}
if (wasInserted) {
return acc.push(rel);
}
if (i === 0 && start) {
wasInserted = true;
return acc.push(relationToInsert);
}
if (i === arr.length - 1 && (end || !wasInserted)) {
wasInserted = true;
return acc.push(relationToInsert);
}
if (after && currRelId === String(after)) {
wasInserted = true;
return acc.push(relationToInsert);
}
if (before && nextRelId === String(before)) {
wasInserted = true;
return acc.push(relationToInsert);
}
return acc.push(rel);
}, []);
}
} }
if (isArray(cleanRelationData.disconnect)) { if (connect.length) {
const idsToRemove = map('id', cleanRelationData.disconnect); const { max } = await this.createQueryBuilder(joinTable.name)
insert = insert.filter((rel) => !idsToRemove.includes(rel[inverseJoinColumn.name])); .max('order')
.where({ [joinColumn.name]: id })
.where(joinTable.on || {})
.first()
.execute();
insert = uniqBy('id', connect).map((rel, idx) => ({
[joinColumn.name]: id,
[inverseJoinColumn.name]: rel.id,
order: max + idx + 1,
}));
} }
insert.forEach((rel, i) => { if (
rel.order = i; isBidirectional(attribute) &&
}); ['oneToOne', 'oneToMany'].includes(attribute.relation)
) {
// TODO: reordering the relations when oneToMany
await this.createQueryBuilder(joinTable.name)
.delete()
.where({ [inverseJoinColumn.name]: toIds(data[attributeName]) })
.where(joinTable.on || {})
.execute();
}
if (!isEmpty(insert)) {
await this.createQueryBuilder(joinTable.name).insert(insert).execute();
}
} else { } else {
// overwrite
insert = toAssocs(data[attributeName]).map((data, idx) => { insert = toAssocs(data[attributeName]).map((data, idx) => {
return { return {
[joinColumn.name]: id, [joinColumn.name]: id,
@ -722,30 +744,28 @@ const createEntityManager = (db) => {
order: idx + 1, order: idx + 1,
}; };
}); });
}
if ( if (
isBidirectional(attribute) && isBidirectional(attribute) &&
['oneToOne', 'oneToMany'].includes(attribute.relation) ['oneToOne', 'oneToMany'].includes(attribute.relation)
) { ) {
// TODO: reordering the relations when oneToMany
await this.createQueryBuilder(joinTable.name)
.delete()
.where({ [inverseJoinColumn.name]: toIds(data[attributeName]) })
.where(joinTable.on || {})
.execute();
}
await this.createQueryBuilder(joinTable.name) await this.createQueryBuilder(joinTable.name)
.delete() .delete()
.where({ [inverseJoinColumn.name]: toIds(data[attributeName]) }) .where({ [joinColumn.name]: id })
.where(joinTable.on || {}) .where(joinTable.on || {})
.execute(); .execute();
}
}
// clear previous associations in the joinTable if (!isEmpty(insert)) {
await this.createQueryBuilder(joinTable.name) await this.createQueryBuilder(joinTable.name).insert(insert).execute();
.delete() }
.where({ [joinColumn.name]: id })
.where(joinTable.on || {})
.execute();
if (!onlyDeleteRelation) {
if (!isEmpty(insert)) {
await this.createQueryBuilder(joinTable.name).insert(insert).execute();
} }
} }
} }