85 lines
3.7 KiB
PHP
85 lines
3.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use MeshVault\Catalog\ManagedPath;
|
|
use Phinx\Migration\AbstractMigration;
|
|
|
|
/** Writers must be stopped for both directions, including the read-only preflight. */
|
|
final class CatalogIdentity extends AbstractMigration
|
|
{
|
|
public function up(): void
|
|
{
|
|
$problems = [];
|
|
foreach ($this->fetchAll('SELECT id, managed_path FROM files ORDER BY id') as $file) {
|
|
try {
|
|
ManagedPath::validate($file['managed_path']);
|
|
} catch (InvalidArgumentException) {
|
|
$problems[] = 'noncanonical managed path on file ID ' . $file['id'];
|
|
}
|
|
}
|
|
$duplicates = $this->fetchAll('SELECT f.id FROM files f INNER JOIN
|
|
(SELECT managed_path FROM files GROUP BY managed_path HAVING COUNT(*) > 1) d
|
|
ON f.managed_path = d.managed_path ORDER BY f.id');
|
|
if ($duplicates !== []) {
|
|
$problems[] = 'duplicate managed paths on file IDs ' . implode(', ', array_column($duplicates, 'id'));
|
|
}
|
|
$this->reject(
|
|
$problems,
|
|
'Resolve paths and ownership explicitly; do not merge or delete records automatically.'
|
|
);
|
|
|
|
$this->table('files')
|
|
->dropForeignKey('model_id')
|
|
->changeColumn('model_id', 'integer', ['null' => true, 'signed' => false])
|
|
->addForeignKey('model_id', 'models', 'id', ['delete' => 'RESTRICT', 'update' => 'NO_ACTION'])
|
|
->removeIndexByName('idx_files_model_path')
|
|
->addIndex(['managed_path'], ['unique' => true, 'name' => 'idx_files_managed_path'])
|
|
->addIndex(['model_id'], ['name' => 'idx_files_model_id'])
|
|
->update();
|
|
$this->table('models')
|
|
->removeIndexByName('idx_models_folder_name')
|
|
->addIndex(['folder_id', 'name'], ['name' => 'idx_models_folder_name'])
|
|
->update();
|
|
}
|
|
|
|
public function down(): void
|
|
{
|
|
$problems = [];
|
|
$unassigned = $this->fetchAll('SELECT id FROM files WHERE model_id IS NULL ORDER BY id');
|
|
if ($unassigned !== []) {
|
|
$problems[] = 'unassigned file IDs ' . implode(', ', array_column($unassigned, 'id'));
|
|
}
|
|
$duplicates = $this->fetchAll('SELECT m.id FROM models m INNER JOIN
|
|
(SELECT folder_id, name FROM models GROUP BY folder_id, name HAVING COUNT(*) > 1) d
|
|
ON m.folder_id = d.folder_id AND m.name = d.name ORDER BY m.id');
|
|
if ($duplicates !== []) {
|
|
$problems[] = 'duplicate same-folder titles on model IDs ' . implode(', ', array_column($duplicates, 'id'));
|
|
}
|
|
$this->reject(
|
|
$problems,
|
|
'Resolve by explicitly assigning files and disambiguating model titles before retrying.'
|
|
);
|
|
|
|
$this->table('files')
|
|
->dropForeignKey('model_id')
|
|
->changeColumn('model_id', 'integer', ['null' => false, 'signed' => false])
|
|
->addForeignKey('model_id', 'models', 'id', ['delete' => 'CASCADE', 'update' => 'NO_ACTION'])
|
|
->removeIndexByName('idx_files_managed_path')
|
|
->removeIndexByName('idx_files_model_id')
|
|
->addIndex(['model_id', 'managed_path'], ['unique' => true, 'name' => 'idx_files_model_path'])
|
|
->update();
|
|
$this->table('models')
|
|
->removeIndexByName('idx_models_folder_name')
|
|
->addIndex(['folder_id', 'name'], ['unique' => true, 'name' => 'idx_models_folder_name'])
|
|
->update();
|
|
}
|
|
|
|
private function reject(array $problems, string $guidance): void
|
|
{
|
|
if ($problems !== []) {
|
|
throw new RuntimeException('Catalog identity preflight: ' . implode('; ', $problems) . '. ' . $guidance);
|
|
}
|
|
}
|
|
}
|