use onConflict in updateRelations

This commit is contained in:
Pierre Noël 2022-09-20 15:53:17 +02:00
parent 3b0d6b6e7f
commit a9819dc375
5 changed files with 116 additions and 66 deletions

View File

@ -16,6 +16,8 @@ const {
isEqual, isEqual,
differenceWith, differenceWith,
isNumber, isNumber,
map,
difference,
} = require('lodash/fp'); } = require('lodash/fp');
const types = require('../types'); const types = require('../types');
const { createField } = require('../fields'); const { createField } = require('../fields');
@ -723,7 +725,7 @@ const createEntityManager = (db) => {
// only delete relations // only delete relations
if (isNull(cleanRelationData.set)) { if (isNull(cleanRelationData.set)) {
await deleteRelations({ id, attribute, joinTable, db }, { relsToDelete: 'all' }); await deleteRelations({ id, attribute, joinTable, db }, { relIdsToDelete: 'all' });
} else { } else {
const isPartialUpdate = !has('set', cleanRelationData); const isPartialUpdate = !has('set', cleanRelationData);
let relIdsToaddOrMove; let relIdsToaddOrMove;
@ -740,10 +742,7 @@ const createEntityManager = (db) => {
differenceWith(isEqual, cleanRelationData.disconnect, cleanRelationData.connect) differenceWith(isEqual, cleanRelationData.disconnect, cleanRelationData.connect)
); );
await deleteRelations( await deleteRelations({ id, attribute, joinTable, db }, { relIdsToDelete });
{ id, attribute, joinTable, db },
{ relsToDelete: relIdsToDelete }
);
// add/move // add/move
let max; let max;
@ -838,63 +837,75 @@ const createEntityManager = (db) => {
relIdsToaddOrMove = toIds(cleanRelationData.set); relIdsToaddOrMove = toIds(cleanRelationData.set);
await deleteRelations( await deleteRelations(
{ id, attribute, joinTable, db }, { id, attribute, joinTable, db },
{ relsToDelete: 'all', relsToNotDelete: relIdsToaddOrMove } { relIdsToDelete: 'all', relIdsToNotDelete: relIdsToaddOrMove }
); );
const currentMovingRels = await this.createQueryBuilder(joinTable.name) if (isEmpty(cleanRelationData.set)) {
.select(select) continue;
.where({
[joinColumn.name]: id,
[inverseJoinColumn.name]: { $in: relIdsToaddOrMove },
})
.where(joinTable.on || {})
.execute();
const currentMovingRelsMap = currentMovingRels.reduce(
(acc, rel) => Object.assign(acc, { [rel[inverseJoinColumn.name]]: rel }),
{}
);
let index = 0;
for (const relToAdd of cleanRelationData.set) {
const currentRel = currentMovingRelsMap[relToAdd.id];
if (currentRel && isAnyToMany(attribute)) {
const update = { [orderColumnName]: index + 1 };
await this.createQueryBuilder(joinTable.name)
.update(update)
.where({
[joinColumn.name]: id,
[inverseJoinColumn.name]: relToAdd.id,
})
.where(joinTable.on || {})
.execute();
} else if (!currentRel) {
const insert = {
[joinColumn.name]: id,
[inverseJoinColumn.name]: relToAdd.id,
...(joinTable.on || {}),
...(relToAdd.__pivot || {}),
};
if (isAnyToMany(attribute)) {
insert[orderColumnName] = index + 1;
}
// can be optimized in one query
if (isBidirectional(attribute) && isManyToAny(attribute)) {
const { max: reverseMax } = await this.createQueryBuilder(joinTable.name)
.max(inverseOrderColumnName)
.where({ [inverseJoinColumn.name]: id })
.where(joinTable.on || {})
.first()
.execute();
insert[inverseOrderColumnName] = reverseMax + 1;
}
await this.createQueryBuilder(joinTable.name).insert(insert).execute();
}
index += 1;
} }
const insert = cleanRelationData.set.map((relToAdd) => ({
[joinColumn.name]: id,
[inverseJoinColumn.name]: relToAdd.id,
...(joinTable.on || {}),
...(relToAdd.__pivot || {}),
}));
// add order value
if (isAnyToMany(attribute)) {
insert.forEach((row, idx) => {
row[orderColumnName] = idx + 1;
});
}
// add inv order value
if (isBidirectional(attribute) && isManyToAny(attribute)) {
const existingRels = await this.createQueryBuilder(joinTable.name)
.select('id')
.where({
[joinColumn.name]: id,
[inverseJoinColumn.name]: { $in: relIdsToaddOrMove },
})
.where(joinTable.on || {})
.execute();
const nonExistingRelsIds = difference(relIdsToaddOrMove, map('id', existingRels));
const maxResults = await db
.getConnection()
.select(inverseJoinColumn.name)
.max(inverseOrderColumnName, { as: 'max' })
.whereIn(inverseJoinColumn.name, nonExistingRelsIds)
.where(joinTable.on || {})
.groupBy(inverseJoinColumn.name)
.from(joinTable.name);
const maxMap = maxResults.reduce(
(acc, res) => Object.assign(acc, { [res[inverseJoinColumn.name]]: res.max }),
{}
);
insert.forEach((row) => {
row[inverseOrderColumnName] = (maxMap[row[inverseJoinColumn.name]] || 0) + 1;
});
}
// insert rows
const query = this.createQueryBuilder(joinTable.name)
.insert(insert)
.onConflict([
joinColumn.name,
inverseJoinColumn.name,
...Object.keys(joinTable.on || {}),
]);
if (isAnyToMany(attribute)) {
query.merge([orderColumnName]);
} else {
query.ignore();
}
await query.execute();
} }
// Delete the previous relations for oneToAny relations // Delete the previous relations for oneToAny relations
@ -1031,7 +1042,7 @@ const createEntityManager = (db) => {
if (attribute.joinTable) { if (attribute.joinTable) {
const { joinTable } = attribute; const { joinTable } = attribute;
await deleteRelations({ id, attribute, joinTable, db }, { relsToDelete: 'all' }); await deleteRelations({ id, attribute, joinTable, db }, { relIdsToDelete: 'all' });
} }
} }
}, },

