-- ===================================================================== -- 039_integrity_keys.sql -- Hardening integrità referenziale: UNIQUE su chiavi naturali mancanti + -- FOREIGN KEY mancanti (stesso-DB) + rimozione indice UNIQUE ridondante. -- -- Applicato su PRODUZIONE (nis2_agile_db) il 2026-06-12 tramite runner -- idempotente PHP (pre-check duplicati/orfani/tipi). Risultato: 18 applicati, -- 0 errori, 2 differiti (vedi sezione DEFERRED in coda). -- -- NB: MySQL 8 standard NON supporta "ADD ... IF NOT EXISTS" su indici/FK: -- questo file documenta il DDL applicato. Per ri-applicare in modo sicuro -- usare il runner con guardie su information_schema (controllo esistenza + -- duplicati + righe orfane), NON eseguire ciecamente su un DB già migrato. -- ===================================================================== -- 1) Rimozione UNIQUE ridondante: invites aveva due indici unique identici -- su token_hash (token_hash + uq_invites_token). Si tiene uq_invites_token. ALTER TABLE invites DROP INDEX token_hash; -- 2) UNIQUE mancanti su chiavi naturali (0 duplicati verificati sul dato reale) ALTER TABLE api_keys ADD UNIQUE KEY uq_api_keys_key_hash (key_hash); ALTER TABLE refresh_tokens ADD UNIQUE KEY uq_refresh_tokens_token (token); ALTER TABLE consulting_firms ADD UNIQUE KEY uq_consulting_firms_vat (vat_number); -- 3) FOREIGN KEY mancanti (stesso-DB, 0 righe orfane verificate). -- Convenzione ON DELETE coerente con lo schema esistente: -- organization_id / entità-padre -> CASCADE; link nullable -> SET NULL. ALTER TABLE policy_attestations ADD CONSTRAINT fk_policy_attestations_policy_id FOREIGN KEY (policy_id) REFERENCES policies(id) ON DELETE CASCADE ON UPDATE CASCADE, ADD CONSTRAINT fk_policy_attestations_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE, ADD CONSTRAINT fk_policy_attestations_organization_id FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE policy_versions ADD CONSTRAINT fk_policy_versions_policy_id FOREIGN KEY (policy_id) REFERENCES policies(id) ON DELETE CASCADE ON UPDATE CASCADE, ADD CONSTRAINT fk_policy_versions_organization_id FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE org_acn_requirement_status ADD CONSTRAINT fk_org_acn_requirement_status_organization_id FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE ON UPDATE CASCADE, ADD CONSTRAINT fk_org_acn_requirement_status_requirement_id FOREIGN KEY (requirement_id) REFERENCES acn_requirements(id) ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE supplier_questionnaires ADD CONSTRAINT fk_supplier_questionnaires_organization_id FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE ON UPDATE CASCADE, ADD CONSTRAINT fk_supplier_questionnaires_supplier_id FOREIGN KEY (supplier_id) REFERENCES suppliers(id) ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE org_connectors ADD CONSTRAINT fk_org_connectors_organization_id FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE control_evidence_auto ADD CONSTRAINT fk_control_evidence_auto_organization_id FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE firm_org_assignments ADD CONSTRAINT fk_firm_org_assignments_consulting_firm_id FOREIGN KEY (consulting_firm_id) REFERENCES consulting_firms(id) ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE kri ADD CONSTRAINT fk_kri_linked_risk_id FOREIGN KEY (linked_risk_id) REFERENCES risks(id) ON DELETE SET NULL ON UPDATE CASCADE, ADD CONSTRAINT fk_kri_organization_id FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE ON UPDATE CASCADE; -- ===================================================================== -- DEFERRED (NON applicate il 2026-06-12) — richiedono decisione/bonifica: -- -- a) supplier_categories.organization_id -> organizations -- 10 righe con organization_id = 0 = SENTINEL "categoria di sistema/globale" -- (seed migrazione 033). Una FK qui è SBAGLIATA per design (0 non è una org). -- Azione corretta: lasciare il sentinel; eventualmente in futuro rendere la -- colonna nullable usando NULL per le categorie globali + FK. NON fatto per -- non toccare la semantica delle query applicative. -- -- b) firm_org_assignments.organization_id -> organizations -- 9 righe puntano a org 126/127/128/129 (consulting_firm 1 "Agile", dogfooding) -- che NON esistono in questo DB. Riferimenti rotti/parziali. Servono: -- (1) seed delle org 126-129, OPPURE (2) DELETE delle 9 righe stale, -- poi: ALTER TABLE firm_org_assignments -- ADD CONSTRAINT fk_firm_org_assignments_organization_id -- FOREIGN KEY (organization_id) REFERENCES organizations(id) -- ON DELETE CASCADE ON UPDATE CASCADE; -- =====================================================================