migrations/Version20250731132801.php line 1

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace DoctrineMigrations;
  4. use Doctrine\DBAL\Schema\Schema;
  5. use Doctrine\Migrations\AbstractMigration;
  6. /**
  7.  * Composite primary key (creator_id, device_id) for device.
  8.  * The same device_id can belong to different creators; data is preserved.
  9.  */
  10. final class Version20250731132801 extends AbstractMigration
  11. {
  12.     private const DEVICE_TABLE 'device';
  13.     private const DEVICE_CREATOR_FK 'FK_92FB68E61220EA6';
  14.     public function getDescription(): string
  15.     {
  16.         return 'Device: composite primary key (creator_id, device_id), preserve existing data';
  17.     }
  18.     public function up(Schema $schema): void
  19.     {
  20.         $this->addSql('DELETE FROM device WHERE creator_id IS NULL');
  21.         if ($this->foreignKeyExists(self::DEVICE_TABLEself::DEVICE_CREATOR_FK)) {
  22.             $this->addSql('ALTER TABLE device DROP FOREIGN KEY FK_92FB68E61220EA6');
  23.         }
  24.         if (!$this->columnExists(self::DEVICE_TABLE'device_id')) {
  25.             $this->addSql('ALTER TABLE device ADD device_id BINARY(16) DEFAULT NULL COMMENT \'(DC2Type:uuid)\'');
  26.         }
  27.         if ($this->columnExists(self::DEVICE_TABLE'id')) {
  28.             $this->addSql('UPDATE device SET device_id = id WHERE device_id IS NULL');
  29.         }
  30.         $this->addSql('ALTER TABLE device MODIFY device_id BINARY(16) NOT NULL COMMENT \'(DC2Type:uuid)\'');
  31.         $this->addSql('ALTER TABLE device MODIFY creator_id BIGINT UNSIGNED NOT NULL');
  32.         if ($this->columnExists(self::DEVICE_TABLE'id')) {
  33.             $this->addSql('ALTER TABLE device DROP PRIMARY KEY');
  34.             $this->addSql('ALTER TABLE device DROP COLUMN id');
  35.         }
  36.         if (!$this->primaryKeyExists(self::DEVICE_TABLE, ['creator_id''device_id'])) {
  37.             $this->addSql('ALTER TABLE device ADD PRIMARY KEY (creator_id, device_id)');
  38.         }
  39.         if (!$this->foreignKeyExists(self::DEVICE_TABLEself::DEVICE_CREATOR_FK)) {
  40.             $this->addSql('ALTER TABLE device ADD CONSTRAINT FK_92FB68E61220EA6 FOREIGN KEY (creator_id) REFERENCES creator (id) ON DELETE CASCADE');
  41.         }
  42.     }
  43.     public function down(Schema $schema): void
  44.     {
  45.         if ($this->foreignKeyExists(self::DEVICE_TABLEself::DEVICE_CREATOR_FK)) {
  46.             $this->addSql('ALTER TABLE device DROP FOREIGN KEY FK_92FB68E61220EA6');
  47.         }
  48.         $this->addSql('ALTER TABLE device DROP PRIMARY KEY');
  49.         $this->addSql('ALTER TABLE device ADD id BINARY(16) DEFAULT NULL COMMENT \'(DC2Type:uuid)\'');
  50.         $this->addSql('UPDATE device SET id = device_id');
  51.         $this->addSql('ALTER TABLE device MODIFY id BINARY(16) NOT NULL COMMENT \'(DC2Type:uuid)\'');
  52.         $this->addSql('ALTER TABLE device ADD PRIMARY KEY (id)');
  53.         $this->addSql('ALTER TABLE device DROP COLUMN device_id');
  54.         $this->addSql('ALTER TABLE device MODIFY creator_id BIGINT UNSIGNED DEFAULT NULL');
  55.         $this->addSql('ALTER TABLE device ADD CONSTRAINT FK_92FB68E61220EA6 FOREIGN KEY (creator_id) REFERENCES creator (id) ON DELETE CASCADE');
  56.     }
  57.     public function isTransactional(): bool
  58.     {
  59.         return false;
  60.     }
  61.     private function foreignKeyExists(string $tableNamestring $constraintName): bool
  62.     {
  63.         $sql = <<<'SQL'
  64.             SELECT COUNT(*)
  65.             FROM information_schema.TABLE_CONSTRAINTS
  66.             WHERE CONSTRAINT_SCHEMA = DATABASE()
  67.               AND TABLE_NAME = :tableName
  68.               AND CONSTRAINT_NAME = :constraintName
  69.               AND CONSTRAINT_TYPE = 'FOREIGN KEY'
  70.         SQL;
  71.         return (int) $this->connection->fetchOne($sql, [
  72.             'tableName' => $tableName,
  73.             'constraintName' => $constraintName,
  74.         ]) > 0;
  75.     }
  76.     private function columnExists(string $tableNamestring $columnName): bool
  77.     {
  78.         $sql = <<<'SQL'
  79.             SELECT COUNT(*)
  80.             FROM information_schema.COLUMNS
  81.             WHERE TABLE_SCHEMA = DATABASE()
  82.               AND TABLE_NAME = :tableName
  83.               AND COLUMN_NAME = :columnName
  84.         SQL;
  85.         return (int) $this->connection->fetchOne($sql, [
  86.             'tableName' => $tableName,
  87.             'columnName' => $columnName,
  88.         ]) > 0;
  89.     }
  90.     /**
  91.      * @param list<string> $expectedColumns
  92.      */
  93.     private function primaryKeyExists(string $tableName, array $expectedColumns): bool
  94.     {
  95.         $sql = <<<'SQL'
  96.             SELECT COLUMN_NAME
  97.             FROM information_schema.KEY_COLUMN_USAGE
  98.             WHERE TABLE_SCHEMA = DATABASE()
  99.               AND TABLE_NAME = :tableName
  100.               AND CONSTRAINT_NAME = 'PRIMARY'
  101.             ORDER BY ORDINAL_POSITION
  102.         SQL;
  103.         $actualColumns $this->connection->fetchFirstColumn($sql, [
  104.             'tableName' => $tableName,
  105.         ]);
  106.         return $actualColumns === $expectedColumns;
  107.     }
  108. }