connection so the guards can sit between the * backfill and the NOT NULL change; addSql() would defer them to the end. */ final class Version20260728080516 extends AbstractMigration { public function getDescription(): string { return 'Replace doctor_secretaries.owner_type with the shared tenant pair and widen its unique key'; } public function up(Schema $schema): void { $this->connection->executeStatement( 'ALTER TABLE doctor_secretaries ADD entity_type VARCHAR(10) NULL, ADD entity_id INT NULL' ); // A row claiming the clinic environment without a clinic is data we cannot // interpret; stop and let a human decide rather than guessing an owner. $broken = $this->connection->fetchFirstColumn( "SELECT id FROM doctor_secretaries WHERE owner_type = 'clinic' AND clinic_id IS NULL" ); $this->abortIf( $broken !== [], 'Inconsistent doctor_secretaries rows (owner_type=clinic, clinic_id NULL): ' . implode(',', $broken) ); $this->connection->executeStatement( "UPDATE doctor_secretaries SET entity_type = owner_type, entity_id = IF(owner_type = 'clinic', clinic_id, doctor_id)" ); $remaining = (int) $this->connection->fetchOne( 'SELECT COUNT(*) FROM doctor_secretaries WHERE entity_type IS NULL OR entity_id IS NULL' ); $this->abortIf($remaining > 0, "Backfill left {$remaining} doctor_secretaries rows without a tenant."); $this->connection->executeStatement( 'ALTER TABLE doctor_secretaries MODIFY entity_type VARCHAR(10) NOT NULL, MODIFY entity_id INT NOT NULL' ); // Replacement key first, so the table is never without uniqueness cover. $this->connection->executeStatement( 'CREATE UNIQUE INDEX uniq_doctor_secretary_scope ON doctor_secretaries (doctor_id, secretary_id, entity_type, entity_id)' ); $this->connection->executeStatement('DROP INDEX idx_doctor_secretary_scope ON doctor_secretaries'); $this->connection->executeStatement('ALTER TABLE doctor_secretaries DROP owner_type'); } public function down(Schema $schema): void { $this->addSql("ALTER TABLE doctor_secretaries ADD owner_type VARCHAR(10) DEFAULT 'doctor' NOT NULL"); $this->addSql('UPDATE doctor_secretaries SET owner_type = entity_type'); $this->addSql('CREATE UNIQUE INDEX idx_doctor_secretary_scope ON doctor_secretaries (doctor_id, secretary_id, owner_type)'); $this->addSql('DROP INDEX uniq_doctor_secretary_scope ON doctor_secretaries'); $this->addSql('ALTER TABLE doctor_secretaries DROP entity_type, DROP entity_id'); } /** DDL on MariaDB commits implicitly; wrapping up() in a transaction would be a lie. */ public function isTransactional(): bool { return false; } }