if (preg_match('/^ALTER\s+TABLE\s+`?(\w+)`?\s+ADD\s+COLUMN\s+`?(\w+)`?/i', $stmt, $m)) { $table = $m[1]; $col = $m[2]; $st = $pdo->prepare( 'SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?' ); $st->execute([$table, $col]); if ($st->fetchColumn()) { echo " [skip] colonna gia presente: {$table}.{$col}\n"; return true; } return false; } // ADD INDEX if (preg_match('/^ALTER\s+TABLE\s+`?(\w+)`?\s+ADD\s+(?:INDEX|KEY)\s+`?(\w+)`?/i', $stmt, $m)) { $table = $m[1]; $idx = $m[2]; $st = $pdo->prepare( 'SELECT 1 FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND INDEX_NAME = ?' ); $st->execute([$table, $idx]); if ($st->fetchColumn()) { echo " [skip] indice gia presente: {$table}.{$idx}\n"; return true; } return false; } return false; } foreach ($files as $file) { if (!is_readable($file)) { fwrite(STDERR, "[migrate-a4] File non leggibile: $file\n"); exit(1); } $sql = (string) file_get_contents($file); // Rimuove i commenti "--" (riga intera E in coda): un commento puo contenere // ';' e spezzerebbe lo split. Le migrazioni non usano '--' dentro valori. $lines = preg_split('/\r?\n/', $sql); $clean = []; foreach ($lines as $l) { $pos = strpos($l, '--'); if ($pos !== false) { $l = substr($l, 0, $pos); } if (trim($l) === '') { continue; } $clean[] = $l; } $sql = implode("\n", $clean); // Split semplice su ';' (nessun DELIMITER / stored procedure in questi file: // 045 usa ALTER nude, l'idempotenza e' nel pre-check shouldSkipAlter()). $statements = array_filter(array_map('trim', explode(';', $sql)), fn($s) => $s !== ''); echo "== " . basename($file) . " (" . count($statements) . " statement) ==\n"; foreach ($statements as $stmt) { // ALTER non nativamente idempotenti (es. mig.045 suppliers.stakeholder_type): // pre-check su information_schema, salta se colonna/indice gia presenti. if (shouldSkipAlter($pdo, $stmt)) { continue; } try { $pdo->exec($stmt); $totalStmts++; } catch (PDOException $e) { $head = substr(preg_replace('/\s+/', ' ', $stmt), 0, 80); fwrite(STDERR, "[ERRORE] su \"$head...\": " . $e->getMessage() . "\n"); exit(1); } } } echo "\n[OK] Eseguiti $totalStmts statement.\n"; // Verifica rapida: tabella org_roles (4.1) e le 4 tabelle competenze (4.2). $cols = $pdo->query("SHOW COLUMNS FROM org_roles")->fetchAll(PDO::FETCH_COLUMN, 0); $expected = ['id','organization_id','role_name','parent_role_id','holder_user_id','is_governance_body','description','sort_order','created_by','created_at','updated_at']; $missing = array_diff($expected, $cols); if ($missing) { fwrite(STDERR, "[ATTENZIONE] org_roles colonne mancanti: " . implode(', ', $missing) . "\n"); exit(1); } echo "[OK] org_roles pronta (A4 Fase 4.1).\n"; foreach (['skills','role_skills','user_skills','skill_course_map'] as $t) { $c = $pdo->query("SHOW COLUMNS FROM `$t`")->fetchAll(PDO::FETCH_COLUMN, 0); if (!$c) { fwrite(STDERR, "[ATTENZIONE] tabella mancante: $t\n"); exit(1); } echo "[OK] $t pronta (" . count($c) . " colonne).\n"; } echo "[OK] Competenze pronte (A4 Fase 4.2).\n"; // Verifica 4.3 (RACI + link m2m), 4.4 (scadenziario), 4.5 (stakeholder). foreach (['raci_assignments','procedure_inventory','procedure_risk','inventory_risk','risk_measure','review_schedule'] as $t) { $c = $pdo->query("SHOW COLUMNS FROM `$t`")->fetchAll(PDO::FETCH_COLUMN, 0); if (!$c) { fwrite(STDERR, "[ATTENZIONE] tabella mancante: $t\n"); exit(1); } echo "[OK] $t pronta (" . count($c) . " colonne).\n"; } echo "[OK] RACI + scadenziario pronti (A4 Fasi 4.3 e 4.4).\n"; $supCols = $pdo->query("SHOW COLUMNS FROM suppliers")->fetchAll(PDO::FETCH_COLUMN, 0); if (!in_array('stakeholder_type', $supCols, true)) { fwrite(STDERR, "[ATTENZIONE] suppliers.stakeholder_type mancante (A4 Fase 4.5)\n"); exit(1); } echo "[OK] suppliers.stakeholder_type pronta (A4 Fase 4.5).\n";