-- ============================================================ -- NIS2 Agile - Migration 006: Security & Performance Improvements -- Data: 2026-02-20 -- Compatibile: MySQL 8.0 (senza CREATE INDEX IF NOT EXISTS) -- ============================================================ -- Procedura helper: crea indice solo se non esiste DROP PROCEDURE IF EXISTS create_index_if_not_exists; DELIMITER // CREATE PROCEDURE create_index_if_not_exists( IN p_table VARCHAR(100), IN p_index VARCHAR(100), IN p_cols VARCHAR(500) ) BEGIN IF NOT EXISTS ( SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = p_table AND index_name = p_index ) THEN SET @sql = CONCAT('CREATE INDEX `', p_index, '` ON `', p_table, '` (', p_cols, ')'); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; END IF; END // DELIMITER ; -- ── 1. Indici performance su incidents ──────────────────────────────────── CALL create_index_if_not_exists('incidents', 'idx_inc_org_status', 'organization_id, status'); CALL create_index_if_not_exists('incidents', 'idx_inc_org_significant', 'organization_id, is_significant'); CALL create_index_if_not_exists('incidents', 'idx_inc_early_warning_due','organization_id, early_warning_due'); CALL create_index_if_not_exists('incidents', 'idx_inc_notification_due', 'organization_id, notification_due'); CALL create_index_if_not_exists('incidents', 'idx_inc_final_report_due', 'organization_id, final_report_due'); -- ── 2. Indici performance su risks ──────────────────────────────────────── CALL create_index_if_not_exists('risks', 'idx_risks_org_status', 'organization_id, status'); CALL create_index_if_not_exists('risks', 'idx_risks_score', 'organization_id, inherent_risk_score'); -- ── 3. Indici performance su audit_logs ─────────────────────────────────── CALL create_index_if_not_exists('audit_logs', 'idx_audit_org_created', 'organization_id, created_at'); CALL create_index_if_not_exists('audit_logs', 'idx_audit_entity', 'entity_type, entity_id'); -- ── 4. Trigger per rendere audit_log immutabile ─────────────────────────── DROP TRIGGER IF EXISTS prevent_audit_log_update; CREATE TRIGGER prevent_audit_log_update BEFORE UPDATE ON audit_logs FOR EACH ROW BEGIN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'audit_logs is append-only: UPDATE not permitted'; END; DROP TRIGGER IF EXISTS prevent_audit_log_delete; CREATE TRIGGER prevent_audit_log_delete BEFORE DELETE ON audit_logs FOR EACH ROW BEGIN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'audit_logs is append-only: DELETE not permitted'; END; -- ── 5. Soft delete su tabelle critiche ──────────────────────────────────── ALTER TABLE risks ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMP NULL DEFAULT NULL AFTER updated_at; ALTER TABLE policies ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMP NULL DEFAULT NULL AFTER updated_at; ALTER TABLE suppliers ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMP NULL DEFAULT NULL AFTER updated_at; -- Indici per le colonne deleted_at CALL create_index_if_not_exists('risks', 'idx_risks_deleted', 'organization_id, deleted_at'); CALL create_index_if_not_exists('policies', 'idx_policies_deleted', 'organization_id, deleted_at'); -- ── 6. Indice su refresh_tokens ─────────────────────────────────────────── CALL create_index_if_not_exists('refresh_tokens', 'idx_refresh_user_expires', 'user_id, expires_at'); -- ── 7. Pulizia procedura temporanea ─────────────────────────────────────── DROP PROCEDURE IF EXISTS create_index_if_not_exists; -- ── 8. Verifica ─────────────────────────────────────────────────────────── SELECT 'Migration 006 completed successfully' AS status;