db->DBDriver !== 'MySQLi') { throw new RuntimeException('This migration requires MySQL/MariaDB through the MySQLi driver.'); } if (! $this->db->tableExists('whatsapp_group_links') || ! $this->db->fieldExists('class_section_id', 'whatsapp_group_links') || ! $this->db->fieldExists('school_year', 'whatsapp_group_links')) { return; } $duplicates = $this->db->query( 'SELECT class_section_id, school_year, COUNT(*) AS row_count FROM `whatsapp_group_links` GROUP BY class_section_id, school_year HAVING COUNT(*) > 1 LIMIT 5' )->getResultArray(); if ($duplicates !== []) { throw new RuntimeException( 'Cannot add whatsapp_group_links unique key by school year because duplicate class_section_id/school_year rows exist: ' . json_encode($duplicates) ); } $this->dropIndexIfExists('whatsapp_group_links', 'uq_section_term'); $this->db->query( 'ALTER TABLE `whatsapp_group_links` ADD UNIQUE INDEX `uq_section_term` (`class_section_id`, `school_year`)' ); } public function down(): void { throw new RuntimeException('This migration is intentionally irreversible because reverting can fail once multiple school years exist for the same section.'); } private function dropIndexIfExists(string $table, string $index): void { $exists = $this->db->query( 'SELECT COUNT(*) AS aggregate FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND INDEX_NAME = ?', [$table, $index] )->getRow(); if ((int) ($exists->aggregate ?? 0) === 0) { return; } $this->db->query(sprintf( 'ALTER TABLE `%s` DROP INDEX `%s`', str_replace('`', '``', $table), str_replace('`', '``', $index) )); } }