From ab21614a5a4d61c15467c322652033affad20011 Mon Sep 17 00:00:00 2001 From: Keno Dressel Date: Thu, 11 Jul 2024 13:47:23 +0200 Subject: [PATCH] fix(notifications): adds index to also deduplicate notifications without source --- ...20240711-add-index-unique-notifications.js | 94 +++++++++++++++++++ migrations/_current.json | 22 ++++- modules/notification/models/notification.js | 8 ++ 3 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 migrations/20240711-add-index-unique-notifications.js diff --git a/migrations/20240711-add-index-unique-notifications.js b/migrations/20240711-add-index-unique-notifications.js new file mode 100644 index 00000000..5bc5b334 --- /dev/null +++ b/migrations/20240711-add-index-unique-notifications.js @@ -0,0 +1,94 @@ +'use strict'; + +var Sequelize = require('sequelize'); + +/** + * Actions summary: + * + * addIndex "notifications_type_entity_id_entity_type_receiver" to table "Notifications" + * + **/ + +var info = { + 'revision': 29, + 'name': 'add-index-unique-notifications', + 'created': '2024-07-11T11:41:47.319Z', + 'comment': '' +}; + +var migrationCommands = function (transaction) { + return []; +}; +var rollbackCommands = function (transaction) { + return [{ + fn: 'removeIndex', + params: [ + 'Notifications', + 'notifications_type_entity_id_entity_type_receiver', + { + transaction: transaction + } + ] + }, + ]; +}; + +module.exports = { + pos: 0, + useTransaction: true, + execute: function (queryInterface, Sequelize, _commands) { + var index = this.pos; + + function run(transaction) { + const commands = _commands(transaction); + return new Promise(function (resolve, reject) { + function next() { + if (index < commands.length) { + let command = commands[index]; + console.log('[#' + index + '] execute: ' + command.fn); + index++; + queryInterface[command.fn].apply(queryInterface, command.params) + .then(next, reject); + } else { + resolve(); + } + } + + next(); + }); + } + + if (this.useTransaction) { + return queryInterface.sequelize.transaction(run); + } else { + return run(null); + } + }, + up: async function (queryInterface, Sequelize) { + const transaction = await queryInterface.sequelize.transaction(); + // DROP ALL NOTIFICATIONS THAT WOULD HINDER THE INDEX + await queryInterface.sequelize.query('DELETE FROM "Notifications"\n' + + 'WHERE "id" IN (\n' + + ' SELECT "id"\n' + + ' FROM (\n' + + ' SELECT \n' + + ' "id",\n' + + ' ROW_NUMBER() OVER (\n' + + ' PARTITION BY "type", "entityId", "entityType", "receiver"\n' + + ' ORDER BY "id"\n' + + ' ) AS row_num\n' + + ' FROM "Notifications"\n' + + ' WHERE "sourceType" IS NULL AND "sourceId" IS NULL\n' + + ' ) sub\n' + + ' WHERE sub.row_num > 1\n' + + ');', { transaction }); + // CREATE INDEX MANUALLY BECAUSE SEQUELIZE HAS ISSUES WITH WHERE IS NULL + await queryInterface.sequelize.query('CREATE UNIQUE INDEX "notifications_type_entity_id_entity_type_receiver" ON "Notifications" ("type", "entityId", "entityType", "receiver") WHERE "sourceType" IS NULL AND "sourceId" IS NULL;', { transaction }); + await transaction.commit(); + return this.execute(queryInterface, Sequelize, migrationCommands); + }, + down: function (queryInterface, Sequelize) { + return this.execute(queryInterface, Sequelize, rollbackCommands); + }, + info: info +}; diff --git a/migrations/_current.json b/migrations/_current.json index c17a9962..69720800 100644 --- a/migrations/_current.json +++ b/migrations/_current.json @@ -580,6 +580,26 @@ "indicesType": "UNIQUE", "type": "UNIQUE" } + }, + "fe94400734466dc2b18423d6187d2e9aa5a1a396": { + "unique": true, + "fields": [ + "type", + "entityId", + "entityType", + "receiver" + ], + "where": { + "sourceType": null, + "sourceId": null + }, + "name": "notifications_type_entity_id_entity_type_receiver", + "options": { + "indexName": "notifications_type_entity_id_entity_type_receiver", + "name": "notifications_type_entity_id_entity_type_receiver", + "indicesType": "UNIQUE", + "type": "UNIQUE" + } } } }, @@ -963,5 +983,5 @@ "indexes": [] } }, - "revision": 28 + "revision": 29 } diff --git a/modules/notification/models/notification.js b/modules/notification/models/notification.js index ed752a8b..534a7176 100644 --- a/modules/notification/models/notification.js +++ b/modules/notification/models/notification.js @@ -51,6 +51,14 @@ module.exports = (sequelize, DataTypes) => sequelize.define('Notification', { unique: true, fields: ['type', 'entityId', 'entityType', 'receiver', 'sourceType', 'sourceId'], }, + { + unique: true, + fields: ['type', 'entityId', 'entityType', 'receiver'], + where: { // seems not to be applied in the generated SQL query + sourceType: null, + sourceId: null, + }, + }, ], timestamps: true, });