diff --git a/src/bundle/Core/Command/VirtualFieldDuplicateFixCommand.php b/src/bundle/Core/Command/VirtualFieldDuplicateFixCommand.php index 722a58526b..2be1294db3 100644 --- a/src/bundle/Core/Command/VirtualFieldDuplicateFixCommand.php +++ b/src/bundle/Core/Command/VirtualFieldDuplicateFixCommand.php @@ -9,7 +9,6 @@ namespace Ibexa\Bundle\Core\Command; use Doctrine\DBAL\Connection; -use Exception; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -26,14 +25,17 @@ final class VirtualFieldDuplicateFixCommand extends Command private const DEFAULT_SLEEP = 0; + protected static $defaultName = 'ibexa:content:remove-duplicate-fields'; + + protected static $defaultDescription = 'Removes duplicate fields created as a result of faulty IBX-5388 performance fix.'; + /** @var \Doctrine\DBAL\Connection */ private $connection; public function __construct( Connection $connection ) { - parent::__construct('ibexa:content:remove-duplicate-fields'); - $this->setDescription('Removes duplicate fields created as a result of faulty IBX-5388 performance fix.'); + parent::__construct(); $this->connection = $connection; } @@ -43,7 +45,7 @@ public function configure(): void $this->addOption( 'batch-size', 'b', - InputOption::VALUE_OPTIONAL, + InputOption::VALUE_REQUIRED, 'Number of attributes affected per iteration', self::DEFAULT_BATCH_SIZE ); @@ -51,7 +53,7 @@ public function configure(): void $this->addOption( 'max-iterations', 'i', - InputOption::VALUE_OPTIONAL, + InputOption::VALUE_REQUIRED, 'Max iterations count (default or -1: unlimited)', self::MAX_ITERATIONS_UNLIMITED ); @@ -59,17 +61,10 @@ public function configure(): void $this->addOption( 'sleep', 's', - InputOption::VALUE_OPTIONAL, + InputOption::VALUE_REQUIRED, 'Wait between iterations, in milliseconds', self::DEFAULT_SLEEP ); - - $this->addOption( - 'force', - 'f', - InputOption::VALUE_NONE, - 'Force operation (implies non-interactive mode)', - ); } protected function execute(InputInterface $input, OutputInterface $output): int @@ -78,11 +73,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $stopwatch = new Stopwatch(true); $stopwatch->start('total', 'command'); - $force = $input->getOption('force'); - if ($force) { - $input->setInteractive(false); - } - $batchSize = (int)$input->getOption('batch-size'); if ($batchSize === 0) { $style->warning('Batch size is set to 0. Nothing to do.'); @@ -99,67 +89,65 @@ protected function execute(InputInterface $input, OutputInterface $output): int $sleep = (int)$input->getOption('sleep'); - try { - $totalCount = $this->getDuplicatedAttributeTotalCount($style, $stopwatch); + $totalCount = $this->getDuplicatedAttributeTotalCount($style, $stopwatch); - if ($totalCount > 0) { - $confirmation = $this->askForConfirmation($style); - if (!$confirmation && !$force) { - $style->info('Confirmation rejected. Terminating.'); + if ($totalCount === 0) { + $style->success('Database is clean of attribute duplicates. Nothing to do.'); - return Command::FAILURE; - } - } else { - $style->success('Database is clean of attribute duplicates. Nothing to do.'); + return Command::SUCCESS; + } - return Command::SUCCESS; - } + if ($input->isInteractive()) { + $confirmation = $this->askForConfirmation($style); + if (!$confirmation) { + $style->info('Confirmation rejected. Terminating.'); - $iteration = 1; - $totalDeleted = 0; - do { - $deleted = 0; - $stopwatch->start('iteration', 'sql'); - - $attributes = $this->getDuplicatedAttributesBatch($batchSize); - foreach ($attributes as $attribute) { - $attributeIds = $this->getDuplicatedAttributeIds($attribute); - - $deleted += $this->deleteAttributes($attributeIds); - $totalDeleted += $deleted; - } - - $style->info( - sprintf( - 'Iteration %d: Removed %d duplicates (total removed this execution: %d). [Debug %s]', - $iteration, - $deleted, - $totalDeleted, - $stopwatch->stop('iteration') - ) - ); - - if ($maxIterations !== self::MAX_ITERATIONS_UNLIMITED && ++$iteration > $maxIterations) { - $style->warning('Max iterations count reached. Terminating.'); - - return self::FAILURE; - } - - // Wait, if needed, before moving to next iteration - usleep($sleep * 1000); - } while ($batchSize === count($attributes)); - - $style->success(sprintf( - 'Operation successful. Removed total of %d duplicates. [Debug %s]', - $totalDeleted, - $stopwatch->stop('total') - )); - } catch (Exception $exception) { - $style->error($exception->getMessage()); - - return Command::FAILURE; + return Command::FAILURE; + } } + $iteration = 1; + $totalDeleted = 0; + do { + $deleted = 0; + $stopwatch->start('iteration', 'sql'); + + $attributes = $this->getDuplicatedAttributesBatch($batchSize); + foreach ($attributes as $attribute) { + $attributeIds = $this->getDuplicatedAttributeIds($attribute); + + $iterationDeleted = $this->deleteAttributes($attributeIds); + + $deleted += $iterationDeleted; + $totalDeleted += $iterationDeleted; + } + + $style->info( + sprintf( + 'Iteration %d: Removed %d duplicate database rows (total removed this execution: %d). [Debug %s]', + $iteration, + $deleted, + $totalDeleted, + $stopwatch->stop('iteration') + ) + ); + + if ($maxIterations !== self::MAX_ITERATIONS_UNLIMITED && ++$iteration > $maxIterations) { + $style->warning('Max iterations count reached. Terminating.'); + + return self::SUCCESS; + } + + // Wait, if needed, before moving to next iteration + usleep($sleep * 1000); + } while ($batchSize === count($attributes)); + + $style->success(sprintf( + 'Operation successful. Removed total of %d duplicate database rows. [Debug %s]', + $totalDeleted, + $stopwatch->stop('total') + )); + return Command::SUCCESS; } @@ -229,22 +217,18 @@ private function getDuplicatedAttributeIds(array $attribute): array $query ->select('id') ->from('ezcontentobject_attribute') - ->where('version = :version') + ->andWhere('version = :version') ->andWhere('contentclassattribute_id = :contentclassattribute_id') ->andWhere('contentobject_id = :contentobject_id') ->andWhere('language_id = :language_id') ->orderBy('id', 'ASC') - ->setFirstResult(0); + // Keep the original attribute row, the very first one + ->setFirstResult(1); $query->setParameters($attribute); - $result = $query->execute()->fetchFirstColumn(); - $attributeIds = array_map('intval', $result); - - // Keep the original attribute row, the very first one - array_shift($attributeIds); - return $attributeIds; + return array_map('intval', $result); } private function askForConfirmation(SymfonyStyle $style): bool @@ -265,7 +249,7 @@ private function deleteAttributes($ids): int $query ->delete('ezcontentobject_attribute') - ->where($query->expr()->in('id', $ids)); + ->andWhere($query->expr()->in('id', $ids)); return (int)$query->execute(); }