-
-
Notifications
You must be signed in to change notification settings - Fork 224
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix truncate on MySQL >= 5.5 #127
base: 1.5.x
Are you sure you want to change the base?
Conversation
PhpStorm shows an error. I think it's because ObjectManager was already imported.
Updated incorrect PhpDoc
addReference() actually throws exception
…et managed my UnitOfWork.
Add explicit visibility for function load
@ddeboer Actually it's version 5.5.7 http://dev.mysql.com/doc/relnotes/mysql/5.5/en/news-5-5-7.html |
@tPl0ch You're right. Unfortunately, Doctrine doesn't allow checking for MySQL version, so we just have to disable foreign key checking on any and all versions. |
I'm afraid this solution is much too specific and should be fixed with a more general approach. Other platforms have this problem, too and need a solution for it just like MySQL. |
As far as I know, only MySQL >= 5.5 has this problem.
How would that work? Something like adding
We can access the SchemaManager through |
@ddeboer other vendors DO have the same limitation: Oracle SQL Server PostgreSQL SQL Anywhere To be honest I would even expect a table truncation to fail if a foreign key constraint referes to the table that should be truncated. Otherwise how would you expect the database to retain data integrity? So what has to be done is either drop integrity constraints pointing to that particular table and recreate afterwards or find a way to temporarily disable foreign key checks for that operation (which should be done optionally with some flag here in data fixtures IMO). What I wanted to suggest is that the schema manager should have a |
@deeky666 Very interesting. Does the limitation hold when the referencing tables are already empty? This is what changed in MySQL's behaviour, with truncate failing even on empty tables when they are referenced. If so, I agree with you that my fix doesn't suffice and we should be looking for a more general solution (in doctrine/dbal) instead. |
@ddeboer I don't know how the other vendors behave for empty tables but IMHO that does not really matter because you cannot rely on empty tables when using data fixtures or am I misunderstanding the issue here? I guess we really might do better finding a general solution here :) |
@deeky666 For this case, it does matter whether truncating empty tables works or not: this library's |
@ddeboer Ah I didn't know that. So this issue might really only relate to that specific MySQL version and it looks more like a bug in MySQL because why shouldn't it work if no referencing rows are present anymore... weird |
So in that case, I guess my fix make sense as it is? |
@jwage @guilhermeblanco Can this PR be merged? |
+1 for this fix |
Sorry for being picky here but I still don't think this issue should be solved in a way it is proposed by this PR. We already have a lot of vendor specific code portions in ORM for example what creates a hard coupling between ORM and specific database vendors and leads to ugly code and opens up a can of worms if we continue like that. This should not be the way to go. We should not check for specific platforms in data-fixture but instead find a general way to fix that in DBAL and be more agnostic about the underlying platform. |
Okay, opened a PR on doctrine/dbal. @deeky666 Tell me what you think. |
Just an idea, i'm not into all the different database providers so not sure if it would be possible. |
What is currently the recommended workaround for truncating all fixtures with the best performance? Remigrating the entire database takes way too long. |
+1 |
👍 What is still needed to get it merged? |
I faced this issue today and this workaround helped me: https://coderwall.com/p/staybw/workaround-for-1701-cannot-truncate-a-table-referenced-in-a-foreign-key-constraint-using-doctrine-fixtures-load-purge-with-truncate |
This comment has been minimized.
This comment has been minimized.
This did not work for me as I am using Caveat of this workaround: The database platform has to be hard-coded. This was acceptable for me in the dev environment. Step-by-StepFind the platform you want to use as a parent class here: https://github.com/doctrine/dbal/tree/2.12.x/lib/Doctrine/DBAL/Platforms. # src/Doctrine/Platform.php
public function getTruncateTableSQL($tableName, $cascade = false)
{
$truncateSql = parent::getTruncateTableSQL($tableName, $cascade);
return 'SET foreign_key_checks = 0;'.$truncateSql.';SET foreign_key_checks = 1;';
} Find the driver your want to use as a parent class here: https://github.com/doctrine/dbal/tree/2.12.x/lib/Doctrine/DBAL/Driver. # src/Doctrine/Driver.php
public function getDatabasePlatform()
{
return new Platform();
} Configure your new driver with the # config/packages/dev/doctrine.yaml
doctrine:
dbal:
driver_class: App\Doctrine\Driver
server_version: 'mariadb-0.0.0' # nonexistant mariadb version so the platform provided by our driver is used
url: '%env(trimschema:DATABASE_URL)%' # stripschema removes the schema from the DATABASE_URL You can create # src/Doctrine/TrimSchemaEnvVarProcessor.php
namespace App\Doctrine;
use Symfony\Component\DependencyInjection\EnvVarProcessorInterface;
class TrimSchemaEnvVarProcessor implements EnvVarProcessorInterface
{
public function getEnv(string $prefix, string $name, \Closure $getEnv)
{
$env = $getEnv($name);
return substr($env, strpos($env, '://') + 3);
}
public static function getProvidedTypes()
{
return [
'trimschema' => 'string',
];
}
} Full Driver & Platform example for MariaDb# src/Doctrine/Platform.php
namespace App\Doctrine;
use Doctrine\DBAL\Platforms\MySqlPlatform;
// cannot use MariaDb1027Platform as parent (as marked final), hence using its parent and then copying its methods
class Platform extends MySqlPlatform
{
public function getTruncateTableSQL($tableName, $cascade = false)
{
$truncateSql = parent::getTruncateTableSQL($tableName, $cascade);
return 'SET foreign_key_checks = 0;'.$truncateSql.';SET foreign_key_checks = 1;';
}
public function getJsonTypeDeclarationSQL(array $column): string
{
return 'LONGTEXT';
}
protected function getReservedKeywordsClass(): string
{
return MariaDb102Keywords::class;
}
protected function initializeDoctrineTypeMappings(): void
{
parent::initializeDoctrineTypeMappings();
$this->doctrineTypeMapping['json'] = Types::JSON;
}
} # src/Doctrine/Driver.php
namespace App\Doctrine;
use Doctrine\DBAL\Driver\PDOMySql;
class Driver extends PDOMySql\Driver
{
public function getDatabasePlatform()
{
return new Platform();
}
} Commit implementing this workaround in a symfony project: baupen/web@64a171e |
I wrote a small decorator to wrap the existing ORM purger with the additional behaviour required. Perhaps it could also verify that the platform is MySQL and give a useful error if it isn't. /**.
* @see https://github.com/doctrine/DoctrineFixturesBundle/issues/50
*/
final class ForeignKeyDisablingOrmPurgerDecorator implements ORMPurgerInterface
{
public function __construct(
private readonly ORMPurgerInterface $delegate,
private readonly Connection $connection,
) {}
public function purge()
{
$this->connection->executeStatement('SET FOREIGN_KEY_CHECKS=0');
$this->delegate->purge();
$this->connection->executeStatement('SET FOREIGN_KEY_CHECKS=1');
}
public function setEntityManager(EntityManagerInterface $em)
{
$this->delegate->setEntityManager($em);
}
} Following the docs, add a factory for that decorator final class ForeignKeyDisablingOrmPurgerDecoratorFactory implements PurgerFactory
{
public function createForEntityManager(
?string $emName,
EntityManagerInterface $em,
array $excluded = [],
bool $purgeWithTruncate = false,
): PurgerInterface {
$ormPurger = new ORMPurger($em, $excluded);
$ormPurger->setPurgeMode($purgeWithTruncate ? ORMPurger::PURGE_MODE_TRUNCATE : ORMPurger::PURGE_MODE_DELETE);
return new ForeignKeyDisablingOrmPurgerDecorator($ormPurger, $em->getConnection());
}
} Register it and then specify it by alias when loading fixtures:
|
When loading fixtures with purge mode truncate (
--purge-with-truncate
in the bundle's command) on MySQL >= 5.5, an error is thrown:It turns out that with MySQL 5.5, the
TRUNCATE
behaviour has changed. From the MySQL docs:With this PR foreign key checks are disabled before starting the purge, and re-enabled after, so purge made truncate works again. Fixes #113, #17.