From 953153500d490f5b5abf7283c34242c3b22a855a Mon Sep 17 00:00:00 2001 From: Robbie Averill Date: Fri, 18 May 2018 13:04:36 +1200 Subject: [PATCH] FIX Polymorphic relationship class columns have obsolete class names remapped --- src/ORM/DatabaseAdmin.php | 112 ++++++++++++++++++++++++++++---------- 1 file changed, 83 insertions(+), 29 deletions(-) diff --git a/src/ORM/DatabaseAdmin.php b/src/ORM/DatabaseAdmin.php index c556c7bb0f7..cedcdec663f 100644 --- a/src/ORM/DatabaseAdmin.php +++ b/src/ORM/DatabaseAdmin.php @@ -7,9 +7,11 @@ use SilverStripe\Control\Director; use SilverStripe\Core\ClassInfo; use SilverStripe\Core\Environment; +use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Manifest\ClassLoader; use SilverStripe\Dev\DevelopmentAdmin; use SilverStripe\Dev\TestOnly; +use SilverStripe\ORM\FieldType\DBClassName; use SilverStripe\Security\Permission; use SilverStripe\Security\Security; use SilverStripe\Versioned\Versioned; @@ -319,35 +321,12 @@ public function doBuild($quiet = false, $populate = true, $testMode = false) } // Remap obsolete class names - $schema = DataObject::getSchema(); - foreach ($this->config()->classname_value_remapping as $oldClassName => $newClassName) { - $baseDataClass = $schema->baseDataClass($newClassName); - $badRecordCount = DataObject::get($baseDataClass) - ->filter(["ClassName" => $oldClassName ]) - ->count(); - if ($badRecordCount > 0) { - if (Director::is_cli()) { - echo " * Correcting $badRecordCount obsolete classname values for $newClassName\n"; - } else { - echo "
  • Correcting $badRecordCount obsolete classname values for $newClassName
  • \n"; - } - $table = $schema->baseDataTable($baseDataClass); - - $updateQuery = "UPDATE \"$table%s\" SET \"ClassName\" = ? WHERE \"ClassName\" = ?"; - $updateQueries = [sprintf($updateQuery, '')]; - - // Remap versioned table ClassName values as well - /** @var Versioned|DataObject $class */ - $class = DataObject::singleton($newClassName); - if ($class->hasExtension(Versioned::class)) { - if ($class->hasStages()) { - $updateQueries[] = sprintf($updateQuery, '_Live'); - } - $updateQueries[] = sprintf($updateQuery, '_Versions'); - } - - foreach ($updateQueries as $query) { - DB::prepared_query($query, [$newClassName, $oldClassName]); + $remappingConfig = $this->config()->get('classname_value_remapping'); + $remappingFields = $this->getClassNameRemappingFields(); + foreach ($remappingFields as $className => $fieldNames) { + foreach ($fieldNames as $fieldName) { + foreach ($remappingConfig as $oldClassName => $newClassName) { + $this->updateLegacyClassNames($className, $fieldName, $oldClassName, $newClassName); } } } @@ -390,6 +369,81 @@ public function doBuild($quiet = false, $populate = true, $testMode = false) ClassInfo::reset_db_cache(); } + /** + * Given a base data class, a field name and an old and new class name (value), look for obsolete ($oldClassName) + * values in the $dataClass's $fieldName column and replace it with $newClassName. + * + * @param string $dataClass The data class to look up + * @param string $fieldName The field name to look in for obsolete class names + * @param string $oldClassName The old class name + * @param string $newClassName The new class name + */ + protected function updateLegacyClassNames($dataClass, $fieldName, $oldClassName, $newClassName) + { + $schema = DataObject::getSchema(); + // Check first to ensure that the class has the specified field to update + if (!$schema->databaseField($dataClass, $fieldName, false)) { + return; + } + + // Load a list of any records that have obsolete class names + $badRecordCount = DataObject::get($dataClass) + ->filter([$fieldName => $oldClassName]) + ->count(); + + if (!$badRecordCount) { + return; + } + + if (Director::is_cli()) { + echo " * Correcting {$badRecordCount} obsolete {$fieldName} values for {$newClassName}\n"; + } else { + echo "
  • Correcting {$badRecordCount} obsolete {$fieldName} values for {$newClassName}
  • \n"; + } + $table = $schema->tableName($dataClass); + + $updateQuery = "UPDATE \"{$table}%s\" SET \"{$fieldName}\" = ? WHERE \"{$fieldName}\" = ?"; + $updateQueries = [sprintf($updateQuery, '')]; + + // Remap versioned table class name values as well + /** @var Versioned|DataObject $class */ + $class = DataObject::singleton($newClassName); + if ($class->hasExtension(Versioned::class)) { + if ($class->hasStages()) { + $updateQueries[] = sprintf($updateQuery, '_Live'); + } + $updateQueries[] = sprintf($updateQuery, '_Versions'); + } + + foreach ($updateQueries as $query) { + DB::prepared_query($query, [$newClassName, $oldClassName]); + } + } + + /** + * Find all DBClassName fields on valid subclasses of DataObject that should be remapped. This includes + * `ClassName` fields as well as polymorphic class name fields. + * + * @return array[] + */ + protected function getClassNameRemappingFields() + { + $dataClasses = ClassInfo::getValidSubClasses(DataObject::class); + $schema = DataObject::getSchema(); + $remapping = []; + + foreach ($dataClasses as $className) { + $fieldSpecs = $schema->fieldSpecs($className); + foreach ($fieldSpecs as $fieldName => $fieldSpec) { + if (Injector::inst()->create($fieldSpec, 'Dummy') instanceof DBClassName) { + $remapping[$className][] = $fieldName; + } + } + } + + return $remapping; + } + /** * Remove invalid records from tables - that is, records that don't have * corresponding records in their parent class tables.