View File

@ -112,11 +112,11 @@ const deletePreviousAnyToOneRelations = async ({ id, attribute, joinTable, relId
// INVERSE ORDER UPDATE // INVERSE ORDER UPDATE
const deleteRelations = async ( const deleteRelations = async (
{ id, attribute, joinTable, db }, { id, attribute, joinTable, db },
{ relsToNotDelete = [], relsToDelete = [] } { relIdsToNotDelete = [], relIdsToDelete = [] }
) => { ) => {
const { joinColumn, inverseJoinColumn, orderColumnName, inverseOrderColumnName } = joinTable; const { joinColumn, inverseJoinColumn, orderColumnName, inverseOrderColumnName } = joinTable;
const select = getSelect(joinTable, attribute); const select = getSelect(joinTable, attribute);
const all = relsToDelete === 'all'; const all = relIdsToDelete === 'all';
if (isAnyToMany(attribute) || (isBidirectional(attribute) && isManyToAny(attribute))) { if (isAnyToMany(attribute) || (isBidirectional(attribute) && isManyToAny(attribute))) {
let lastId = 0; let lastId = 0;
@ -128,8 +128,8 @@ const deleteRelations = async (
.where({ .where({
[joinColumn.name]: id, [joinColumn.name]: id,
id: { $gt: lastId }, id: { $gt: lastId },
[inverseJoinColumn.name]: { $notIn: relsToNotDelete }, [inverseJoinColumn.name]: { $notIn: relIdsToNotDelete },
...(all ? {} : { [inverseJoinColumn.name]: { $in: relsToDelete } }), ...(all ? {} : { [inverseJoinColumn.name]: { $in: relIdsToDelete } }),
}) })
.where(joinTable.on || {}) .where(joinTable.on || {})
.orderBy('id') .orderBy('id')
@ -183,8 +183,8 @@ const deleteRelations = async (
.delete() .delete()
.where({ .where({
[joinColumn.name]: id, [joinColumn.name]: id,
[inverseJoinColumn.name]: { $notIn: relsToNotDelete }, [inverseJoinColumn.name]: { $notIn: relIdsToNotDelete },
...(all ? {} : { [inverseJoinColumn.name]: { $in: relsToDelete } }), ...(all ? {} : { [inverseJoinColumn.name]: { $in: relIdsToDelete } }),
}) })
.where(joinTable.on || {}) .where(joinTable.on || {})
.execute(); .execute();

View File

@ -142,6 +142,11 @@ const createCompoLinkModelMeta = (baseModelMeta) => {
name: `${baseModelMeta.tableName}_entity_fk`, name: `${baseModelMeta.tableName}_entity_fk`,
columns: ['entity_id'], columns: ['entity_id'],
}, },
{
name: `${baseModelMeta.tableName}_unique`,
columns: ['entity_id', 'component_id', 'field'],
type: 'unique',
},
], ],
foreignKeys: [ foreignKeys: [
{ {

View File

@ -444,6 +444,11 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
name: `${joinTableName}_inv_fk`, name: `${joinTableName}_inv_fk`,
columns: [inverseJoinColumnName], columns: [inverseJoinColumnName],
}, },
{
name: `${joinTableName}_unique`,
columns: [joinColumnName, inverseJoinColumnName],
type: 'unique',
},
], ],
foreignKeys: [ foreignKeys: [
{ {

View File

@ -23,6 +23,9 @@ const createQueryBuilder = (uid, db, initialState = {}) => {
offset: null, offset: null,
transaction: null, transaction: null,
forUpdate: false, forUpdate: false,
onConflict: null,
merge: null,
ignore: false,
orderBy: [], orderBy: [],
groupBy: [], groupBy: [],
increments: [], increments: [],
@ -69,6 +72,24 @@ const createQueryBuilder = (uid, db, initialState = {}) => {
return this; return this;
}, },
onConflict(args) {
state.onConflict = args;
return this;
},
merge(args) {
state.merge = args;
return this;
},
ignore() {
state.ignore = true;
return this;
},
delete() { delete() {
state.type = 'delete'; state.type = 'delete';
@ -400,6 +421,14 @@ const createQueryBuilder = (uid, db, initialState = {}) => {
state.decrements.forEach((decr) => qb.decrement(decr.column, decr.amount)); state.decrements.forEach((decr) => qb.decrement(decr.column, decr.amount));
} }
if (state.onConflict) {
if (state.merge) {
qb.onConflict(state.onConflict).merge(state.merge);
} else if (state.ignore) {
qb.onConflict(state.onConflict).ignore();
}
}
if (state.limit) { if (state.limit) {
qb.limit(state.limit); qb.limit(state.limit);
} }